SafeGrd / Fly.io Postgres backup

How to back up a Postgres database on Fly.io, and keep a file you hold

Fly.io has two kinds of Postgres. Managed Postgres (MPG) is the current one, a cluster inside your organization's private network with automatic backups, a connection pooler and an API. Fly Postgres, the older app you ran yourself, is deprecated in favour of it. Neither gives you a file outside Fly. This page is that file: how to reach the database from inside the network and from your laptop, the command, where the copy goes, the restore and the check. The last section is how SafeGrd runs it as a Machine beside the database.

What Fly's backups cover

From Fly's documentation, read on 2026-10-08:

1. Reach the database

An MPG cluster has two hostnames on the private network. Fly's client page says SSL is enabled by default on every MPG connection, so no flag is needed:

# through PgBouncer: what fly mpg attach gives your app
postgresql://fly-user:PASSWORD@pgbouncer.CLUSTER.flympg.net/fly-db
# direct: the one for pg_dump
postgresql://fly-user:PASSWORD@direct.CLUSTER.flympg.net/fly-db

Make a role for the backup that can read and cannot write, so a leaked credential changes nothing:

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=fly.dump "postgresql://backup:PASSWORD@direct.CLUSTER.flympg.net/fly-db"

The client has to be at least the server's major version; SELECT version() says which. --no-owner keeps fly-user out of the dump, so it restores anywhere.

3. Off Fly, encrypted, on a schedule

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

The natural place to run it is a small Machine in the same organization, on a schedule, with the direct string in a secret. Tigris, the object storage Fly offers, is S3-compatible and in the same network; a bucket at another provider is the copy that does not share Fly's account. 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

Into a new MPG cluster: through fly mpg proxy, as Fly's import guide does. Into anything else: an empty database on any PostgreSQL of the same or a newer major version. Stop at the first error:

aws s3 cp "s3://$BUCKET/fly/2026-10-08.dump.age" - | age -d -i backup-key.txt > fly.dump
pg_restore --exit-on-error --no-owner --dbname "postgres://fly-user:PASSWORD@localhost:16380/restored" fly.dump

Then compare row counts per table with the source, as How to test that a backup restores shows. Fly's own restore makes a new cluster from Fly's backup and is the right tool for a quick undo; it does not test the file you hold.

How SafeGrd does it

SafeGrd runs as a Machine in your organization, from the ghcr.io/safegrd/cli image, with a volume for its key and config. It resolves the cluster's direct hostname on the private network, so the read costs no egress, and a sandbox drill runs on the PostgreSQL server the image carries. 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

  1. fly launch --image ghcr.io/safegrd/cli --no-deploy in an empty directory makes the app; add a volume (fly volumes create safegrd) mounted at /home/safegrd/.safegrd in fly.toml, and the direct connection string as a secret.
  2. Deploy, then fly ssh console and run safegrd enroll once, which signs the host in and registers it. 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. Set the Machine to stay running; the daemon's own clock decides when a backup is due.

A database with a public hostname, an unmanaged Fly Postgres you exposed, works from a GitHub Actions workflow too. An MPG cluster has no public hostname, so the Machine is the host.

Questions

Does this replace Fly's backups?

No. Fly's own restore is the quick way to a new cluster from a recent state. The dump outside Fly is the copy you can keep as long as you like, restore anywhere, and hand to someone.

What does the Machine cost?

Fly bills a Machine per second it runs. The backup reads over the private network, so there is no egress for the read; a sandbox drill needs memory for the database it loads, so size the Machine for the largest surface it drills.

Related