Lately, I’ve been involved in a project that uses MongoDB. I haven’t worked much with NoSQL databases, so it’s great to give it a shot. I’m used to working with dockerized applications, but this specific project doesn’t have this setup, instead using a locally running MongoDB on the host. It’s okay, but I’m so accustomed to Docker that I can’t help but feel uncomfortable nowadays having a database running directly on my local machine.
So, I decided to dockerize the database myself. I tried several setups online, but none of them worked out of the box. I finally got it working and now have a functional compose.yml. The special detail of this setup is that I needed to have several containers running, configured as Replica Sets.
What is a MongoDB Replica Set? ๐
In order to improve database availability, MongoDB implements the Replica Set concept. This basically allows multiple instances of the database to run with the same data. They operate in a Leader-Follower architecture, where the Leader manages communication outside the cluster and sends information to its followers to keep their data updated.
All instances report their status via heartbeats. If the leader crashes, the remaining followers decide which one will become the new leader, and the database continues working without exposing any issues to clients.
The Code ๐
The compose.yml file is fairly simple. It sets up three different MongoDB instances, exposing their ports, and a fourth instance that only connects to these instances to configure them as part of a Replica Set.
services:
mongo1:
container_name: mongo1
image: mongo:latest
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports:
- 27017:27017
volumes:
- mongo1_data:/data/db
networks:
- mongo_network
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
start_period: 40s
mongo2:
container_name: mongo2
image: mongo:latest
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports:
- 27018:27017
volumes:
- mongo2_data:/data/db
networks:
- mongo_network
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
mongo3:
container_name: mongo3
image: mongo:latest
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports:
- 27019:27017
volumes:
- mongo3_data:/data/db
networks:
- mongo_network
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
interval: 10s
timeout: 10s
retries: 5
start_period: 40s
# This service initializes the replica set
mongo-init:
image: mongo:latest
restart: "no"
depends_on:
mongo1:
condition: service_healthy
mongo2:
condition: service_healthy
mongo3:
condition: service_healthy
networks:
- mongo_network
volumes:
- ./init-replica.sh:/init-replica.sh
entrypoint: ["bash", "/init-replica.sh"]
networks:
mongo_network:
driver: bridge
volumes:
mongo1_data:
mongo2_data:
mongo3_data:
mongo1, mongo2, and mongo3 simply start instances of MongoDB with a named volume mount for each one and expose their 27017 port (MongoDB’s default port). The mongo-init service waits for these instances to be running and then executes the following script, init-replica.sh:
#!/bin/bash
echo "Waiting for MongoDB instances to start..."
sleep 10
echo "Initializing replica set..."
mongosh --host mongo1:27017 <<EOF
rs.initiate(
{
_id : 'rs0',
members: [
{ _id : 0, host : "mongo1:27017", priority: 1 },
{ _id : 1, host : "mongo2:27017", priority: 0.5 },
{ _id : 2, host : "mongo3:27017", priority: 0.5 }
]
}
)
EOF
mongosh --host mongo1:27017 --eval "rs.status()"
Remember to run chmod +x init-replica.sh to give the script the necessary execution permissions.
With these two files in place, just run docker compose up -d and your MongoDB replica set is ready to use!