Quick and Easy MongoDB Replica Set Deployment with Docker Compose

abdelfattahradwan

Abdelfattah Radwan

Posted on July 15, 2024

Quick and Easy MongoDB Replica Set Deployment with Docker Compose

For the past few weeks, in my free time, I wanted to learn how to set up a MongoDB replica set that I could use for development or production, but I was frustrated by the resources online.

Most were outdated or didn’t explain how to set up authentication using key files, which is required when deploying replica sets.

So, without further ado, let’s get started!

First of all, you will need to download and install Docker. I downloaded & installed Docker Desktop for Windows.

Next, you need to open up a Windows Terminal (or CMD.exe) window, and run the following command: docker image pull mongo:latest. This will pull the latest version of the mongo:latest image.

You can also use Docker Desktop to do that from the GUI. Open up the Docker Desktop application, search for mongo:latest, select "latest" from the "Tag" dropdown, and click "Pull".

Docker Desktop search results for

Once the image is pulled successfully, create a new directory named "mongodb" anywhere on your machine.

Inside that directory, create a new file named docker-compose.yml and open it up in your preferred text/source code editor. I will be using VSCode.

Let's begin by specifying the name of our Docker Compose project.

name: mongodb-replica-set
Enter fullscreen mode Exit fullscreen mode

Then, let’s define our first MongoDB replica set instance:

services:
  mongodb-1:
    image: mongo:latest
    container_name: mongodb-1
    command:
      [
        "mongod",
        "--port",
        "27017",
        "--replSet",
        "rs0",
        "--bind_ip_all",
        "--keyFile",
        "/data/configdb/keyfile",
      ]
    ports:
      - "27017:27017"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: always
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=root
      - MONGO_REPLICA_SET_KEY=${MONGO_REPLICA_SET_KEY}
    volumes:
      - "./init-keyfile.sh:/docker-entrypoint-initdb.d/init-keyfile.sh"
      - "./data/mongodb-1:/data/db"
      - "./data/mongodb-1/configdb:/data/configdb"
Enter fullscreen mode Exit fullscreen mode

You might be a little intimidated by the size of this “simple” config but fear not, let’s go through it:

  • Image and Container Name: We use the latest MongoDB image and name this container mongodb-1.
  • Command: This starts the MongoDB server (mongod) with specific options:
    • port 27017: Sets the port to 27017.
    • replSet rs0: Specifies the replica set name as rs0.
    • bind_ip_all: Binds MongoDB to all available IP addresses.
    • keyFile /data/configdb/keyfile: Specifies the key file for authentication.
  • Ports: Maps port 27017 of the container to port 27017 on the host machine.
  • Extra Hosts: Adds an entry to the container’s /etc/hosts file.
  • Restart Policy: Ensures the container always restarts.
  • Environment Variables: Sets the root username, password, and the replica set key.
  • Volumes: Mounts necessary files and directories from the host to the container.

On Windows, although not required, I suggest you add the "host.docker.internal:host-gateway" to the extra_hosts attribute. This allows drivers and apps like MongoDB Compass to resolve addresses of other replica set instances. On Linux, that is not required as far as I know.

You might have also noticed that we have a custom environment variable called MONGO_REPLICA_SET_KEY. It holds the key that will be put inside our instances’ key files. The key file itself is created using the init-keyfile.sh script. The script simply copies the value of MONGO_REPLICA_SET_KEY into a file named "keyfile" located at /data/configdb and sets the correct permissions and owner of the file.

#!/bin/bash

# Echo MONGO_REPLICA_SET_KEY to the keyfile.

echo $MONGO_REPLICA_SET_KEY > /data/configdb/keyfile

# Change the permissions of the keyfile.

chmod 400 /data/configdb/keyfile

# Change the ownership of the keyfile.

chown mongodb:mongodb /data/configdb/keyfile
Enter fullscreen mode Exit fullscreen mode

You can generate a key using the following command (taken from the MongoDB docs):

openssl rand -a -base64 756
Enter fullscreen mode Exit fullscreen mode

Copy and paste the generated key into a .env file:

