logo
Published on

Docker-compose for a local MongoDB cluster

Authors

In this post we'll explore how to set up MongoDB replica set using Docker and Docker Compose.

Generate the keyfile

First, we'll need a key that the replica set nodes will use to communicate with each other securly.

The key's length must be between 6 and 1024 characters and may only contain characters in the base64 set. We can generate such a key using openssl:

openssl rand -base64 768 > mongo-replication.key

Then we'll reduce the permissions on the key, else MongoDB will complain that the permissions of the key are too open.

chmod 400 mongo-replication.key
sudo chown 999:999 mongo-replication.key

Keyfiles are only suitable for development purposes. For productions environments you should use x.509 certificates.

Starting the containers

We'll start three MongoDB containers, that will compose a replica set called rs1. We'll also pass the key that we created earlier to each instance. This is the content of the docker-compose.yml file:

version: '3.7'

services:
  mongodb_1:
    image: mongo:5
    hostname: mongodb_1
    command: --replSet rs1 --keyFile /etc/mongo-replication.key
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: admin
    ports:
      - 127.0.10.1:27017:27017
    volumes:
      - mongodb_data_1:/data/db
      - ./mongo-replication.key:/etc/mongo-replication.key

  mongodb_2:
    image: mongo:5
    hostname: mongodb_2
    command: --replSet rs1 --keyFile /etc/mongo-replication.key
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: admin
    ports:
      - 127.0.10.2:27017:27017
    volumes:
      - mongodb_data_2:/data/db
      - ./mongo-replication.key:/etc/mongo-replication.key
    depends_on:
      - mongodb_1

  mongodb_3:
    image: mongo:5
    hostname: mongodb_3
    command: --replSet rs1 --keyFile /etc/mongo-replication.key
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: admin
    ports:
      - 127.0.10.3:27017:27017
    volumes:
      - mongodb_data_3:/data/db
      - ./mongo-replication.key:/etc/mongo-replication.key
    depends_on:
      - mongodb_1

volumes:
  mongodb_data_1:
  mongodb_data_2:
  mongodb_data_3:

Start the services using:

docker-compose up -d

The containers should be up within a few seconds:

CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                        NAMES
ce555abc94b9   mongo:5   "docker-entrypoint.s…"   21 seconds ago   Up 19 seconds   127.0.10.2:27017->27017/tcp  mongodb-mongodb_2-1
ad847f72fce4   mongo:5   "docker-entrypoint.s…"   21 seconds ago   Up 19 seconds   127.0.10.3:27017->27017/tcp  mongodb-mongodb_3-1
fbd824608451   mongo:5   "docker-entrypoint.s…"   21 seconds ago   Up 20 seconds   127.0.10.1:27017->27017/tcp  mongodb-mongodb_1-1

Initialize the replica set

With all the instances up and running, we have to initialize the replica set next.

Add the content below to a file called init-replica-set.js:

db.auth('admin', 'admin')
rs.initiate({
  _id: 'rs1',
  version: 1,
  members: [
    { _id: 0, host: 'mongodb_1:27017', priority: 1 },
    { _id: 1, host: 'mongodb_2:27017', priority: 0 },
    { _id: 2, host: 'mongodb_3:27017', priority: 0 },
  ],
})

and execute it against the MongoDB cluster using:

docker run --rm --network mongodb_default mongo:5 mongosh \
           --host mongodb_1:27017 --username admin --password admin \
           --authenticationDatabase admin admin \
           --eval "$(< init-replica-set.js)"

The response from MongoDB will be a simple { "ok" : 1 }

Use the following command to inspect the replica set status:

docker run --rm --network mongodb_default mongo:5 mongosh \
           --host mongodb_1:27017 --username admin --password admin \
           --authenticationDatabase admin admin \
           --eval "rs.status()"

The response should be something like this:

{
    "set" : "rs1",
    ...
    "members" : [
    {
      "_id" : 0,
      "name" : "mongodb_1:27017",
      "health" : 1,
      "state" : 1,
      "stateStr" : "PRIMARY",
      ...
    },
    {
      "_id" : 1,
      "name" : "mongodb_2:27017",
      "health" : 1,
      "state" : 2,
      "stateStr" : "SECONDARY",
      ...
    },
    {
      "_id" : 2,
      "name" : "mongodb_3:27017",
      "health" : 1,
      "state" : 2,
      "stateStr" : "SECONDARY",
      ...
    }
  ]
  ...
}

Create a user

The replica set is up and running; next step, create a user with the dbOwner role. This role combines the readWrite, dbAdmin and userAdmin roles, allowing the user to do pretty much anything to the database.

db.auth('admin', 'admin')
db = db.getSiblingDB('my_database')
db.createUser({
  user: 'my_user',
  pwd: 'my_pass',
  roles: [
    {
      role: 'dbOwner',
      db: 'my_database',
    },
  ],
})

Add the above to a file called init-user.js and execute it against the MongoDB cluster using:

docker run --rm --network mongodb_default mongo:5 mongosh \
           --host mongodb_1:27017 --username admin --password admin \
           --authenticationDatabase admin admin \
           --eval "$(< init-user.js)"

Connecting to the replica set

Using REPL

docker run --rm -i -t --network mongodb_default mongo:5 mongosh \
           --host mongodb_1,mongodb_2,mongodb_3 --username admin --password admin \
           --authenticationDatabase admin admin

Connection string

mongodb://my_user:my_pass@mongodb_1:27017,mongodb_2:27017,mongodb_3:27017/my_database?replicaSet=rs1

add the following hosts to your hosts file

127.0.10.1  mongodb_1
127.0.10.2  mongodb_2
127.0.10.3  mongodb_3

Taking it all down

Take down containers and delete their corresponding volumes:

docker-compose down -v