152

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?

ReynierPM
  • 17,594
  • 53
  • 193
  • 363
  • can you create a container based on mysql and set it up as you want and then use it? – Valentin Mar 20 '17 at 21:17
  • @Valentin of course I can but what is your point? – ReynierPM Mar 20 '17 at 22:43
  • My point is, that you can set up a default DB, admin user/password and possibly auth method in dockerfile using variables and then pass them in compose file – Valentin Mar 20 '17 at 22:53
  • Note: If using any of the solutions that use mongo-init script, please make sure you set restart: unless-stopped (or something besides no) for containers dependent on mongo. This is because these containers will fail to connect to mongo the first time while it initializes (even with depends_on flag). See this thread for details https://stackoverflow.com/questions/31746182/docker-compose-wait-for-container-x-before-starting-y/35170810#35170810 – java-addict301 Oct 05 '20 at 23:45

14 Answers14

185

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.

Philipp Jahoda
  • 50,880
  • 24
  • 180
  • 187
Paul Wasilewski
  • 9,762
  • 5
  • 45
  • 49
  • You have to replace all <> values in the snippet with your settings. – Paul Wasilewski Apr 21 '19 at 06:51
  • Yes, not full copypaste. – Vadim Filin Apr 21 '19 at 11:55
  • 9
    what is mongo-init.js:ro ? – Heril Muratovic May 31 '19 at 11:33
  • 19
    @HerilMuratovic, the `volumes` entry `./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro` is linking the local file `./mongo-init.js` to the container path `/docker-entrypoint-initdb.d/mongo-init.js` as read-only `ro`. You can find more details about how to use *volumes* here: https://docs.docker.com/storage/volumes/ – Paul Wasilewski May 31 '19 at 18:48
  • Thank you @PaulWasilewski! I didn't know that "ro" is flag for readonly. – Heril Muratovic Jun 01 '19 at 09:19
  • I am attempting to follow this solution but I am not able to get it to work. Mongo's debugging tool mentions: `[main] directory [/docker-entrypoint-initdb.d/mongo-init.js] doesn't have any *.js files`. You sure this works for *.js files? – Gustavo Silva Jun 01 '19 at 11:20
  • 3
    @GustavoSilva I am absolutely sure it’s working. It seems that your docker compose file and the js script are not in the same folder. Or are you working on windows? Then please adjust the `volumes` option and add the absolute path to the `mongo-init.js` file for example `c:\docker\mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro`. – Paul Wasilewski Jun 01 '19 at 13:33
  • Hum, wouldn't it work if compose file and the script are in different directory? I have obviously updated the paths. My volume section was `./path/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro` I am unsure whether this only works for this image tag. What do you say about that? – Gustavo Silva Jun 01 '19 at 13:36
  • @GustavoSilva it’s not image related. You need to make sure that the path entries in the volumes option are right. So if the js script is not in the same directory then you have to set the path accordingly. Also your debug message indicates that the js script was not linked. – Paul Wasilewski Jun 01 '19 at 13:40
  • @PaulWasilewski Ok, thank you for your help. I believe in you and there has to be an issue with the paths then. Obrigado – Gustavo Silva Jun 01 '19 at 13:42
  • @GustavoSilva and one more point, make sure that the file path can be shared with a docker container. Docker File Sharing Settings. – Paul Wasilewski Jun 01 '19 at 13:49
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/194303/discussion-between-gustavo-silva-and-paul-wasilewski). – Gustavo Silva Jun 01 '19 at 21:37
  • @PaulWasilewski I've tried this solution but my Javascript file and directory structure is a little bit more complicated. If you have time, would you be willing to check out the question I've posted here: https://stackoverflow.com/questions/57209269/i-want-to-create-a-mongodb-database-and-populate-it-with-a-number-of-collections – pickle Jul 25 '19 at 20:34
  • 2
    What is db? The code seems incomplete to me, is that the mongoose connection? – andrevenancio Oct 17 '19 at 09:07
  • 2
    The example is working and complete. `db` is the currently connected database. The `mongo-init.js` script is executed in a mongo shell environment once the container is created. – Paul Wasilewski Oct 17 '19 at 20:21
  • How to create multiple databases and users this way ? – Aditya Mar 01 '20 at 01:36
  • 1
    In order for db.CreateUser to work, I had to first add items to the database. – Lazor Nov 18 '20 at 16:58
  • OMFG @Lazor you're right! The same thing happened to me. Maddening! – sheac Mar 13 '21 at 20:07
  • FYI: `MongoDB only creates the database when you first store data in that database. This data could be a collection or even a document.` from this source https://www.mongodb.com/basics/create-database . – phrogg Sep 22 '21 at 12:44
  • @phrogg @Lazor You don't have to store data first, but You have to select the database first like so `db = db.getSiblingDB(_getEnv("MONGO_DATABASE"));` Then after selecting it, you can create your user(s). – Max O. Jul 27 '22 at 08:05
144

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.

Matt
  • 68,711
  • 7
  • 155
  • 158
  • This is excellent, I suppose `set -e` is telling the `entrypoint.sh` to read variables from `ENV` definition so I can use the same approach as the example from MySQL, I am right? As an addition to your answer I did found [this PR](https://github.com/docker-library/mongo/pull/145) where apparently someone is working in something similar to your proposal just coming from another point of view – ReynierPM Mar 21 '17 at 11:27
  • The environment variables you get by default. `set -e` causes the whole script to exit when a command fails, so the script can't silently fail and startup mongo. – Matt Mar 21 '17 at 21:04
  • Similar to all the Dockerfile build scripts you see that use `&&` everywhere. – Matt Mar 21 '17 at 21:04
  • 1
    That mongo PR is a much more thorough implementation! and the `docker-entrypoint-initdb.d` directory makes it extensible. I hope that get's merged – Matt Mar 21 '17 at 21:25
  • 1
    It's merged (see [here](https://github.com/docker-library/mongo/pull/145)) – ReynierPM Mar 21 '17 at 23:46
  • I am having issues initializing an empty DB using `MONGO_INITDB_DATABASE` since I think a script is needed. Could you add a small example for such script for a database named container? – ReynierPM May 06 '17 at 14:44
  • @ReynierPM added an example init – Matt May 06 '17 at 22:04
  • how the custom script will work with docker compose? – jzqa Sep 28 '17 at 06:23
  • @jzqa You can set the [environment variables](https://docs.docker.com/compose/environment-variables/) in compose. Possibly mount the init files from a [volume](https://docs.docker.com/compose/compose-file/#volumes), but it's probably better to build a `Dockerfile` for that. – Matt Sep 28 '17 at 10:33
  • @Matt how would you populate collections other than users? For instance a table in mongo named 'settings'? – boydenhartog Jun 15 '20 at 10:53
  • @boydenhartog With the javascript files in the `docker-entrypoint-initdb.d` directory – Matt Jun 16 '20 at 02:22
  • @Matt yea I figured that much but so far I have only come across examples where users are created. There's hardly any documentation on this functionality. For instance in the example above where the db object is coming from and what methods it has. – boydenhartog Jun 16 '20 at 11:20
  • @boydenhartog oh right... that's the mongo javascripty [shell environment](https://docs.mongodb.com/manual/reference/method/), so scripts run in what you use as the `mongo` command via `mongo script.js`. I've added that to the answer – Matt Jun 17 '20 at 02:17
  • @Matt thats exactly the info that I was missing. Thanks!! – boydenhartog Jun 17 '20 at 08:51
  • when there is nothing populated in the /data/db directory. this part is really saved my life. – Efe AYDIN May 14 '21 at 12:13
89

No need to create a database in advance (they're created on the fly), but it makes sense to create a non-root user the first time the container starts.

docker-compose.yml:

services:
  ...
  mongo:
    image: mongo:6-jammy
    env_file: .env-mongo
    volumes:
      - mongo:/data/db
      - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js
      # - ./init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh  # mongo < 6

volumes:
  mongo:

init-mongo.sh doesn't need to be executable. It's sourced.

.env (app's variables):

MONGO_USER=user
MONGO_PASSWORD=userpasswd
MONGO_DB=foo

.env-mongo (mongo service's variables):

MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=rootpasswd
MONGO_INITDB_DATABASE=foo  # for mongo >= 6

Since mongo-6 one can use process.env, so use init-mongo.js. Before that, use init-mongo.sh (change the script that you attach in docker-compose.yml, and MONGO_INITDB_DATABASE is not needed in this case).

.env is meant to be used by the app service(s), .env-mongo by the mongo service.

MONGO_INITDB_* variables are used by the mongo's entrypoint. The docs can be found on Docker Hub. Also it's the entrypoint that runs the initialization script.

init-mongo.js:

db.getSiblingDB('admin').auth(
    process.env.MONGO_INITDB_ROOT_USERNAME,
    process.env.MONGO_INITDB_ROOT_PASSWORD
);
db.createUser({
    user: process.env.MONGO_USER,
    pwd: process.env.MONGO_PASSWORD,
    roles: ["readWrite"],
});

init-mongo.sh:

q_MONGO_USER=`jq --arg v "$MONGO_USER" -n '$v'`
q_MONGO_PASSWORD=`jq --arg v "$MONGO_PASSWORD" -n '$v'`
mongo -u "$MONGO_INITDB_ROOT_USERNAME" -p "$MONGO_INITDB_ROOT_PASSWORD" admin <<EOF
    use foo;
    db.createUser({
        user: $q_MONGO_USER,
        pwd: $q_MONGO_PASSWORD,
        roles: ["readWrite"],
    });
EOF

More files in a gist.

x-yuri
  • 16,722
  • 15
  • 114
  • 161
  • 29
    I just spent 3hrs around searching for how to create my own db initially. Crazy that documentation does not mention that you need to authenticate within .js or .sh files. Examples over the internet are not complete at all. You literally need to dig into their .sh script to conclude how to write this stuff. How to "create empty database" when container starts. PR should be created for this to be simplified. A little bit crazy if you ask me. – cool Dec 28 '19 at 12:23
  • 2
    I spent hours looking for a solution to this problem. This just saved me. Thank you. – NaijaProgrammer Mar 20 '20 at 04:13
  • 1
    `$(cat "$MONGO_INITDB_ROOT_PASSWORD_FILE")` should be replaced with `$MONGO_INITDB_ROOT_PASSWORD` these days because the Mongo image already handles the secret for us. https://hub.docker.com/_/mongo – FlippingBinary Jul 20 '20 at 21:35
  • It was so in [3.2](https://github.com/docker-library/mongo/blob/b808c630a16f3ad1024a0eefeda57ab2cc83a35c/3.2/docker-entrypoint.sh#L182) as well. I probably failed to notice. – x-yuri Jul 21 '20 at 13:03
  • @x-yuri What's the benefit of using configs for init-mongo.sh, rather than just copying the file to docker-entrypoint-initdb.d and letting it run there automatically? – Lazor Nov 18 '20 at 17:03
  • @Lazor The upside of configs is that you don't have to store the file on the host filesystem. And by copying you probably mean building a custom image with the file being at the mentioned place. The downside of this approach apparently is that you need a custom image, whether it matters for you or not. – x-yuri Nov 20 '20 at 10:15
  • This works like a charm, thanks for sharing! – Sébastien Loix Dec 16 '20 at 13:27
  • This is the solution that worked for me, thanks! – Mike Jan 30 '21 at 20:58
  • Starting from Mongodb version 6.0 the command mongo was replaced by mongosh – JustRandom Jan 23 '23 at 18:45
  • 1
    @JustRandom You can check out the updated answer. – x-yuri Feb 27 '23 at 07:41
67

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.

Mateusz Stefek
  • 3,478
  • 2
  • 23
  • 28
  • 1
    Is there a typo in the roles? db should be test-database? – Winster Mar 27 '20 at 09:51
  • Two comments on the second line that has getSiblingDB. First, everything worked in my test without this line. Second, it seems like bad form to overwrite the db variable like this, since it is considered a global object and overwriting it changes the context for everything. – Lazor Nov 18 '20 at 17:00
15

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.

ice_chrysler
  • 2,633
  • 1
  • 21
  • 27
  • 4
    `mongo --username admin --password password --host localhost --port 27017` i am not able to connect as the logs show the users and db is created successfully. ERROR: `$ mongo --username admin --password password --host localhost --port 27017 MongoDB shell version v3.4.10 connecting to: mongodb://localhost:27017/ MongoDB server version: 3.6.5 WARNING: shell and server versions do not match 2018-06-07T13:05:09.022+0000 E QUERY [thread1] Error: Authentication failed. : DB.prototype._authOrThrow@src/mongo/shell/db.js:1461:20 @(auth):6:1 @(auth):1:2 exception: login failed` – Tara Prasad Gurung Jun 07 '18 at 13:08
  • 1
    you need to `mongo admin --username ADMIN --password PASSWORT` to connect to your mongo instance. – Philipp Schemel Jun 29 '18 at 08:45
  • 1
    Can't get this to work. "Could not find user admin@admin". It doesn't seem to automatically create the user? – batjko Feb 04 '19 at 09:59
  • ```mongodb://root:example@localhost/my-db?authSource=admin``` – FDisk Mar 17 '19 at 21:34
  • 2
    @TaraPrasadGurung I had this problem, noticed that i already had a volume created for database, you have to delete this volume for the container to create a new user. – Daniel S. Apr 18 '19 at 02:30
  • Was having the same issue, but @DanielS. suggestion of deleting the volume fixed the issue. Thanks. – Geoherna Nov 12 '21 at 00:08
