How to create a DB for MongoDB container on start up?
MongodbDockerDocker ComposeMongodb Problem Overview
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?
Mongodb Solutions
Solution 1 - Mongodb
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.
Solution 2 - Mongodb
The 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_USERNAME
MONGO_INITDB_ROOT_PASSWORD
Example
docker run -d \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=password \
mongod
or Dockerfile
FROM docker.io/mongo
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.
Solution 3 - Mongodb
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.
Solution 4 - Mongodb
Here'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.
Solution 5 - Mongodb
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.
Solution 6 - Mongodb
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:
Solution 7 - Mongodb
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 http://localhost:8111/:
Solution 8 - Mongodb
If 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...
https://docs.docker.com/engine/swarm/secrets/
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)
Solution 9 - Mongodb
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,
Solution 10 - Mongodb
My 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
# https://stackoverflow.com/a/53522699
# https://stackoverflow.com/a/37811764
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
Solution 11 - Mongodb
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.
Solution 12 - Mongodb
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
.js
or.sh
mongo 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.js
file. 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_1
mongodb://db_2_service_user:<password>@mongodb:27017/db_2
Solution 13 - Mongodb
This 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: