CoursesDocker in depthPractical recipes

Recipe: MongoDB

Auth, a named volume, and a healthcheck.

Intermediate10 min · lesson 25 of 30

MongoDB out of the box historically bound to all interfaces with no auth — the source of countless breached databases. The official image fixes the defaults if you use them: set a root user and password (as a secret), put the data on a named volume, and never publish the port unless you must. Enable authentication from the first run, not after data is already in.

compose.yaml
services:
mongo:
image: mongo:7
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD_FILE: /run/secrets/mongo_pw # _FILE = read from secret
MONGO_INITDB_DATABASE: payments
secrets: [mongo_pw]
volumes: ["mongodata:/data/db"]
healthcheck:
test: ["CMD","mongosh","--quiet","--eval","db.adminCommand('ping')"]
interval: 10s
# no ports: — reachable only from other services on the network
volumes: { mongodata: {} }
secrets: { mongo_pw: { file: ./secrets/mongo_pw.txt } }

Root user vs application user

MONGO_INITDB_ROOT_* creates an admin user, but your application should not connect as root. Use an init script — any .js or .sh placed in /docker-entrypoint-initdb.d runs once on first startup — to create a least-privileged user scoped to just the application database. The app then authenticates as that user, so a leaked app credential cannot administer the whole server.

/docker-entrypoint-initdb.d/01-app-user.js
db = db.getSiblingDB("payments");
db.createUser({
user: "app",
pwd: process.env.APP_DB_PASSWORD, // supplied via secret/env
roles: [{ role: "readWrite", db: "payments" }] // scoped, not admin
});

The init-script directory is a general pattern across the official database images (Postgres, MySQL, MongoDB): drop schema, seed data, or user-creation scripts there and they run once, in order, on an empty data directory. On a volume that already has data, they are skipped — so they are first-run bootstrap, not migrations.

Turn on auth before the first byte of data
A Mongo instance that runs even briefly without authentication, on a reachable port, can be found and emptied by automated scanners in minutes. Set the root credentials on the very first run, create a scoped app user, and keep the port unpublished. Retrofitting auth onto a running, exposed instance is a race you can lose.