Deploy on Railway
Railway runs the all-in-one image in the single node and managed Postgres modes. A project has three or four resources:
| Resource | Runs |
|---|---|
golem | A service from the golemservices/golem image, with a volume at /var/lib/golem |
| Bucket | A Railway Storage Bucket for the blobs and, in mode 1, the Litestream replica |
| Postgres | Railway Postgres, in mode 2 only |
Go through the production checklist too.
What Railway decides
- Deploys. A service with a volume stops its old deployment before it starts the new one. That is what the one shard manager rule needs. Without a volume, Railway starts the new deployment and stops the old one only after the new one is active, and two servers then run on the same database for a while. Attach a volume in both modes, also in mode 2, where it holds only caches.
- Replicas. A service with a volume cannot have replicas. Run one server; it scales vertically.
- Volume size. Volumes are limited by the plan: 0.5 GB on the free and trial plans, 5 GB on Hobby and 50 GB on Pro. The trial volume is too small for real use.
- Backups. Enable a volume backup schedule in the service’s Backups tab; do not assume that daily backups are enabled. A daily schedule can lose up to a day of the SQLite state. Turn on Litestream in mode 1, or use mode 2.
- Streams. Railway’s edge closes an HTTP request after 15 minutes even while data flows, and after 5 minutes without data. Server-sent event streams, such as MCP streams, therefore end after at most 15 minutes, and clients reconnect. WebSocket connections have no limit.
- Forwarded headers. Railway’s edge sets
X-Forwarded-ProtoandX-Forwarded-Host, but it does not document the addresses it connects from, so the server cannot trust them. The server routes by theHostheader and seeshttpas the scheme. This affects the URLs it builds from the request, such as the address an OIDC login returns to.
Single node
Create the bucket
In a new Railway project, add a Bucket. Its credentials are the variables BUCKET, ACCESS_KEY_ID, SECRET_ACCESS_KEY, REGION and ENDPOINT of the bucket resource. The steps below refer to the bucket resource as Bucket; use the name of yours.
Write the variables
Run the generate-env.sh script with Railway’s variable references as values, so that the variables follow the bucket:
curl -fsSLO https://raw.githubusercontent.com/golemcloud/golem/main/docker-examples/single-node/generate-env.sh
chmod +x generate-env.sh
umask 077
GOLEM_S3_BUCKET='${{Bucket.BUCKET}}' \
GOLEM_S3_REGION='${{Bucket.REGION}}' \
GOLEM_S3_ENDPOINT='${{Bucket.ENDPOINT}}' \
./generate-env.sh > golem.env
cat >> golem.env <<'EOF'
AWS_ACCESS_KEY_ID=${{Bucket.ACCESS_KEY_ID}}
AWS_SECRET_ACCESS_KEY=${{Bucket.SECRET_ACCESS_KEY}}
GOLEM__ROUTER__MANAGEMENT_HOST=golem.example.com
GOLEM_LITESTREAM=true
GOLEM_LITESTREAM_REPLICA_URL=s3://${{Bucket.BUCKET}}/litestream?endpoint=${{Bucket.ENDPOINT}}®ion=${{Bucket.REGION}}
EOFgolem.env contains the admin token, GOLEM__REGISTRY_SERVICE__INITIAL_ACCOUNTS__ROOT__TOKEN. Keep it, and delete the file when the service runs.
Create the service
Add an empty service named golem. Before connecting the image, configure:
- Variables. Open the Raw Editor and paste
golem.env. - Volume. Attach a volume with the mount path
/var/lib/golem, and add the variableRAILWAY_RUN_UID=0. Railway documents that images that run as a non-root user, as this image does with uid 65534, have permission problems on volumes, and thatRAILWAY_RUN_UID=0fixes them. The server then runs as root. See Volume ownership. - Port. Set
PORT=9881, and leave the start command empty to use the image’s entrypoint. - Health check. Leave Railway’s healthcheck path unset for this image. Railway sends its probes with the host
healthcheck.railway.app, which does not matchGOLEM__ROUTER__MANAGEMENT_HOST. The router sends these requests to the application endpoints, not the management health check. Check/healthcheckthrough the management domain instead. Railway can send traffic before Golem is ready; during startup, the server answers requests with 503. The first start on a new volume downloads the built-in plugins and tools, which takes about a minute.
Add the domains
In the service’s networking settings, generate a Railway-provided domain or add a custom domain for the management API, with the target port 9881. Set GOLEM__ROUTER__MANAGEMENT_HOST to that host name, without https://. Add a separate domain for each HTTP API or MCP deployment, also targeting port 9881. For custom domains, Railway shows the DNS records to create and gets the certificates.
Deploy an application
Connect the service to the Docker image golemservices/golem:v1.6.0. This starts the deployment. Wait for /healthcheck on your management domain to return 200, then deploy an application with the admin token. See Connect the CLI.
To upgrade, change the image tag of the service.
To restore after the loss of the volume, remove the active deployment and wait until it has stopped. Replace the volume at /var/lib/golem without starting an intermediate deployment, then redeploy. The image restores the databases from the Litestream replica before the server starts. The new volume has no cache of the built-in plugins and tools, so that start downloads them again from GitHub.
The CLI’s volume attach and detach commands can start deployments automatically, even after the image source is disconnected. Do not detach and attach volumes as separate live changes: an intermediate container can write to the replica without the intended volume. For CLI automation, commit the volume swap in one environmentPatchCommit API call with skipDeploys: true, verify that no deployment has started, then redeploy explicitly. Set the old volume’s mountPath to null and the replacement’s mountPath to /var/lib/golem under the service’s volumeMounts in that patch.
Litestream is off by default; turn it on from the first start of a new volume. This guide turns it on because the platform’s volumes belong to one host and a daily backup can lose up to a day of state; with a reliable persistent volume, it is not needed. Litestream is backup and restore for one server, not high availability. Exactly one container may write to a replica URL: two containers on the same replica corrupt it. Replication is asynchronous, about once a second, so losing the volume can lose the newest oplog writes, and agents then replay from an earlier point and may run side effects again. Litestream replicates only the SQLite databases, so the blobs must be in S3. It does not replicate the cache of the built-in plugins and tools either, so a restore onto a new volume downloads them again and needs HTTPS connections to GitHub. At startup, the image restores only the databases that are missing from the volume, so a restart with its data restores nothing. For production durability, use the managed Postgres mode. See Litestream limits.
Managed Postgres
For a new deployment, follow the single-node steps, but add Postgres to the project before connecting the Golem image. Use the following command instead of the single-node environment-file command. Postgres.PGHOST is the database’s private host name; Golem does not need a public TCP proxy for the database.
umask 077
GOLEM_S3_BUCKET='${{Bucket.BUCKET}}' \
GOLEM_S3_REGION='${{Bucket.REGION}}' \
GOLEM_S3_ENDPOINT='${{Bucket.ENDPOINT}}' \
GOLEM_POSTGRES_HOST='${{Postgres.PGHOST}}' \
GOLEM_POSTGRES_PORT='${{Postgres.PGPORT}}' \
GOLEM_POSTGRES_DB='${{Postgres.PGDATABASE}}' \
GOLEM_POSTGRES_USER='${{Postgres.PGUSER}}' \
GOLEM_POSTGRES_PASSWORD='${{Postgres.PGPASSWORD}}' \
./generate-env.sh > golem.env
cat >> golem.env <<'EOF'
AWS_ACCESS_KEY_ID=${{Bucket.ACCESS_KEY_ID}}
AWS_SECRET_ACCESS_KEY=${{Bucket.SECRET_ACCESS_KEY}}
GOLEM__ROUTER__MANAGEMENT_HOST=golem.example.com
EOFSet the management host to your generated domain, and configure PORT=9881 and RAILWAY_RUN_UID=0 as in the single-node steps. Leave out the Litestream variables: the state is in Postgres. The script configures all six stores, including the gateway session store, so no Redis service is needed. Changing these variables on a SQLite deployment does not copy its state to Postgres.
Keep the volume at /var/lib/golem: it makes Railway stop the old deployment before it starts the new one. The volume holds caches, not the authoritative state. If it is lost, stop the Golem deployment, replace the volume without an intermediate deployment, and redeploy with the same Postgres, bucket and secrets. The server downloads the built-in artifacts again and recovers the application and agent state from Postgres and the bucket. Do not replace or delete the Postgres volume during this procedure.
For backups of the database, see Railway’s point-in-time recovery . A server or database restart is not a backup restore test.
Test a Postgres backup restore
- Enable point-in-time recovery in the Postgres service’s Backups tab. Railway creates a separate archive bucket and redeploys Postgres. Wait for the first base backup to finish. The restore window starts with that backup, not when the database was created.
- Create known agent state and record a UTC recovery time. After that time, change the state again. Keep both expected states so that the test can distinguish recovery to the selected time from recovery to the latest state.
- Restore to the selected time in the Backups tab. Railway creates a separate Postgres service and volume; it does not replace the source database. Wait for the restored database to finish recovery and accept connections.
- Stop the Golem deployment and verify that it has stopped. Prevent automatic deployments while changing its variables. Point all six stores at the restored Postgres service, including the shard-manager, indexed and gateway session stores. Update their host, port, database, user and password references together. Keep the Golem volume, blob bucket and secrets unchanged; do not generate new secrets for this test.
- Redeploy Golem and wait for its management health check. Read the agent state and compare it with the state at the recovery time. Verify that the later changes are absent, and make a new invocation that writes state. Restart the restored database and Golem, and verify that this new write is still present.
- For a test, stop Golem again, restore all six stores’ original database references, and redeploy. Verify the source state before deleting the temporary database and its volume. For a real cutover, keep the restored database and enable PITR on it: Railway restores a database without archiving enabled.
Restore the stores together so that the shard-manager epochs and indexed-storage fencing records come from the same point in time. Do not clear fencing records to make an agent writable; see storage recovery precautions. Keep the blobs required by the restored database. Database recovery does not undo external side effects.
Fixed cluster
A fixed cluster on Railway needs verification. The shard manager records the source address of each executor’s registration, and Railway does not document whether its private network keeps that address. Railway environments created before 16 October 2025 have an IPv6-only private network, which needs GOLEM__BIND_ADDRESS=:: on every service.