PabloRC thoughts

Running MongoDB with Replica Sets in Docker Compose

ยท 540 words ยท 3 minutes to read

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!