DEV Community

Cover image for Local MongoDB Replica Set Cluster for Real-Time Container Apps (with Mongo Express)
Selim
Selim

Posted on • Edited on

Local MongoDB Replica Set Cluster for Real-Time Container Apps (with Mongo Express)

Run a full 3-node MongoDB replica set locally for real-time containerized applications, with authentication, failover, and Mongo Express — all in one compose.yml.

If you’re developing containerized applications that need real-time MongoDB features like Change Streams, you’ll want a local replica set.

This guide shows you how to run a 3-node MongoDB cluster with authentication, automatic failover, and a built-in web interface — all in one compose.yml file.

I built this setup primarily for Podman, but it works with most container platforms, including Docker, with no changes.

You’ll get:

  • A 3-node MongoDB replica set — perfect for local development & real-time features
  • Keyfile-based authentication
  • Automatic replica set initialization
  • A configurable root admin user via .env
  • Mongo Express for browser-based DB management
  • Ready-to-use connection strings for primary and secondary nodes
  • Works with Docker & Podman

Why Use a Replica Set Locally?

Running a MongoDB replica set locally lets you:

  • Test high availability scenarios — simulate node failures
  • Work with real-time features like Change Streams
  • Develop apps that mirror production environments
  • Explore read preference configurations with primary and secondary nodes

The Setup

The cluster is defined in a single compose.yml that includes:

  • keyfile-generator – creates a secure keyfile for internal node authentication
  • mongo1, mongo2, mongo3 – the three replica set members
  • mongo-express – a web-based MongoDB admin interface

1 .env File

Create a .env file in the same directory:

MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=pass
Enter fullscreen mode Exit fullscreen mode

Change these values to your preferred admin credentials.


2 compose.yml

Here’s the full configuration:

services:
  keyfile-generator:
    image: alpine:latest
    restart: "no"
    command: >
      sh -c "
        apk add --no-cache openssl &&
        if [ ! -f /data/keyfile ]; then
          openssl rand -base64 756 > /data/keyfile &&
          chmod 400 /data/keyfile;
        fi
      "
    volumes:
      - keyfile_data:/data

  mongo1:
    image: mongo:latest
    container_name: mongo1
    depends_on:
      - keyfile-generator
    restart: unless-stopped
    ports:
      - 27017:27017
    networks:
      - mongoCluster
    volumes:
      - mongo1_data:/data/db
      - mongo1_config:/data/configdb
      - keyfile_data:/etc/mongo-keyfile
    environment:
      - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME}
      - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD}
    command: >
      bash -c '
        echo "Starting MongoDB setup..."
        while [ ! -f /etc/mongo-keyfile/keyfile ]; do
          echo "Waiting for keyfile..."
          sleep 2
        done
        chmod 400 /etc/mongo-keyfile/keyfile
        mongod --replSet rs0 --bind_ip_all --keyFile /etc/mongo-keyfile/keyfile --fork --logpath /var/log/mongod.log
        for i in {1..30}; do
          if mongosh --quiet --eval "db.runCommand({ping: 1})" > /dev/null 2>&1; then
            break
          fi
          sleep 2
        done
        if ! mongosh --quiet --eval "rs.status()" > /dev/null 2>&1; then
          mongosh --eval "
            rs.initiate({
              _id: \"rs0\",
              members: [
                { _id: 0, host: \"mongo1:27017\", priority: 2 },
                { _id: 1, host: \"mongo2:27017\", priority: 1 },
                { _id: 2, host: \"mongo3:27017\", priority: 1 }
              ]
            })
          "
        fi
        for i in {1..60}; do
          if mongosh --quiet --eval "db.hello().isWritablePrimary" 2>/dev/null | grep -q true; then
            mongosh admin --quiet --eval "db.createUser({user:\"${MONGO_INITDB_ROOT_USERNAME}\",pwd:\"${MONGO_INITDB_ROOT_PASSWORD}\",roles:[{role:\"root\",db:\"admin\"}]})"
            break
          fi
          sleep 2
        done
        mongod --shutdown
        sleep 3
        exec mongod --replSet rs0 --bind_ip_all --auth --keyFile /etc/mongo-keyfile/keyfile
      '

  mongo2:
    image: mongo:latest
    container_name: mongo2
    depends_on:
      - keyfile-generator
    restart: unless-stopped
    ports:
      - 27018:27017
    networks:
      - mongoCluster
    volumes:
      - mongo2_data:/data/db
      - mongo2_config:/data/configdb
      - keyfile_data:/etc/mongo-keyfile
    command: >
      bash -c "
        chmod 400 /etc/mongo-keyfile/keyfile &&
        exec mongod --replSet rs0 --bind_ip_all --auth --keyFile /etc/mongo-keyfile/keyfile
      "

  mongo3:
    image: mongo:latest
    container_name: mongo3
    depends_on:
      - keyfile-generator
    restart: unless-stopped
    ports:
      - 27019:27017
    networks:
      - mongoCluster
    volumes:
      - mongo3_data:/data/db
      - mongo3_config:/data/configdb
      - keyfile_data:/etc/mongo-keyfile
    command: >
      bash -c "
        chmod 400 /etc/mongo-keyfile/keyfile &&
        exec mongod --replSet rs0 --bind_ip_all --auth --keyFile /etc/mongo-keyfile/keyfile
      "

  mongo-express:
    image: mongo-express:latest
    container_name: mongo-express
    depends_on:
      - mongo1
      - mongo2
      - mongo3
    restart: unless-stopped
    ports:
      - 8081:8081
    networks:
      - mongoCluster
    environment:
      - ME_CONFIG_MONGODB_ADMINUSERNAME=${MONGO_INITDB_ROOT_USERNAME}
      - ME_CONFIG_MONGODB_ADMINPASSWORD=${MONGO_INITDB_ROOT_PASSWORD}
      - ME_CONFIG_MONGODB_URL=mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongo1:27017,mongo2:27017,mongo3:27017/?replicaSet=rs0
      - ME_CONFIG_BASICAUTH_USERNAME=${MONGO_INITDB_ROOT_USERNAME}
      - ME_CONFIG_BASICAUTH_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD}

networks:
  mongoCluster:
    driver: bridge

volumes:
  mongo1_data:
  mongo1_config:
  mongo2_data:
  mongo2_config:
  mongo3_data:
  mongo3_config:
  keyfile_data:
Enter fullscreen mode Exit fullscreen mode

3 Starting the Cluster

With Podman:

podman compose up -d
Enter fullscreen mode Exit fullscreen mode

With Docker:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

On first run:

  • A secure keyfile is generated
  • The replica set (rs0) is initialized
  • A root admin user is created
  • MongoDB restarts in auth mode
  • After the first Bootup is completed you may delete the keyfile-generator Container

Connecting to the Cluster

e.g. MongoDB Compass

Direct node connections:

  • Primary: mongodb://root:pass@localhost:27017/?authSource=admin&directConnection=true
  • Secondary 1: mongodb://root:pass@localhost:27018/?authSource=admin&directConnection=true
  • Secondary 2: mongodb://root:pass@localhost:27019/?authSource=admin&directConnection=true

(Replace root / pass if changed in .env)


Mongo Express UI

Access Mongo Express at:

http://localhost:8081
Enter fullscreen mode Exit fullscreen mode

Login with your .env credentials.


Next Steps

  • Add TLS/SSL for encrypted connections
  • Implement Failover Logic between direct node connection strings in your app
  • Implement Change Streams in your app
  • Simulate node failures to test high availability

Top comments (1)

Collapse
 
c_s_9c80b1d4234319fc3c33a profile image
C S

Clean presentation and talked freely. Such wow