Just a quick post as I am working on updating a recent Go project from ORM to ODM (Mongodb) so I wanted to show how I quickly configured a Mongodb replicate set with Docker-compose following an article I found on google here. The reason for the change is simply for scalability (work related) and sheer interests (personal growth). A great read on the subject of replication can be found here.

You can read a great deal about Compose to get a better understanding; but I believe it is quite easy to understand the yaml|yml file. The following docker-compose.yml


important

You will need to run docker twice. Once with the environment variables while having commented out the entrypoint followed by the opposite. The reason for this is quite simple. When mongo1 uses the environment variables it creates the user root in the admin database while also running --auth. To properly configure the replica set you first need to create the authentication. I believe having the entrypoint the way it is causes a problem trying to do everything in one shot.


version: '3.7'

services:
    mongo1:
        hostname: mongo1
        container_name: gogqlserver_mongo1
        image: mongo:4.2.2-bionic
        restart: unless-stopped
#        environment:
#            MONGO_INITDB_ROOT_USERNAME: root
#            MONGO_INITDB_ROOT_PASSWORD: password
#            MONGO_INITDB_DATABASE: admin
        expose:
            - 27017
        ports:
            - 27017:27017
        volumes:
            - ./docker/mongo/etc/mongod-keyfile:/etc/mongod-keyfile:ro # Permission: 400
            - ./docker/mongo/root:/root:ro
#            - ./docker/mongo/volume:/data/db
#            - ./docker/mongo/config:/data/configdb
        entrypoint: [ "/usr/bin/mongod", "--auth", "--keyFile", "/etc/mongod-keyfile", "--bind_ip_all", "--replSet", "rs0" ]
        depends_on:
          - mongo2
          - mongo3
        
    mongo2:
        hostname: mongo2
        container_name: gogqlserver_mongo2
        image: mongo:4.2.2-bionic
        restart: unless-stopped
        expose:
            - 27017
        ports:
            - 27018:27017
        volumes:
            - ./docker/mongo/etc/mongod-keyfile:/etc/mongod-keyfile:ro # Permission: 400
        entrypoint: [ "/usr/bin/mongod", "--auth", "--keyFile", "/etc/mongod-keyfile", "--bind_ip_all", "--replSet", "rs0" ]
        depends_on:
          - mongo3

    mongo3:
        hostname: mongo3
        container_name: gogqlserver_mongo3
        image: mongo:4.2.2-bionic
        restart: unless-stopped
        expose:
            - 27017
        ports:
            - 27019:27017
        volumes:
            - ./docker/mongo/etc/mongod-keyfile:/etc/mongod-keyfile:ro # Permission: 400
        entrypoint: [ "/usr/bin/mongod", "--auth", "--keyFile", "/etc/mongod-keyfile", "--bind_ip_all", "--replSet", "rs0" ]

    mongo-express:
        container_name: gogqlserver_mongo_express
        image: mongo-express:0.49.0
        restart: unless-stopped
        ports:
            - 8081:8081
        environment:
            ME_CONFIG_MONGODB_SERVER: 'mongo1,mongo2,mongo3'
            ME_CONFIG_MONGODB_ADMINUSERNAME: root
            ME_CONFIG_MONGODB_ADMINPASSWORD: password
        depends_on:
          - mongo1
          - mongo2
          - mongo3

contains three mongo containers with the primary as mongo1. I have also included mongo-express an admin interface to use as a playground for mongo. However, before accessing http://localhost:8081 - the mongo-express admin site we need to run a command rs.initiate(). We will need three scripts and one keyfile with the following structure.

docker/mongo
├── etc
│   └── mongod-keyfile
└── root
    ├── 000_init_replicaSet.js
    ├── 001_init_database.js
    └── 002_init_user.js

000_init_replicaSet.js

rs.initiate(
    {
        _id: "rs0",
        members: [
            {_id: 0, host: "mongo1:27017"},
            {_id: 1, host: "mongo2:27017"},
            {_id: 2, host: "mongo3:27017", arbiterOnly: true}
        ]
    }
);

001_init_database.js

var testdb = db.getSiblingDB('test');

testdb.createCollection('test');

002_init_user.js

var testdb = db.getSiblingDB('test');

testdb.createUser(
    {
        user: 'test',
        pwd: 'password',
        roles: [
            {
                role: 'root', db: 'admin'
            },
            {
                role: 'dbOwner', db: 'test'
            }
        ]
    }
);

testdb.getUsers();

and finally

openssl rand -base64 756 > docker/mongo/etc/mongod-keyfile
chmod 400 docker/mongo/etc/mongod-keyfile

You can also use the following script as well but you still need to manually edit the docker-compose.yml and run the script twice. I suppose i could add several sed commands to do the work.

#!/bin/sh
docker-compose stop
docker-compose up --build --remove-orphans -d
sleep 2
docker-compose exec mongo1 mongo admin -u root -p password /root/000_init_replicaSet.js
docker-compose exec mongo1 mongo admin -u root -p password /root/001_init_database.js
docker-compose exec mongo1 mongo admin -u root -p password /root/002_init_user.js

In the end you should have a functional authenticated replica set with Mongo. Do not forget that you need to use rs.slaveOk() on secondary and as well use your user account to access Mongo. The user created by the environment variables only exists in the admin database.