MONGO_REPLICA_SET_KEY="YOUR_REPLICA_SET_KEY"
Enter fullscreen mode Exit fullscreen mode

Now, let’s define a couple more MongoDB instances for our replica set:

mongodb-2:
    image: mongo:latest
    container_name: mongodb-2
    command:
      [
        "mongod",
        "--port",
        "27018",
        "--replSet",
        "rs0",
        "--bind_ip_all",
        "--keyFile",
        "/data/configdb/keyfile",
      ]
    ports:
      - "27018:27018"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: always
    environment:
      - MONGO_REPLICA_SET_KEY=${MONGO_REPLICA_SET_KEY}
    volumes:
      - "./init-keyfile.sh:/docker-entrypoint-initdb.d/init-keyfile.sh"
      - "./data/mongodb-2:/data/db"
      - "./data/mongodb-2/configdb:/data/configdb"
Enter fullscreen mode Exit fullscreen mode
mongodb-3:
    image: mongo:latest
    container_name: mongodb-3
    command:
      [
        "mongod",
        "--port",
        "27019",
        "--replSet",
        "rs0",
        "--bind_ip_all",
        "--keyFile",
        "/data/configdb/keyfile",
      ]
    ports:
      - "27019:27019"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: always
    environment:
      - MONGO_REPLICA_SET_KEY=${MONGO_REPLICA_SET_KEY}
    volumes:
      - "./init-keyfile.sh:/docker-entrypoint-initdb.d/init-keyfile.sh"
      - "./data/mongodb-3:/data/db"
      - "./data/mongodb-3/configdb:/data/configdb"
Enter fullscreen mode Exit fullscreen mode

Both of these two instances are pretty much the same as the first one with only minor differences.

Now, let’s define a fourth MongoDB instance that will wait for the first instance to start before initiating the replica set.

mongodb-init:
    image: mongo:latest
    container_name: mongodb-init
    depends_on:
      - mongodb-1
      - mongodb-2
      - mongodb-3
    extra_hosts:
      - "host.docker.internal:host-gateway"
    command: bash -c "chmod +x init-replica-set.sh && ./init-replica-set.sh"
    volumes:
      - "./init-replica-set.sh:/init-replica-set.sh"
    restart: no
Enter fullscreen mode Exit fullscreen mode

Pretty simple, eh? This instance is configured to wait for the other three instances’ containers to start before it starts and executes a script called init-replica-set.sh. Here is said script:

#!/bin/bash

echo "Waiting for MongoDB to start on host.docker.internal:27017..."

until mongosh --quiet --host host.docker.internal --port 27017 --eval "db.runCommand({ ping: 1 }).ok" | grep 1 &>/dev/null; do
  sleep 1
done

echo "MongoDB has started successfully"

echo "Initiating MongoDB replica set..."

mongosh -u root -p root --host host.docker.internal --port 27017 --eval "
  rs.initiate({
    _id: 'rs0',
    members: [
      {
        _id: 0,
        host: 'host.docker.internal:27017'
      },
      {
        _id: 1,
        host: 'host.docker.internal:27018'
      },
      {
        _id: 2,
        host: 'host.docker.internal:27019'
      }
    ]
  })
"
Enter fullscreen mode Exit fullscreen mode

Again, don’t worry if it looks intimidating, let’s go through it:

echo "Waiting for MongoDB to start on host.docker.internal:27017..."

until mongosh --quiet --host host.docker.internal --port 27017 --eval "db.runCommand({ ping: 1 }).ok" | grep 1 &>/dev/null; do
  sleep 1
done

echo "MongoDB has started successfully"
Enter fullscreen mode Exit fullscreen mode
  • This block waits until the MongoDB instance on port 27017 is up and running. It checks by pinging the MongoDB server and waiting for a successful response.
  • The loop continues to check every second (sleep 1) until a successful ping response is received.
echo "Initiating MongoDB replica set..."

mongosh -u root -p root --host host.docker.internal --port 27017 --eval "
  rs.initiate({
    _id: 'rs0',
    members: [
      {
        _id: 0,
        host: 'host.docker.internal:27017'
      },
      {
        _id: 1,
        host: 'host.docker.internal:27018'
      },
      {
        _id: 2,
        host: 'host.docker.internal:27019'
      }
    ]
  })
