Skip to Content
V4.1.0Database Redundancy

Database Redundancy

High availability for SAMURAI Networks’ database with a clustered MongoDB replica set.

SAMURAI Networks stores all of its data in MongoDB. The bundled image runs a single embedded mongod, which is fine for evaluation, but production deployments should point SAMURAI Networks at an external replica set for high availability and durability. SAMURAI Networks connects to it through the MONGODB_URL you set at deploy time (see Deployment).

What a replica set is

A replica set is a group of mongod instances that hold the same data set:

  • One primary receives all writes.
  • Two or more secondaries replicate the primary’s oplog and stay in sync.
  • If the primary fails, the remaining members elect a new primary automatically, usually within seconds, and the application reconnects without manual intervention.

Run an odd number of voting members (three is the common minimum) so a majority can always be reached to elect a primary. Three full data-bearing members is the recommended baseline; a 2 + 1 layout (two data members plus a lightweight arbiter) also works but keeps only one extra copy of the data.

A replica set provides high availability (failover and redundancy). It is not the same as a sharded cluster, which spreads one data set across many shards for horizontal scale. SAMURAI Networks needs a replica set, not sharding.

Set up a three-node replica set

The example brings up a three-member set (rs0) with authentication enabled using Docker Compose on one host. For a native deployment with each member on its own machine, see On independent VMs below.

Create an internal-auth keyfile

Members authenticate to each other with a shared keyfile. Generate one and restrict its permissions:

openssl rand -base64 756 > mongo-keyfile chmod 400 mongo-keyfile

Start the members

# docker-compose.yml services: mongo1: image: mongo:8 command: ["--replSet", "rs0", "--keyFile", "/etc/mongo/keyfile", "--bind_ip_all"] volumes: - ./mongo-keyfile:/etc/mongo/keyfile:ro - mongo1-data:/data/db networks: [samurai-db] mongo2: image: mongo:8 command: ["--replSet", "rs0", "--keyFile", "/etc/mongo/keyfile", "--bind_ip_all"] volumes: - ./mongo-keyfile:/etc/mongo/keyfile:ro - mongo2-data:/data/db networks: [samurai-db] mongo3: image: mongo:8 command: ["--replSet", "rs0", "--keyFile", "/etc/mongo/keyfile", "--bind_ip_all"] volumes: - ./mongo-keyfile:/etc/mongo/keyfile:ro - mongo3-data:/data/db networks: [samurai-db] networks: samurai-db: name: samurai-db volumes: mongo1-data: mongo2-data: mongo3-data:
docker compose up -d

Why no published ports? Each container has its own network namespace and IP, so all three mongod processes listen on 27017 at the same time without colliding. A port clash only happens when you map the same host port (for example -p 27017:27017) to more than one container. Here the members reach each other over the samurai-db Docker network by service name (mongo1:27017, mongo2:27017, mongo3:27017), and SAMURAI Networks joins the same network, so no host ports are published. Run admin commands with docker compose exec.

⚠️

Enabling a keyfile also turns on access control. Initialize the set and create the first user in the same session through MongoDB’s localhost exception, before any other user exists.

Initialize the set

docker compose exec mongo1 mongosh --eval ' rs.initiate({ _id: "rs0", members: [ { _id: 0, host: "mongo1:27017" }, { _id: 1, host: "mongo2:27017" }, { _id: 2, host: "mongo3:27017" } ] })'

Use real hostnames or IPs (reachable from SAMURAI Networks and between members) instead of the Compose service names in a multi-host deployment.

Create the users

docker compose exec mongo1 mongosh --eval ' admin = db.getSiblingDB("admin"); admin.createUser({ user: "admin", pwd: "CHANGE_ME", roles: ["root"] }); admin.auth("admin", "CHANGE_ME"); admin.createUser({ user: "samurai", pwd: "CHANGE_ME_TOO", roles: [{ role: "readWrite", db: "samurai" }] })'

On independent VMs

The Compose setup runs all three members on one host, which is convenient but shares a single failure domain. In production, run each member on its own VM with MongoDB installed natively. The replica-set logic is identical; only how you provision and start the members changes.

Install MongoDB on each VM (Ubuntu)

Install MongoDB 8.0 from the official APT repository on all three VMs:

sudo apt-get install -y gnupg curl curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc \ | sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/8.0 multiverse" \ | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list sudo apt-get update sudo apt-get install -y mongodb-org

The repository codename above is for Ubuntu 22.04 (jammy); use noble for 24.04. See Install on Ubuntu for other releases.

Distribute the keyfile

Create the keyfile once (as above) and copy it to the same path on every VM, owned by the MongoDB service user mongodb with 400 permissions:

sudo install -o mongodb -g mongodb -m 400 mongo-keyfile /etc/mongod.keyfile

Configure each member

Edit /etc/mongod.conf on every VM so it joins rs0, enables internal auth, and listens on an address the other members and SAMURAI Networks can reach:

net: port: 27017 bindIp: 0.0.0.0 # or the VM's private IP / a comma-separated list security: keyFile: /etc/mongod.keyfile replication: replSetName: rs0

Allow TCP 27017 between the three VMs and inbound from the SAMURAI Networks host, then enable on boot and (re)start MongoDB on each VM so it picks up the config:

sudo systemctl enable mongod sudo systemctl restart mongod

Initialize and create users

From the first VM, run mongosh and issue the same rs.initiate(...) and user creation shown above, but use each VM’s hostname or IP for the members:

rs.initiate({ _id: "rs0", members: [ { _id: 0, host: "db1.example.com:27017" }, { _id: 1, host: "db2.example.com:27017" }, { _id: 2, host: "db3.example.com:27017" } ] })

Point SAMURAI Networks at the replica set

Set MONGODB_URL to a connection string that lists every member and the replica-set name, plus MONGODB_DATABASE:

docker run -d --name samurai --network samurai-db -p 80:80 -p 443:443 \ -v samurai-data:/app/mongo-data \ -e MONGODB_URL="mongodb://samurai:CHANGE_ME_TOO@mongo1:27017,mongo2:27017,mongo3:27017/samurai?replicaSet=rs0&authSource=admin" \ -e MONGODB_DATABASE="samurai" \ beyrak44/samurai:latest
  • --network samurai-db joins SAMURAI Networks to the same Docker network as the members, so the mongoN service names in the URL resolve. On independent VMs, omit --network and use the VM hostnames or IPs in the URL instead.
  • List all members in the URL so the driver can find the primary after a failover.
  • replicaSet=rs0 tells the driver to discover the set and follow primary elections.
  • authSource=admin matches where the samurai user was created.
  • The embedded mongod volume (-v samurai-data:/app/mongo-data) is not used for data once an external URL is reachable, but leaving it mounted is harmless.

SAMURAI Networks is external-first: when MONGODB_URL is set and reachable it uses the replica set; if it is set but unreachable at startup it falls back to the embedded mongod (see Deployment). Confirm the application logs show it connected to your set.

Use TLS and a private network between SAMURAI Networks and the database in production. Keep the keyfile and user passwords in a secret store, not in the Compose file or shell history.

Verify failover

Stop the current primary and confirm a secondary is elected and SAMURAI Networks keeps working:

docker compose exec mongo1 mongosh --eval 'rs.status().members.map(m => ({ name: m.name, state: m.stateStr }))' docker compose stop mongo1 # the current primary # re-run rs.status() against mongo2; a new PRIMARY should appear within seconds

Resources

Last updated on