DEV Community

Preston Vasquez
Preston Vasquez

Posted on

Dockerizing a MongoDB Replica Set With TLS/SSL

Introduction

The goal of this guide is to create a local SSL-enabled MongoDB Replica Set using docker-compose.

TL;DR: Here is a demo repository.

Prerequisites

Docker is the only pre-requisite, but if you want to connect to the mongo shell locally (recommended) you will need MongoDB.

File Structure

Here is the file structure used in this tutorial:

.db/
config/
├─ mongodb.conf
ssl/
├─ ca.pem
├─ server.pem
docker-compose.yml
init.sh
run.sh
wait-for-mongo.sh
Enter fullscreen mode Exit fullscreen mode

.db/*

This is the database path for the mongod configuration and should be generated automatically by docker-compose.

ssl/*

The files ssl/ca.pem and ssl/server.pem are the TLS CA file and TLS certificate key file, respectively. The data in the ssl/ directory is used by the configuration file to initialize a MongoDB server with SSL enabled. This directory will be volumized on the Docker container for the primary replicant.

This tutorial assumes prior knowledge of TLS/SSL as well as access to valid certificates. For more information on generating certificates using openssl, see this guide.

config/mongodb.conf

This is the configuration file used to configure the mongod instance at startup and will be be volumized on the Docker container for the primary replicant:

net:
  ssl:
    mode: requireSSL
    PEMKeyFile: "/data/ssl/server.pem"
    CAFile: "/data/ssl/ca.pem"
storage:
  dbPath: "/data/db"
net:
  bindIp: 127.0.0.1
  port: 27017
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

In our docker-compose file, we create three nodes: mongo1 (primary), mongo2, and mongo3. These replicant containers use the bridge network mongo_network.

version: '3'

networks:
  mongo_network:
    driver: bridge

services:
  mongo1:
    hostname: mongo1
    image: mongo
    ports:
      - 27017:27017
    restart: always
    entrypoint: [ "/usr/bin/mongod", "--config", "/data/config/ssl.conf", "--bind_ip_all", "--replSet", "dbrs" ]
    networks:
      - mongo_network
    volumes:
      - ./.db/mongo1:/data/db
      - ./wait-for-mongodb.sh:/scripts/wait-for-mongodb.sh
      - ./init.sh:/scripts/init.sh
      - ./ssl:/data/ssl
      - ./config:/data/config
    links:
      - mongo2
      - mongo3

  mongo2:
    hostname: mongo2
    image: mongo
    ports:
      - 27018:27017
    restart: always
    entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "dbrs" ]
    networks:
      - mongo_network
    volumes:
      - ./.db/mongo2:/data/db
      - ./wait-for-mongodb.sh:/scripts/wait-for-mongodb.sh

  mongo3:
    hostname: mongo3
    image: mongo
    ports:
      - 27019:27017
    restart: always
    entrypoint: [ "/usr/bin/mongod", "--bind_ip_all", "--replSet", "dbrs" ]
    networks:
      - mongo_network
    volumes:
      - ./.db/mongo3:/data/db
      - ./wait-for-mongodb.sh:/scripts/wait-for-mongodb.sh
Enter fullscreen mode Exit fullscreen mode

Before composing, it is recommended to update the /etc/hosts file to include the replicant mappings:

# Replica set mappings
127.0.0.1 mongo1
127.0.0.1 mongo2
127.0.0.1 mongo3
Enter fullscreen mode Exit fullscreen mode

init.sh

This shell file is used to initialize the replica set and will be ran in the docker container for the primary node (i.e. mongo1, defined in the "docker-compose.yml" section).

#!/bin/bash

mongosh --tls \
  --tlsCAFile /data/ssl/ca.pem \
  --tlsCertificateKeyFile /data/ssl/server.pem <<EOF
var config = {
    "_id": "dbrs",
    "version": 1,
    "members": [
        {
            "_id": 1,
            "host": "mongo1:27017",
            "priority": 3
        }
    ]
};
rs.initiate(config, { force: true });
rs.status();

var config = {
    "_id": "dbrs",
    "version": 1,
    "members": [
        {
            "_id": 2,
            "host": "mongo2:27017",
            "priority": 2
        },
        {
            "_id": 3,
            "host": "mongo3:27017",
            "priority": 1
        }
    ]
};
rs.initiate(config, { force: true });
rs.status();
EOF
Enter fullscreen mode Exit fullscreen mode

wait-for-mongo.sh

This script will wait for a mongo server to come up

#!/bin/bash

NAME=$1
OPTS=$2

while [ -z `mongosh --eval 'db.runCommand("ping").ok' --quiet --host $OPTS 2>/dev/null` ]; do
    echo "Waiting for MongoDB $NAME to start..."
    sleep 1
done

echo "MongoDB $NAME is up and running"
Enter fullscreen mode Exit fullscreen mode

run.sh

run.sh will (1) start the replicants, (2) wait for the replicants to come up, and (3) define primary and secondary replicants on mongo1.

#!/bin/bash

set -e

# remove volumes
rm -rf .db

# drop existing containers
docker compose -f "docker-compose.yml" down

# prune containers
docker system prune --force

docker-compose -f "docker-compose.yml" up -d \
    --remove-orphans \
    --force-recreate \
    --build mongo1

docker-compose -f "docker-compose.yml" up -d \
    --remove-orphans \
    --force-recreate \
    --build mongo2

docker-compose -f "docker-compose.yml" up -d \
    --remove-orphans \
    --force-recreate \
    --build mongo3

docker-compose -f "docker-compose.yml" exec -T mongo3 /scripts/wait-for-mongodb.sh "mongo3:27017"
docker-compose -f "docker-compose.yml" exec -T mongo2 /scripts/wait-for-mongodb.sh "mongo2:27017"

M1_OPTS="--tls --tlsCAFile /data/ssl/ca.pem --tlsCertificateKeyFile /data/ssl/server.pem --tlsAllowInvalidCertificates"
docker-compose -f "docker-compose.yml" exec -T mongo1 /scripts/wait-for-mongodb.sh "mongo1:27017" "$M1_OPTS"

echo "Creating replica set..."
docker-compose -f "docker-compose.yml" exec -T mongo1 /scripts/init.sh
Enter fullscreen mode Exit fullscreen mode

Putting everything together, we run the above shell script and then test our setup by opening a mongo shell:

mongosh --tls --tlsCAFile <path-to-ca-file> --tlsCertificateKeyFile <path-to-client-file>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)