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.
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()
Take a backup and confirm it restores somewhere else:
mongodump --uri "$MONGO_URL" --archive=pre-replset.archive --gzip
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.
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).
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
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
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.
Redeploy or restart each service so it reads the new MONGO_URL.
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.
The conversion does not change the data files, so rolling back is a restart:
replicaSet=rs0 (or directConnection=true) from every service's MONGO_URL.--replSet and --keyFile). It runs as a standalone again; the local database that holds the oplog is ignored.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.
rs.add().