MongoDB as a single-node replica set

How to convert the production MongoDB from a standalone server to a single-node replica set, which publishing a template version needs, and how to verify and roll it back.

Publishing a template version commits the new version and every one of its processes in one MongoDB multi-document transaction (template versions). MongoDB runs transactions only on a replica set. A single-node replica set is the same one server with an operation log (oplog) turned on: no second machine, no change to the data files. On a standalone, the release step stops before doing anything with: "Template publication needs MongoDB multi-document transactions, which need a replica set; this MongoDB is a standalone."

Do this as a planned operations step before the first deploy that contains the release step. (Production soter-os was converted on 2026-10-01: rs0, key file /data/db/replica.key, replicaSet=rs0 on the MongoDB service's MONGO_URL, which every app service references.) It needs a short MongoDB restart; every service reconnects on its own.

Before you start

  1. Check what it is now, from a shell that can reach the database (mongosh "$MONGO_URL"):

    db.hello().setName          // undefined on a standalone; "rs0" once converted
    db.version()
    
  2. Take a backup and confirm it restores somewhere else:

    mongodump --uri "$MONGO_URL" --archive=pre-replset.archive --gzip
    
  3. Decide the member's host name: the address every client uses to reach MongoDB. On Railway that is the service's private domain and port, for example mongodb.railway.internal:27017. A replica-set client discovers the member by the name in the replica-set configuration and connects to that name, so it must resolve for every service.

Convert

  1. Access control needs a key file. If MongoDB runs with users (the Railway MongoDB template sets a root user), a replica set requires a key file even with one member. Create it once on the database volume:

    openssl rand -base64 756 > /data/db/replica.key
    chmod 400 /data/db/replica.key
    

    Make sure the file is owned by the user mongod runs as (mongodb in the official image).

  2. Restart mongod as a replica-set member. Change the MongoDB service's start command to add the replica-set name (and the key file when access control is on), keeping every existing option:

    mongod --replSet rs0 --bind_ip_all --keyFile /data/db/replica.key
    
  3. Initiate the set once, connected as the root user:

    rs.initiate({ _id: "rs0", members: [{ _id: 0, host: "mongodb.railway.internal:27017" }] })
    rs.status().members[0].stateStr   // "PRIMARY" within a few seconds
    
  4. Point the services at the set. Add replicaSet=rs0 to MONGO_URL on every service that connects to MongoDB: the process-platform web service, each other web service, and the step worker (and any operator shell). For example mongodb://user:pass@mongodb.railway.internal:27017/?authSource=admin&replicaSet=rs0. A client that reaches MongoDB through a proxy whose address is not the member's host name (an operator laptop through a TCP proxy, say) uses directConnection=true instead of replicaSet=rs0; transactions work on a direct connection to the primary.

  5. Redeploy or restart each service so it reads the new MONGO_URL.

Verify

db.hello().setName                 // "rs0"
db.hello().isWritablePrimary       // true
rs.conf().members.map(m => m.host) // the host name every service uses

Then, from inside the process-platform service (so it uses that service's MONGO_URL), run the release step's dry run. It reads only, and reports what the next deploy would publish:

npm run templates:publish -- --dry-run

Finally confirm each service is healthy (web: GET /health returns 200) and that the worker logs no connection errors.

Roll back

The conversion does not change the data files, so rolling back is a restart:

  1. Remove replicaSet=rs0 (or directConnection=true) from every service's MONGO_URL.
  2. Restart mongod with its original start command (without --replSet and --keyFile). It runs as a standalone again; the local database that holds the oplog is ignored.
  3. Restart the services.

After a rollback the release step cannot publish a template version: a deploy that would publish one fails in its release step, and the previous deployment keeps serving. If the data itself is suspect, restore the backup taken before the conversion.


Known gaps

  • This is a single node: it gives transactions, not redundancy or failover. Adding members later is the usual rs.add().
  • The exact Railway start-command and volume layout depend on how the MongoDB service was created; check them in the service settings before changing anything.