Dockerizing a MongoDB Replica Set With TLS/SSL
Preston Vasquez
Posted on November 7, 2022
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
.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
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
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
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
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"
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
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>
Posted on November 7, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.