I am working with Docker and I have a stack with PHP, MySQL, Apache and Redis. I need to add MongoDB now so I was checking the Dockerfile for the latest version and also the docker-entrypoint.sh file from the MongoDB Dockerhub but I couldn't find a way to setup a default DB, admin user/password and possibly auth method for the container from a docker-compose.yml file.
In MySQL you can setup some ENV variables as for example:
db: image: mysql:5.7 env_file: .env environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} And this will setup the DB and the user/password as the root password.
Is there any way to achieve the same with MongoDB? Anyone has some experience or workaround?
413 Answers
Here another cleaner solution by using docker-compose and a js script.
This example assumes that both files (docker-compose.yml and mongo-init.js) lay in the same folder.
docker-compose.yml
version: '3.7' services: mongodb: image: mongo:latest container_name: mongodb restart: always environment: MONGO_INITDB_ROOT_USERNAME: <admin-user> MONGO_INITDB_ROOT_PASSWORD: <admin-password> MONGO_INITDB_DATABASE: <database to create> ports: - 27017:27017 volumes: - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro mongo-init.js
db.createUser( { user: "<user for database which shall be created>", pwd: "<password of user>", roles: [ { role: "readWrite", db: "<database to create>" } ] } ); Then simply start the service by running the following docker-compose command
docker-compose up --build -d mongodb Note: The code in the docker-entrypoint-init.d folder is only executed if the database has never been initialized before.
19The docker hub mongo image will run any scripts in /docker-entrypoint-initdb.d/ when there is nothing populated in the /data/db directory.
Database Initialisation
The mongo container image provides the /docker-entrypoint-initdb.d/ path to deploy custom .js or .sh setup scripts that will be run once on database initialisation. .js scripts will be run against test by default or MONGO_INITDB_DATABASE if defined in the environment.
COPY mysetup.sh /docker-entrypoint-initdb.d/ or
COPY mysetup.js /docker-entrypoint-initdb.d/ A simple initialisation mongo shell javascript file that demonstrates setting up the container collection with data, logging and how to exit with an error (for result checking).
let error = true let res = [ db.container.drop(), db.container.createIndex({ myfield: 1 }, { unique: true }), db.container.createIndex({ thatfield: 1 }), db.container.createIndex({ thatfield: 1 }), db.container.insert({ myfield: 'hello', thatfield: 'testing' }), db.container.insert({ myfield: 'hello2', thatfield: 'testing' }), db.container.insert({ myfield: 'hello3', thatfield: 'testing' }), db.container.insert({ myfield: 'hello3', thatfield: 'testing' }) ] printjson(res) if (error) { print('Error, exiting') quit(1) } Admin User Setup
The environment variables to control "root" user setup are
MONGO_INITDB_ROOT_USERNAMEMONGO_INITDB_ROOT_PASSWORD
Example
docker run -d \ -e MONGO_INITDB_ROOT_USERNAME=admin \ -e MONGO_INITDB_ROOT_PASSWORD=password \ mongod or Dockerfile
FROM ENV MONGO_INITDB_ROOT_USERNAME admin ENV MONGO_INITDB_ROOT_PASSWORD password You don't need to use --auth on the command line as the docker entrypoint.sh script adds this in when it detects the environment variables exist.
UPD Today I avoid Docker Swarm, secrets, and configs. I'd run it with docker-compose and the .env file. As long as I don't need autoscaling. If I do, I'd probably choose k8s. And database passwords, root account or not... Do they really matter when you're running a single database in a container not connected to the outside world?.. I'd like to know what you think about it, but Stack Overflow is probably not well suited for this sort of communication.
Mongo image can be affected by MONGO_INITDB_DATABASE variable, but it won't create the database. This variable determines current database when running /docker-entrypoint-initdb.d/* scripts. Since you can't use environment variables in scripts executed by Mongo, I went with a shell script:
docker-swarm.yml:
version: '3.1' secrets: mongo-root-passwd: file: mongo-root-passwd mongo-user-passwd: file: mongo-user-passwd services: mongo: image: mongo:3.2 environment: MONGO_INITDB_ROOT_USERNAME: $MONGO_ROOT_USER MONGO_INITDB_ROOT_PASSWORD_FILE: /run/secrets/mongo-root-passwd MONGO_INITDB_USERNAME: $MONGO_USER MONGO_INITDB_PASSWORD_FILE: /run/secrets/mongo-user-passwd MONGO_INITDB_DATABASE: $MONGO_DB volumes: - ./init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh secrets: - mongo-root-passwd - mongo-user-passwd init-mongo.sh:
mongo -- "$MONGO_INITDB_DATABASE" <<EOF var rootUser = '$MONGO_INITDB_ROOT_USERNAME'; var rootPassword = '$MONGO_INITDB_ROOT_PASSWORD'; var admin = db.getSiblingDB('admin'); admin.auth(rootUser, rootPassword); var user = '$MONGO_INITDB_USERNAME'; var passwd = '$(cat "$MONGO_INITDB_PASSWORD_FILE")'; db.createUser({user: user, pwd: passwd, roles: ["readWrite"]}); EOF Alternatively, you can store init-mongo.sh in configs (docker config create) and mount it with:
configs: init-mongo.sh: external: true ... services: mongo: ... configs: - source: init-mongo.sh target: /docker-entrypoint-initdb.d/init-mongo.sh And secrets can be not stored in a file.
A couple of gists on the matter.
8Here's a working solution that creates admin-user user with a password, additional database (test-database), and test-user in that database.
Dockerfile:
FROM mongo:4.0.3 ENV MONGO_INITDB_ROOT_USERNAME admin-user ENV MONGO_INITDB_ROOT_PASSWORD admin-password ENV MONGO_INITDB_DATABASE admin ADD mongo-init.js /docker-entrypoint-initdb.d/ mongo-init.js:
db.auth('admin-user', 'admin-password') db = db.getSiblingDB('test-database') db.createUser({ user: 'test-user', pwd: 'test-password', roles: [ { role: 'root', db: 'test-database', }, ], }); The tricky part was to understand that *.js files were run unauthenticated. The solution authenticates the script as the admin-user in the admin database. MONGO_INITDB_DATABASE admin is essential, otherwise the script would be executed against the test db. Check the source code of docker-entrypoint.sh.
In case someone is looking for how to configure MongoDB with authentication using docker-compose, here is a sample configuration using environment variables:
version: "3.3" services: db: image: mongo environment: - MONGO_INITDB_ROOT_USERNAME=admin - MONGO_INITDB_ROOT_PASSWORD=<YOUR_PASSWORD> ports: - "27017:27017" When running docker-compose up your mongo instance is run automatically with auth enabled. You will have a admin database with the given password.
Given this .env file:
DB_NAME=foo DB_USER=bar DB_PASSWORD=baz And this mongo-init.sh file:
mongo --eval "db.auth('$MONGO_INITDB_ROOT_USERNAME', '$MONGO_INITDB_ROOT_PASSWORD'); db = db.getSiblingDB('$DB_NAME'); db.createUser({ user: '$DB_USER', pwd: '$DB_PASSWORD', roles: [{ role: 'readWrite', db: '$DB_NAME' }] });" This docker-compose.yml will create the admin database and admin user, authenticate as the admin user, then create the real database and add the real user:
version: '3' services: # app: # build: . # env_file: .env # environment: # DB_HOST: 'mongodb://mongodb' mongodb: image: mongo:4 environment: MONGO_INITDB_ROOT_USERNAME: admin-user MONGO_INITDB_ROOT_PASSWORD: admin-password DB_NAME: $DB_NAME DB_USER: $DB_USER DB_PASSWORD: $DB_PASSWORD ports: - 27017:27017 volumes: - db-data:/data/db - ./mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh volumes: db-data: This works for me:
docker-compose.yaml
version: "3.8" services: mongodb: image: mongo:3.6 restart: always environment: - MONGO_INITDB_ROOT_USERNAME=root - MONGO_INITDB_ROOT_PASSWORD=hello volumes: - ./mongo-init/:/docker-entrypoint-initdb.d/:ro ports: - 27017:27017 - 9229:9229 mongo-express: image: mongo-express restart: always ports: - 8111:8081 environment: - ME_CONFIG_MONGODB_SERVER=mongodb - ME_CONFIG_MONGODB_ADMINUSERNAME=root - ME_CONFIG_MONGODB_ADMINPASSWORD=hello ./mongo-init/init.js
conn = new Mongo(); db = conn.getDB("MyDatabaseName"); db.myCollectionName.createIndex({ "address.zip": 1 }, { unique: false }); db.myCollectionName.insert({ "address": { "city": "Paris", "zip": "123" }, "name": "Mike", "phone": "1234" }); db.myCollectionName.insert({ "address": { "city": "Marsel", "zip": "321" }, "name": "Helga", "phone": "4321" }); Look at the dashboard by
1If you are looking to remove usernames and passwords from your docker-compose.yml you can use Docker Secrets, here is how I have approached it.
version: '3.6' services: db: image: mongo:3 container_name: mycontainer secrets: - MONGO_INITDB_ROOT_USERNAME - MONGO_INITDB_ROOT_PASSWORD environment: - MONGO_INITDB_ROOT_USERNAME_FILE=/var/run/secrets/MONGO_INITDB_ROOT_USERNAME - MONGO_INITDB_ROOT_PASSWORD_FILE=/var/run/secrets/MONGO_INITDB_ROOT_PASSWORD secrets: MONGO_INITDB_ROOT_USERNAME: file: secrets/${NODE_ENV}_mongo_root_username.txt MONGO_INITDB_ROOT_PASSWORD: file: secrets/${NODE_ENV}_mongo_root_password.txt I have use the file: option for my secrets however, you can also use external: and use the secrets in a swarm.
The secrets are available to any script in the container at /var/run/secrets
The Docker documentation has this to say about storing sensitive data...
You can use secrets to manage any sensitive data which a container needs at runtime but you don’t want to store in the image or in source control, such as:
Usernames and passwords TLS certificates and keys SSH keys Other important data such as the name of a database or internal server Generic strings or binary content (up to 500 kb in size)
Just a quick add-on to a couple of answers here - that are all great, it indeed saved me a lot of time figuring things out ! I would add a bit of cod if the user you want to create needs to be attached to a specific database
The main thing is that you need to be logged on your admin base to create new users. So with all the docker-compose.yml presented on the feed, your mongo-init.js file would look like :
db = db.getSiblingDB('admin'); // move to the admin db - always created in Mongo db.auth("rootUser", "rootPassword"); // log as root admin if you decided to authenticate in your docker-compose file... db = db.getSiblingDB('DB_test'); // create and move to your new database db.createUser({ 'user': "dbUser", 'pwd': "dbPwd", 'roles': [{ 'role': 'dbOwner', 'db': 'DB_test'}]}); // user created db.createCollection('collection_test'); // add new collection If that can help someone, I'm happy - cheers,
1This is how I do it using env variables and secrets.
Following will create "app_user" and "app_database" on mongo container startup (only if used database stored in /data/db is empty).
Dockerfile
FROM mongo:5.0.3 COPY images/mongo/init.js /docker-entrypoint-initdb.d/ init.js
db.log.insertOne({"message": "Database created."}); db.createUser( { user: _getEnv("MONGO_USER"), pwd: cat(_getEnv("MONGO_PASSWORD_FILE")), roles: [ "readWrite", "dbAdmin" ] } ); - Script will use MONGO_INITDB_DATABASE (env variable defined in docker-compose.yml) for database name.
- Scripts in /docker-entrypoint-initdb.d/ will only be run if used database stored in /data/db is empty.
docker-compose.yml
mongo: build: context: . dockerfile: ./images/mongo/Dockerfile container_name: app_mongo environment: MONGO_INITDB_DATABASE: "app_database" MONGO_INITDB_ROOT_PASSWORD_FILE: /run/secrets/app_mongo_root_password MONGO_INITDB_ROOT_USERNAME: "root" MONGO_PASSWORD_FILE: /run/secrets/app_mongo_password MONGO_USER: "app_user" secrets: - app_mongo_password - app_mongo_root_password volumes: - mongo:/data/db secrets: app_mongo_password: file: ./secrets/app_mongo_password.txt app_mongo_root_password: file: ./secrets/app_mongo_root_password.txt volumes: mongo: 2My answer is based on the one provided by @x-yuri; but my scenario it's a little bit different. I wanted an image containing the script, not bind without needing to bind-mount it.
mongo-init.sh -- don't know whether or not is need but but I ran chmod +x mongo-init.sh also:
#!/bin/bash # # mongo -- "$MONGO_INITDB_DATABASE" <<EOF var rootUser = '$MONGO_INITDB_ROOT_USERNAME'; var rootPassword = '$MONGO_INITDB_ROOT_PASSWORD'; var user = '$MONGO_INITDB_USERNAME'; var passwd = '$MONGO_INITDB_PASSWORD'; var admin = db.getSiblingDB('admin'); admin.auth(rootUser, rootPassword); db.createUser({ user: user, pwd: passwd, roles: [ { role: "root", db: "admin" } ] }); EOF Dockerfile:
FROM mongo:3.6 COPY mongo-init.sh /docker-entrypoint-initdb.d/mongo-init.sh CMD [ "/docker-entrypoint-initdb.d/mongo-init.sh" ] docker-compose.yml:
version: '3' services: mongodb: build: . container_name: mongodb-test environment: - MONGO_INITDB_ROOT_USERNAME=root - MONGO_INITDB_ROOT_PASSWORD=example - MONGO_INITDB_USERNAME=myproject - MONGO_INITDB_PASSWORD=myproject - MONGO_INITDB_DATABASE=myproject myproject: image: myuser/myimage restart: on-failure container_name: myproject environment: - DB_URI=mongodb - DB_HOST=mongodb-test - DB_NAME=myproject - DB_USERNAME=myproject - DB_PASSWORD=myproject - DB_OPTIONS= - DB_PORT=27017 ports: - "80:80" After that, I went ahead and publish this Dockefile as an image to use in other projects.
note: without adding the CMD it mongo throws: unbound variable error
If you are using docker-compose and are trying to run any of these suggestions after you have created a volume, you may need to delete the volume because the init script will not run if the volume already there.
To see if this is the case, try running:-
docker volume ls
If your mongo data is there, you should see it in a volume names something like:-
<db>_<volume>
If the volume is something like db_volume, you will need to run:-
docker volume rm db_volume You may need to trash your container for this to work.
Steps to create multiple databases and users in MongoDB docker container without root access.
- Stop docker-compose with
command, ordocker-compose down'docker-compose -f docker-compose.yml -f docker-compose.dev.yml <...>'if you have multiple files. - Remove volumes assigned to the mongo container. Run
'docker volume ls'. If there is anything similar to<projectfolder>_mongodb--vol, remove it by applyingdocker volume rm <projectfolder>_mongodb--vol - Set up folder with
.jsor.shmongo database init scripts. For example,mongo-init/db_1.js:
Repeat the procedure forconn = new Mongo(); db = conn.getDB("db_1") db.createUser( { user: "db_1_service_user", pwd: "<password>", roles: [ { role: "readWrite", db: "db_1" } ] } );mongo-init/db_2.jsfile. conn.getDB() creates the database, db.createUser() creates a user and assigns it to the database. - docker-compose.yml fragment, notice the mount binding from
./mongo-init/local folder containing database init scripts to the remote/docker-entrypoint-initdb.d/:version: "3.8" services: my-service_1: depends_on: - mongodb my-service_2: depends_on: - mongodb mongodb: image: mongo:4.4-bionic ports: - "27017:27017" volumes: - mongodb--vol:/data/db - ./mongo-init/:/docker-entrypoint-initdb.d/:ro healthcheck: test: "echo 'db.runCommand(\"ping\").ok'" interval: 5s timeout: 5s retries: 3 # in-service volumes are very slow in MacOS and Windows, # so using fast shared volumes for large storage volumes: mongodb--vol: - Apply connection strings to your applications:
mongodb://db_1_service_user:<password>@mongodb:27017/db_1mongodb://db_2_service_user:<password>@mongodb:27017/db_2
