jOHNdOE
jOHNdOE

Reputation: 525

How to create user in mongodb with docker-compose

I'm trying to create some kind of script that will create a docker with mongodb and automatically create a user.

I can usually manage my docker images with docker-compose but this time, I don't know how to do it.

Basically, here is what I have to do:

  1. clean/destroy container (docker-compose down)
  2. create a docker container with mongodb and start it (without --auth parameter)
  3. execute a java script containing db.createUser()
  4. stop the container
  5. restart the same container with --auth parameter to allow login with the user created in the javascript

I can't find how to do that properly with docker-compose because when it starts, I have to give it the command --auth. If I do that, I cannot execute my javascript to add my user. MongoDB allows users creation without being logged in if there is no user and if --auth parameter is not provided.

I want to do that automatically, I do not want to manually do some commands. The goal is to have a script that can be executed before each integration tests to start from a clean database.

Here is my project:

integration-test/src/test/resources/scripts/docker-compose.yml

mongodb:
    container_name: mongo
    image: mongo
  ports:
    - "27017:27017"
  volumes:
    - .:/setup
  command: --auth

integration-test/src/test/resources/scripts/docker-init.sh

docker-compose down
docker-compose up -d
sleep 1
docker exec mongo bash -c "mongo myDatabase /setup/mongodb-setup.js"

integration-test/src/test/resources/scripts/mongodb-setup.js

db.createUser(
{
    user: "myUser",
    pwd: "myPassword",
    roles: [
      { role: "readWrite", db: "myDatabase" }
    ]
})

Finding a way to start again a container with a new parameter (in this case --auth) would help but I can't find how to do that (docker start does not take parameters).

Any idea how I should do what I would like ?

If not, I can still delete everything from my database with some Java code or something else but I would like a complete mongodb docker setup created with a script.

Upvotes: 50

Views: 122709

Answers (11)

Hazhir
Hazhir

Reputation: 51

You can use side container to initialize all the users and database that you need with a help of a JavaScript file. Your docker-compose.yml file should look like:

version: '3.1'  
services:  
  mongo:
    image: mongo:7.0.5
    container_name: mongo
    restart: always
    ports:
      - 27017:27017
    environment:
      MONGO_INITDB_ROOT_USERNAME: test
      MONGO_INITDB_ROOT_PASSWORD: test
    volumes:
      - ./mongo/data:/data/db
      - ./mongo/scripts/initdb.js:/initdb.js

  mongo-initdb:
    image: mongo:7.0.5
    container_name: mongo-initdb
    command: mongosh --host mongo admin /initdb.js -u test -p test --authenticationDatabase admin
    volumes:
      - ./mongo/scripts/initdb.js:/initdb.js
    depends_on:
      - mongo

make sure your javascript is placed at "./mongo/scripts/initdb.js". The content of the js file is as follow:

// Function to check if a user already exists
function doesUserExist(username, database) {
    var users = database.getUsers();
    for (var i = 0; i < users.length; i++) {
        if (users[i].user === username) {
            return true;
        }
    }
    return false;
}

// User configuration array
var usersToCreate = [
    { username: "test1", pwd: "test1", dbName: "test1", role: "readWrite" },
    { username: "test2", pwd: "test2", dbName: "test2", role: "readWrite" },
    // Add more users as needed
];

// Connect to the admin database
var adminDb = db.getSiblingDB('admin');

// Loop through the users and create them if they don't exist
usersToCreate.forEach(function(user) {
    if (!doesUserExist(user.username, adminDb)) {
        // Create user if not exists
        adminDb.createUser({
            user: user.username,
            pwd: user.pwd,  // Use a secure password
            roles: [{ role: user.role, db: user.dbName }]
        });
        print('User ' + user.username + ' created');
    } else {
        print('User ' + user.username + ' already exists');
    }

    // Connect to the target database and create a collection
    var userDb = db.getSiblingDB(user.dbName);
    userDb.createCollection('deleteMe_' + user.username);
});

print('User creation script executed');

This way you can create multiple User, Database and Collection if you need.