"
Enter fullscreen mode Exit fullscreen mode
  • This block initiates the replica set using the mongosh command.
  • It connects to the MongoDB instance on port 27017 using the root credentials.
  • The rs.initiate command sets up the replica set with the specified members (host.docker.internal:27017, host.docker.internal:27018, and host.docker.internal:27019).

Note that we used host.docker.internal and NOT the container name as you’d usually do when working with containers. This again is only required on Windows.

The complete docker-compose.yml file should look something like this:

name: mongodb-replica-set

services:
  mongodb-1:
    image: mongo:latest
    container_name: mongodb-1
    command:
      [
        "mongod",
        "--port",
        "27017",
        "--replSet",
        "rs0",
        "--bind_ip_all",
        "--keyFile",
        "/data/configdb/keyfile",
      ]
    ports:
      - "27017:27017"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: unless-stopped
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=root
      - MONGO_REPLICA_SET_KEY=${MONGO_REPLICA_SET_KEY}
    volumes:
      - "./init-keyfile.sh:/docker-entrypoint-initdb.d/init-keyfile.sh"
      - "./data/mongodb-1:/data/db"
      - "./data/mongodb-1/configdb:/data/configdb"

  mongodb-2:
    image: mongo:latest
    container_name: mongodb-2
    command:
      [
        "mongod",
        "--port",
        "27018",
        "--replSet",
        "rs0",
        "--bind_ip_all",
        "--keyFile",
        "/data/configdb/keyfile",
      ]
    ports:
      - "27018:27018"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: always
    environment:
      - MONGO_REPLICA_SET_KEY=${MONGO_REPLICA_SET_KEY}
    volumes:
      - "./init-keyfile.sh:/docker-entrypoint-initdb.d/init-keyfile.sh"
      - "./data/mongodb-2:/data/db"
      - "./data/mongodb-2/configdb:/data/configdb"

  mongodb-3:
    image: mongo:latest
    container_name: mongodb-3
    command:
      [
        "mongod",
        "--port",
        "27019",
        "--replSet",
        "rs0",
        "--bind_ip_all",
        "--keyFile",
        "/data/configdb/keyfile",
      ]
    ports:
      - "27019:27019"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    restart: always
    environment:
      - MONGO_REPLICA_SET_KEY=${MONGO_REPLICA_SET_KEY}
    volumes:
      - "./init-keyfile.sh:/docker-entrypoint-initdb.d/init-keyfile.sh"
      - "./data/mongodb-3:/data/db"
      - "./data/mongodb-3/configdb:/data/configdb"

  mongodb-init:
    image: mongo:latest
    container_name: mongodb-init
    depends_on:
      - mongodb-1
      - mongodb-2
      - mongodb-3
    extra_hosts:
      - "host.docker.internal:host-gateway"
    command: bash -c "chmod +x init-replica-set.sh && ./init-replica-set.sh"
    volumes:
      - "./init-replica-set.sh:/init-replica-set.sh"
    restart: no

networks:
  default:
    name: mongodb-replica-set-network
Enter fullscreen mode Exit fullscreen mode

Finally, run the following command in the directory containing the docker-compose.yml file: docker compose up -d

You might have to wait a bit before everything starts up, but afterwards, you can connect to your replica set via one of the drivers or MongoDB Compass. I will use MongoDB Compass to demonstrate the final result.

Open MongoDB Compass and enter the following connection string: mongodb://root:root@localhost:27017,localhost:27018,localhost:27019/?replicaSet=rs0

Click on “Connect” and voila! MongoDB Compass will present you with a screen like this:

MongoDB Compass after connecting to the replica set

That’s it! Now, you have a fully functional MongoDB replica set! However, running all instances of the replica set on the same machine defeats the purpose of replica sets: redundancy and data availability. So, I advise you to spread your instances across different machines or create a single-instance replica set for local development.

💖 💪 🙅 🚩
abdelfattahradwan
Abdelfattah Radwan

Posted on July 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related