14

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/:

enter image description here

vladimir
  • 13,428
  • 2
  • 44
  • 70
  • 2
    Just wondering, what is the goal of exposing 9229? According to the reference e.g. https://docs.mongodb.com/manual/reference/default-mongodb-port/ mongod may be sitting on 27017,27018,27019 by default. – nakhodkin Jul 22 '21 at 20:25
10

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 # change this
      MONGO_INITDB_ROOT_PASSWORD: admin-password # change this
      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:
Alf Eaton
  • 5,226
  • 4
  • 45
  • 50
  • Any reason you use sh when all you do is just eval a js line? Why not use a js file? – Max O. Jul 27 '22 at 08:09
  • @MaxO. It might be possible to do the same thing with a `mongo-init.js` file - can it read the environment variables from `process.env`? – Alf Eaton Jul 27 '22 at 12:51
  • That's possible with `_getEnv()` (version >= 4.7.0) and I believe it's also possible with just using the $ – Max O. Aug 21 '22 at 09:22
10

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:
Patrik H.
  • 121
  • 1
  • 3
9

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 code 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 for docker-compose.yml examples, 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,

DazBaldwin
  • 4,125
  • 3
  • 39
  • 43
LaTouwne
  • 144
  • 1
  • 5
6

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)

englishPete
  • 809
  • 10
  • 15
