SafeGrd / Railway backup

How to back up a Railway Postgres database, and keep the copy outside the project

Railway gives a Postgres service two kinds of recovery, volume backups and point-in-time recovery, and both restore into the same project. Railway's own guide says only the offsite logical dumps survive a deleted project or volume. This page is that dump: how to reach the database from inside and outside the project, the command, where the file goes, the restore and the check. The last section is how SafeGrd runs it, as a service in the project or with no service at all.

What Railway's backups cover

From Railway's Postgres backups guide, read on 2026-10-08:

KindSchedule and retentionRestores to
Volume backup, dailyEvery 24 hours, kept 6 daysThe same service, as a new volume, by Deploy
Volume backup, weeklyEvery 7 days, kept 1 monthThe same service
Volume backup, monthlyEvery 30 days, kept 3 monthsThe same service
Point-in-time recoveryWAL archived with pgBackRest to a Postgres-PITR bucket; a full base backup every week and a differential one every day, the last 4 full keptA new sibling service, <source>-restored-...

1. Reach the database

A Railway database is private by default. There are two ways in, and which you use decides what the dump costs:

Railway's Postgres runs its SSL-enabled image; add ?sslmode=require to a public string. The default user is the one Railway created; make a read-only one for the backup so a leaked credential cannot write:

CREATE ROLE backup LOGIN PASSWORD 'a long random password';
GRANT pg_read_all_data TO backup;

2. Dump it

pg_dump --format=custom --no-owner --file=railway.dump "$DATABASE_URL"

Railway's guide uses the same flags: custom format because it is compressed and lets pg_restore run selectively and in parallel, --no-owner on both the dump and the restore. The client has to be at least the server's major version; Railway builds from the official postgres image, so check SELECT version() and install that postgresql-client.

3. Off the platform, encrypted, on a schedule

Encrypt the dump on the way out and write it to a bucket in another account, where a deleted project cannot reach it:

set -o pipefail
pg_dump -Fc --no-owner "$DATABASE_URL" | age -r "$AGE_RECIPIENT" | aws s3 cp - "s3://$BUCKET/railway/$(date -u +%F).dump.age"

Two places to run it:

set -o pipefail makes a failed dump fail the job instead of uploading a truncated file. The bucket policy, a key that cannot delete and the lock on the bucket are in the S3 guide and the Object Lock guide.

4. Restore, and check it

Railway's guide has the right habit: restore into a scratch database named restore_drill, check the row counts and the newest rows, record how long it took and how old the dump was, then drop it. Those two numbers are your real recovery time and recovery point.

aws s3 cp "s3://$BUCKET/railway/2026-10-08.dump.age" - | age -d -i backup-key.txt > railway.dump
psql "$DATABASE_URL" -c "CREATE DATABASE restore_drill"
pg_restore --exit-on-error --no-owner --dbname "${DATABASE_URL%/*}/restore_drill" railway.dump
psql "${DATABASE_URL%/*}/restore_drill" -c "SELECT relname, n_live_tup FROM pg_stat_user_tables ORDER BY 1"

To put a project back after it is gone: a new Postgres service on Railway, or any PostgreSQL of the same or a newer major version, an empty database, and the same pg_restore. How to test that a backup restores has the exact per-table count inside one snapshot, for when an estimate is not enough.

How SafeGrd does it

SafeGrd runs the daemon as a service in the project, where it reads the database over private networking with no egress for the read, or takes the backup from a GitHub Actions job through the TCP proxy. Its scheduled drill is the restore_drill Railway's guide asks you to run, with the row counts checked and the result signed. SafeGrd does the same job on every platform; the PostgreSQL page has what it dumps, how it encrypts, and what a drill checks.

Run your first Fire Drill Read the install guide

As a service in the project

  1. Add a service from the image ghcr.io/safegrd/cli, with a volume mounted at /home/safegrd/.safegrd so the host's key and config survive a redeploy, and the database's DATABASE_URL as a variable.
  2. Open a shell on it once (the Railway CLI's railway ssh) and run safegrd enroll, which signs the host in and registers it. Then add the database as a surface in the SafeGrd console, with the read-only role.
  3. The image's default command is safegrd daemon run, which backs up and drills on the surface's schedule. A sandbox drill runs on the PostgreSQL server the image carries, so the restore test never touches the live database.

With no service

The GitHub Actions setup in the Supabase guide works for Railway with DATABASE_PUBLIC_URL in the secret. Or, under Surfaces in the console, Back up on SafeGrd takes the public string; a machine SafeGrd starts for each backup dumps and encrypts it, then is destroyed, and the string is sealed and given only to that machine (how). Both read through the TCP proxy, so Railway bills the read as egress; a backup that uploads only what changed still reads the whole database.

Questions

Should I turn off Railway's backups?

No. Volume backups and PITR are the fast undo inside the project, and they cost little. The dump outside the project is for when the project, the volume or the account is the thing that was lost.

Does the daemon work on an HA cluster?

Yes. It reads from whichever node the connection string resolves to; point it at a replica to keep the read off the primary.

Related