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
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:
3 Starting the Cluster
With Podman:
podman compose up -d
With Docker:
docker compose up -d
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
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