MongoDB in Docker Compose

services: some-mongo: image: mongo:9.0 volumes: - mongo-data:/data/db environment: MONGO_INITDB_ROOT_USERNAME: mongoadmin MONGO_INITDB_ROOT_PASSWORD: secret volumes: mongo-data: name: mongo-data

In Docker Compose, MongoDB keeps its data in a volume mounted at /data/db: the line mongo-data:/data/db under the service’s volumes. MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD, set together, create a root user in the admin database and start mongod with --auth. Without them, anyone who reaches the port can do anything.

The docker run command, converted

The image’s Docker Hub page shows the volume and the root user in two commands; together, with the tag pinned:

docker run --name some-mongo -v mongo-data:/data/db -e MONGO_INITDB_ROOT_USERNAME=mongoadmin -e MONGO_INITDB_ROOT_PASSWORD=secret -d mongo:9.0
services:
  some-mongo:
    image: mongo:9.0
    volumes:
      - mongo-data:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongoadmin
      MONGO_INITDB_ROOT_PASSWORD: secret
volumes:
  mongo-data:
    name: mongo-data

Use a named volume here rather than a host folder: the image’s documentation warns that on macOS and Windows, the file sharing Docker Desktop uses doesn’t work with MongoDB’s memory-mapped files. The page’s root-user example also passes --network some-network, which the converter would declare external: true. In Compose you can leave it out, since services share the project’s network and find each other by name.

A database the app waits for

services:
  mongo:
    image: mongo:9.0
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongoadmin
      MONGO_INITDB_ROOT_PASSWORD: secret
    volumes:
      - mongo-data:/data/db
    ports:
      - "127.0.0.1:27017:27017"
    healthcheck:
      test: mongosh --quiet --host "$$(hostname)" --eval "db.adminCommand('ping').ok"
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 20s
  mongo-express:
    image: mongo-express:1.0
    ports:
      - "127.0.0.1:8081:8081"
    environment:
      ME_CONFIG_MONGODB_URL: mongodb://mongoadmin:secret@mongo:27017/
      ME_CONFIG_BASICAUTH_ENABLED: "true"
      ME_CONFIG_BASICAUTH_USERNAME: admin
      ME_CONFIG_BASICAUTH_PASSWORD: change-me
    depends_on:
      mongo:
        condition: service_healthy
volumes:
  mongo-data:

The check asks the container’s own hostname, not localhost. On first start the entrypoint runs a temporary mongod --bind_ip 127.0.0.1 to create the user and run the scripts in /docker-entrypoint-initdb.d, so a ping to localhost passes before the real server is up. The hostname resolves to the container’s network address, where only the final mongod --auth --bind_ip_all listens. ping needs no credentials, even with --auth. $$ is a literal $ for Compose, so the shell runs $(hostname).

Gotchas

  • The root variables and the .js and .sh init scripts only act on an empty /data/db. Changing the password variable later changes nothing.
  • The root user lives in admin. A connection string that names another database needs ?authSource=admin, as in mongodb://mongoadmin:secret@mongo:27017/app?authSource=admin.
  • docker compose down -v deletes mongo-data.
  • mongo:latest is 9.0 today. Pin the series: MongoDB upgrades one major version at a time, with the feature compatibility version set in between.

Try it in the docker run to Compose Converter

compose.yaml

1 service · 1 thing left out

Left out, and why

  • -dCompose starts services in the background already, so this flag needs no key.

Runs in your browser. Nothing you enter is sent anywhere.