3

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

2

Steps to create multiple databases and users in MongoDB docker container without root access.

  1. Stop docker-compose with
    docker-compose down
    
    command, or 'docker-compose -f docker-compose.yml -f docker-compose.dev.yml <...>' if you have multiple files.
  2. Remove volumes assigned to the mongo container. Run 'docker volume ls'. If there is anything similar to <projectfolder>_mongodb--vol, remove it by applying
    docker volume rm <projectfolder>_mongodb--vol
    
  3. Set up folder with .js or .sh mongo database init scripts. For example, mongo-init/db_1.js:
    conn = new Mongo();
    db = conn.getDB("db_1")
    db.createUser(
    {
        user: "db_1_service_user",
        pwd: "<password>",
        roles: [
            {
                role: "readWrite",
                db: "db_1"
            }
        ]
    }
    );
    
    Repeat the procedure for mongo-init/db_2.js file. conn.getDB() creates the database, db.createUser() creates a user and assigns it to the database.
  4. 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:
    
  5. 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
    
Artur A
  • 7,115
  • 57
  • 60
2

docker-compose.yml

version: '3.9'

services:
  mongodb:
    image: mongo
    container_name: chat_history
    restart: always
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
      MONGO_INITDB_DATABASE: ${MONGO_INITDB_DATABASE}
    ports:
      - "${MONGO_INITDB_PORT}:27017"
    env_file:
      - .env
    volumes:
      - ./data/mongo:/data/db
      - ./scripts/docker/mongo/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro

mongo-init.js

db = db.getSiblingDB('admin');
db.auth(
    process.env.MONGO_INITDB_ROOT_USERNAME,
    process.env.MONGO_INITDB_ROOT_PASSWORD,
);

db = db.getSiblingDB(process.env.MONGO_INITDB_DATABASE);
db.createUser({
    user: process.env.MONGO_USER,
    pwd: process.env.MONGO_PASSWORD,
    roles: [{
        role: 'readWrite',
        db: process.env.MONGO_INITDB_DATABASE,
    }]
});

db.createCollection(process.env.MAIN_DB_COLLECTION);

First I've authorized the 'admin' user, then I've created a new table so that I could connect to it directly from the app (Go).

zforgo
  • 2,508
  • 2
  • 14
  • 22
1

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.

user1545858
  • 725
  • 4
  • 21