Upvotes: 1

Alf
Alf

Reputation: 71

@greenThumb's answer is good and I have used his approach but due to using .env files

I have suffered with the variable interpolation but this example works great

Hope this helps anyone trying to use an initializer container to deploy MongoDB in docker-engien, swarm or even K8s (Really?)

Tested this with Mongo 4.4 but mongosh is compatible with mongo 4.2 onwards so it should work

Dockerfile

FROM ubuntu:22.04

RUN apt-get update && apt-get upgrade -y && apt-get install -y gnupg wget
RUN wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | apt-key add -
RUN echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-6.0.list
RUN apt-get update && apt-get install -y mongodb-mongosh
RUN rm -rf /var/lib/apt/lists/*

RUN useradd -ms /bin/bash ubuntu

WORKDIR /app

COPY createUser.sh /app/createUser.sh

RUN chmod -R +x /app/createUser.sh

RUN chown ubuntu:ubuntu /app/createUser.sh

USER ubuntu

CMD ["/app/createUser.sh"]

createUser.sh script:

#!/bin/sh

# Set default values for environment variables if not defined
: ${MONGO_INITDB_ROOT_USERNAME:=root}
: ${MONGO_INITDB_ROOT_PASSWORD:=<root\'s_password>}
: ${MONGO_DB_HOST:=<db_host>}
: ${MONGO_DB_NAME:=<db_name>}
: ${MONGO_DB_USERNAME:=<username>}
: ${MONGO_DB_PASSWORD:=<password>}

sleep 10

# Wait for MongoDB to be ready
until mongosh --eval "print(\"waited for connection\")" --host mongo-db --username $MONGO_INITDB_ROOT_USERNAME --password $MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin
do
    echo "MongoDB is not yet available. Retrying in 5 seconds..."
    sleep 5
done

# Check if the database exists
db_exists=$(mongosh --quiet --host mongo-db -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "db.getSiblingDB('$MONGO_DB_NAME')" | grep -c "ok")

if [ "$db_exists" -eq "1" ]; then
    echo "Database $MONGO_DB_NAME doesn't exist. Creating..."

    # Create the database
    mongosh --quiet --host mongo-db -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "db.getSiblingDB('$MONGO_DB_NAME').runCommand({ create: '$MONGO_DB_NAME' })"
    create_db_status=$?

    if [ $create_db_status -eq 0 ]; then
        echo "Database $MONGO_DB_NAME created successfully."
    else
        echo "Database $MONGO_DB_NAME creation failed."
        exit 1
    fi
else
    echo "Database $MONGO_DB_NAME already exists. Skipping creation."
fi

# Check if the user exists
user_exists_output=$(mongosh --quiet --host mongo-db -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "db.getSiblingDB('$MONGO_DB_NAME').getUser('$MONGO_DB_USERNAME')")

if [ "$user_exists_output" = "null" ]; then
    echo "User $MONGO_DB_USERNAME doesn't exist in database $MONGO_DB_NAME. Creating..."

    # Execute the MongoDB commands to create the user using mongosh
    mongo_commands=$(cat <<EOF
    db.getSiblingDB('$MONGO_DB_NAME').createUser({
      user: "$MONGO_DB_USERNAME",
      pwd: "$MONGO_DB_PASSWORD",
      roles: [{ role: "readWrite", db: "$MONGO_DB_NAME" }]
    });
EOF
)

    # Execute the MongoDB commands to create the user
    echo "$mongo_commands" | mongosh --quiet --host mongo-db -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin
    create_user_status=$?

    if [ $create_user_status -eq 0 ]; then
        echo "User $MONGO_DB_USERNAME created successfully in database $MONGO_DB_NAME."
    else
        echo "User $MONGO_DB_USERNAME creation in database $MONGO_DB_NAME failed."
        exit 1
    fi
else
    if [ "$user_exists_output" = "1" ]; then
        echo "Error checking user existence. Exiting with an error code."
        exit 1
    else
        echo "User $MONGO_DB_USERNAME already exists in database $MONGO_DB_NAME. Skipping creation."
    fi
fi

docker-compose:

version: '3.9'

services:
  mongo-db:
    image: ${MONGO_DB_IMAGE:-}
    container_name: mongo-db
    restart: unless-stopped
    stop_grace_period: 30s
    env_file:
      - ./envfiles/.mongo-db.env
      - ./envfiles/.mongo-db-root.env
    volumes:
      - mongo-db-data:/data/db
    healthcheck:
      test: ["CMD", "mongo", "--eval", "db.adminCommand('ping')"]
      interval: 60s
      timeout: 10s
      retries: 6
      start_period: 60s
    logging:
      driver: json-file
      options:
        max-size: 200m
        max-file: "3"
        mode: non-blocking
    networks:
      - mongo_network
    ports:
      - 0.0.0.0:27017:27017

  mongo-initializer:
    image: ${MONGO_INITIALIZER_IMAGE:-}
    container_name: mongo-initializer
    env_file:
      - ./envfiles/.mongo-db.env
      - ./envfiles/.mongo-db-root.env
    depends_on:
      - mongo-db
    networks:
      - mongo_network

Mongo's Dockerfile:

FROM mongo:4.4-focal

#ENV MONGO_INITDB_DATABASE-"db_name"

# Expose the default MongoDB port
EXPOSE 27017

# Start MongoDB
CMD ["mongod"]

Thumbs up if this was useful Thanks,

Upvotes: 0

Pranu Pranav
Pranu Pranav

Reputation: 501

In your project directory create another directory docker-entrypoint-initdb.d then the file tree looks like this:

📦Project-directory
 ┣ 📂docker-entrypoint-initdb.d
 ┃ ┗ 📜mongo-init.js
 ┗ 📜docker-compose.yaml

The docker-compose.yml contains:

version: "3.7"
services:
  mongo:
    container_name: container-mongodb
    image: mongo:latest
    restart: always
    ports:
      - 27017:27017

    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
      MONGO_INITDB_DATABASE: root-db

    volumes:
      - ./docker-entrypoint-initdb.d/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro

mongo-init.js contains the javascript code to create user with different roles.

print("Started Adding the Users.");
db = db.getSiblingDB("admin");
db.createUser({
  user: "userx",
  pwd: "1234",
  roles: [{ role: "readWrite", db: "admin" }],
});
print("End Adding the User Roles.");

You can modify the mongo-init.js as you need.

Upvotes: 15

gforceman
gforceman

Reputation: 21

For initializing mongo with initial user-password-db triple and initdb scripts with only one docker-compose.yml, without any extra configuration, you can use bitnami/mongo image.

In my case, I didn't run my scripts under /docker-entrypoint-initdb.d directory in the container after setting environment variables; MONGODB_USERNAME and MONGODB_PASSWORD (specific env variables for bitnami image) because mongod runs with --auth option automatically when you set these variables. Consequently, I got authentication errors when the container was in the process of executing the scripts.

Because, it was connecting to: mongodb://192.168.192.2:27017/compressors=disabled&gssapiServiceName=mongodb

TERMINAL LOG OF THE ERROR

FIRST DOCKER-COMPOSE FILE:

version: "3"
services:
mongodb:
    container_name: mongodb
    image: 'docker.io/bitnami/mongodb:4.2-debian-10'
    ports:
        - "27017:27017"
    volumes:
        - "mongodb_data:/bitnami/mongodb"
        - "./mongodb/scripts:/docker-entrypoint-initdb.d"
    environment:
        - MONGODB_INITSCRIPTS_DIR=/docker-entrypoint-initdb.d
        - MONGODB_USERNAME=some_username
        - MONGODB_PASSWORD=some_password
        - MONGODB_DATABASE=some_db_name
    networks:
        backend:
    restart: unless-stopped
volumes:
    mongodb_data:
networks:
    backend:
        driver: bridge

INIT JS FILE UNDER ./mongodb/scripts PATH:

let db = connect("localhost:27017/some_db_name");
db.auth("some_username", "some_password");
let collections = db.getCollectionNames();
let storeFound = false; 
let index;
for(index=0; index<collections.length; index++){
   if ("store" === collections[index]){
       storeFound = true;   
   }
}
if(!storeFound ){
   db.createCollection("store");
   db.store.createIndex({"name": 1});
}

So, I decided to add new environment variables to my docker-compose.yml after inspecting https://github.com/bitnami/bitnami-docker-mongodb/blob/master/4.2/debian-10/rootfs/opt/bitnami/scripts/libmongodb.sh file.

In this sh file, there is function like mongodb_custom_init_scripts() for executing the scripts. For executing all script files, it runs mongodb_execute() method. In this method, after mongod instance is up and run, mongo client is connecting to the mongod instance by using some parameters.

########################
# Execute an arbitrary query/queries against the running MongoDB service
# Stdin:
#   Query/queries to execute
# Arguments:
#   $1 - User to run queries
#   $2 - Password
#   $3 - Database where to run the queries
#   $4 - Host (default to result of get_mongo_hostname function)
#   $5 - Port (default $MONGODB_PORT_NUMBER)
#   $6 - Extra arguments (default $MONGODB_CLIENT_EXTRA_FLAGS)
# Returns:
#   None
########################
mongodb_execute() {
    local -r user="${1:-}"
    local -r password="${2:-}"
    local -r database="${3:-}"
    local -r host="${4:-$(get_mongo_hostname)}"
    local -r port="${5:-$MONGODB_PORT_NUMBER}"
    local -r extra_args="${6:-$MONGODB_CLIENT_EXTRA_FLAGS}"
    local result
    local final_user="$user"
    # If password is empty it means no auth, do not specify user
    [[ -z "$password" ]] && final_user=""

    local -a args=("--host" "$host" "--port" "$port")
    [[ -n "$final_user" ]] && args+=("-u" "$final_user")
    [[ -n "$password" ]] && args+=("-p" "$password")
    [[ -n "$extra_args" ]] && args+=($extra_args)
    [[ -n "$database" ]] && args+=("$database")

    "$MONGODB_BIN_DIR/mongo" "${args[@]}"
}

After that I added new environment variables to my docker-compose like MONGODB_ADVERTISED_HOSTNAME, MONGODB_PORT_NUMBER, and, MONGODB_CLIENT_EXTRA_FLAGS

So my final docker-compose.yml looks like:

version: "3"
services:
mongodb:
    container_name: mongodb
    image: 'docker.io/bitnami/mongodb:4.2-debian-10'
    ports:
        - "27017:27017"
    volumes:
        - "mongodb_data:/bitnami/mongodb"
        - "./mongodb/scripts:/docker-entrypoint-initdb.d"
    environment:
        - MONGODB_INITSCRIPTS_DIR=/docker-entrypoint-initdb.d
        - MONGODB_USERNAME=some_username
        - MONGODB_PASSWORD=some_password
        - MONGODB_DATABASE=some_db_name
        - MONGODB_ADVERTISED_HOSTNAME=localhost
        - MONGODB_PORT_NUMBER=27017
        - MONGODB_CLIENT_EXTRA_FLAGS=--authenticationDatabase=some_db_name
    networks:
        backend:
    restart: unless-stopped
volumes:
    mongodb_data:
networks:
    backend:
        driver: bridge

Now, it was connecting by this url:

mongodb://localhost:27017/?authSource=some_db_name&compressors=disabled &gssapiServiceName=mongodb

Upvotes: 0

Adam Kozyra
Adam Kozyra

Reputation: 76

Mongo image provides the /docker-entrypoint-initdb.d/ path to deploy custom .js or .sh setup scripts.

Check this post to get more details : How to create a DB for MongoDB container on start up?

Upvotes: 3

user10047579
user10047579

Reputation:

file: docker-compose.yaml

mongo:
    image: mongo:latest
    volumes_from:
        - data
    ports:
        - "27017:27017"
    command: --auth

    container_name: "db_mongodb"

data:
    image: mongo:latest
    volumes:
        - /var/lib/mongo
        - ./setup:/setup
    command: "true"
    container_name: "db_mongodb_data"

file: .buildMongo.sh

#!/bin/sh
docker-compose down
docker-compose up -d
sleep 1
docker exec db_mongodb  mongo admin /setup/create-admin.js
docker exec db_mongodb  mongo myDb /setup/create-user.js -u admin -p admin --authenticationDatabase admin

The create-admin.js and create-user.js files are commands that you use using the mongo shell. So they must be easy for you to understand. The real direction is like the jzqa answer, "environment variables". So the question here is how to create a user. I think this answers that point at least, you can check the complete setup here https://github.com/Lus1t4nUm/mongo_docker_bootstrap

Upvotes: 1

timchunght
timchunght

Reputation: 292

EDIT: tutumcloud repository is deprecated and no longer maintained, see other answers

I suggest that you use environment variables to set mongo user, database and password. tutum (owned by Docker) published a very good image

https://github.com/tutumcloud/mongodb

docker run -d -p 27017:27017 -p 28017:28017 -e MONGODB_USER="user" -e MONGODB_DATABASE="mydatabase" -e MONGODB_PASS="mypass" tutum/mongodb

You may convert these variables into docker-compose environments variables. You don't have to hard code it.

environment:
    MONGODB_USER: "${db_user_env}"
    MONGODB_DATABASE: "${dbname_env}"
    MONGODB_PASS: "${db_pass}"

This configuration will read from your session's environment variables.

Upvotes: 21

jzqa
jzqa

Reputation: 863

The official mongo image now supports following environment variables that can be used in docker-compose as below:

environment:
      - MONGO_INITDB_ROOT_USERNAME=user
      - MONGO_INITDB_ROOT_PASSWORD=password
      - MONGO_INITDB_DATABASE=test

more explanation at: https://stackoverflow.com/a/42917632/1069610

Upvotes: 43

GreenThumb
GreenThumb

Reputation: 523

This is how I do it, my requirement was to bring up a few containers along with mongodb, the other containers expect a user to be present when they come up, this worked for me. The good part is, the mongoClientTemp exits after the command is executed so the container doesn't stick around.

version: '2'
services:
  mongo:
   image: mongo:latest
   container_name: mongo
   ports:
    - "27017:27017"
   volumes:
    - /app/hdp/mongo/data:/data/db

  mongoClientTemp:
   image: mongo:latest
   container_name: mongoClientTemp
   links:
    - mongo:mongo
   command: mongo --host mongo --eval  "db.getSiblingDB('dashboard').createUser({user:'db', pwd:'dbpass', roles:[{role:'readWrite',db:'dashboard'}]});"
   depends_on:
    - mongo

  another-container:
   image: another-image:v01
   container_name: another-container
   ports:
    - "8080:8080"
   volumes:
    - ./logs:/app/logs
   environment:
    - MONGODB_HOST=mongo
    - MONGODB_PORT=27017
   links:
    - mongo:mongo
   depends_on:
    - mongoClientTemp

Upvotes: 26

After reading the the official mongo docker page, I've found that you can create an admin user one single time, even if the auth option is being used. This is not well documented, but it simply works (hope it is not a feature). Therefore, you can keep using the auth option all the time.

I created a github repository with scripts wrapping up the commands to be used. The most important command lines to run are:

docker exec db_mongodb mongo admin /setup/create-admin.js
docker exec db_mongodb mongo admin /setup/create-user.js -u admin -p admin --authenticationDatabase admin

The first line will create the admin user (and mongo will not complain even with auth option). The second line will create your "normal" user, using the admin rights from the first one.

Upvotes: 5

Chandan Rajah
Chandan Rajah

Reputation: 21

add --noauth option to the mongo command

extract from my docker-compose.yml file

mongors:
  image: mongo:latest
  command: mongod --noprealloc --smallfiles --replSet mongors2 --dbpath /data/db --nojournal --oplogSize 16 --noauth
  environment:
    TERM: xterm
  volumes:
    - ./data/mongors:/data/db

Upvotes: -7

Related Questions