Recipe: MongoDB
Auth, a named volume, and a healthcheck.
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.
services:mongo:image: mongo:7environment:MONGO_INITDB_ROOT_USERNAME: rootMONGO_INITDB_ROOT_PASSWORD_FILE: /run/secrets/mongo_pw # _FILE = read from secretMONGO_INITDB_DATABASE: paymentssecrets: [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 networkvolumes: { 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.
db = db.getSiblingDB("payments");db.createUser({user: "app",pwd: process.env.APP_DB_PASSWORD, // supplied via secret/envroles: [{ 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.