Docker-compose for a local MongoDB cluster

November 13, 2021-3 min read

In this post we'll explore how to set up MongoDB replica set using Docker and Docker Compose.

Generate the keyfile

First, we'll need a key that the replica set nodes will use to communicate with each other securly.

The key's length must be between 6 and 1024 characters and may only contain characters in the base64 set. We can generate such a key using openssl:

openssl rand -base64 768 > mongo-replication.key

Then we'll reduce the permissions on the key, else MongoDB will complain that the permissions of the key are too open.

chmod 400 mongo-replication.key
sudo chown 999:999 mongo-replication.key

Keyfiles are only suitable for development purposes. For productions environments you should use x.509 certificates.

Starting the containers

We'll start three MongoDB containers, that will compose a replica set called rs1. We'll also pass the key that we created earlier to each instance. This is the content of the docker-compose.yml file:

version: '3.7'
services:
mongodb_1:
image: mongo:5
hostname: mongodb_1
command: --replSet rs1 --keyFile /etc/mongo-replication.key
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: admin
ports:
- 127.0.10.1:27017:27017
volumes:
- mongodb_data_1:/data/db
- ./mongo-replication.key:/etc/mongo-replication.key
mongodb_2:
image: mongo:5
hostname: mongodb_2
command: --replSet rs1 --keyFile /etc/mongo-replication.key
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: admin
ports:
- 127.0.10.2:27017:27017
volumes:
- mongodb_data_2:/data/db
- ./mongo-replication.key:/etc/mongo-replication.key
depends_on:
- mongodb_1
mongodb_3:
image: mongo:5
hostname: mongodb_3
command: --replSet rs1 --keyFile /etc/mongo-replication.key
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: admin
ports:
- 127.0.10.3:27017:27017
volumes:
- mongodb_data_3:/data/db
- ./mongo-replication.key:/etc/mongo-replication.key
depends_on:
- mongodb_1
volumes:
mongodb_data_1:
mongodb_data_2:
mongodb_data_3:

Start the services using:

docker-compose up -d

The containers should be up within a few seconds:

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce555abc94b9 mongo:5 "docker-entrypoint.s…" 21 seconds ago Up 19 seconds 127.0.10.2:27017->27017/tcp mongodb-mongodb_2-1
ad847f72fce4 mongo:5 "docker-entrypoint.s…" 21 seconds ago Up 19 seconds 127.0.10.3:27017->27017/tcp mongodb-mongodb_3-1
fbd824608451 mongo:5 "docker-entrypoint.s…" 21 seconds ago Up 20 seconds 127.0.10.1:27017->27017/tcp mongodb-mongodb_1-1

Initialize the replica set

With all the instances up and running, we have to initialize the replica set next.

Add the content below to a file called init-replica-set.js:

db.auth('admin', 'admin');
rs.initiate(
{_id: "rs1", version: 1,
members: [
{ _id: 0, host : "mongodb_1:27017", priority: 1 },
{ _id: 1, host : "mongodb_2:27017", priority: 0 },
{ _id: 2, host : "mongodb_3:27017", priority: 0 }
]
}
);

and execute it against the MongoDB cluster using:

docker run --rm --network mongodb_default mongo:5 mongosh \
--host mongodb_1:27017 --username admin --password admin \
--authenticationDatabase admin admin \
--eval "$(< init-replica-set.js)"

The response from MongoDB will be a simple { "ok" : 1 }

Use the following command to inspect the replica set status:

docker run --rm --network mongodb_default mongo:5 mongosh \
--host mongodb_1:27017 --username admin --password admin \
--authenticationDatabase admin admin \
--eval "rs.status()"

The response should be something like this:

{
"set" : "rs1",
...
"members" : [
{
"_id" : 0,
"name" : "mongodb_1:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
...
},
{
"_id" : 1,
"name" : "mongodb_2:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
...
},
{
"_id" : 2,
"name" : "mongodb_3:27017",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
...
}
]
...
}

Create a user

The replica set is up and running; next step, create a user with the dbOwner role. This role combines the readWrite, dbAdmin and userAdmin roles, allowing the user to do pretty much anything to the database.

db.auth('admin', 'admin');
db = db.getSiblingDB('my_database');
db.createUser({
user: 'my_user',
pwd: 'my_pass',
roles: [
{
role: 'dbOwner',
db: 'my_database',
},
],
});

Add the above to a file called init-user.js and execute it against the MongoDB cluster using:

docker run --rm --network mongodb_default mongo:5 mongosh \
--host mongodb_1:27017 --username admin --password admin \
--authenticationDatabase admin admin \
--eval "$(< init-user.js)"

Connecting to the replica set

Using REPL

docker run --rm -i -t --network mongodb_default mongo:5 mongosh \
--host mongodb_1,mongodb_2,mongodb_3 --username admin --password admin \
--authenticationDatabase admin admin

Connection string

mongodb://my_user:my_pass@mongodb_1:27017,mongodb_2:27017,mongodb_3:27017/my_database?replicaSet=rs1

add the following hosts to your hosts file

127.0.10.1 mongodb_1
127.0.10.2 mongodb_2
127.0.10.3 mongodb_3

Taking it all down

Take down containers and delete their corresponding volumes:

docker-compose down -v