Restore the Fly Postgres database from a pg_dump backup after corruption, data loss, or for testing.
- Accidental data deletion or corruption
- Need to recover to a known-good state
- Testing backup/restore as part of operational readiness
- Refreshing preview data
psql,pg_dump,pg_restoreinstalled locally- Fly CLI authenticated (
fly auth login) - A pg_dump backup file (
.dumpin custom format)
Database restoration will cause data loss for any changes made after the backup was taken. For production, coordinate with stakeholders and consider putting the site in read-only mode first.
Source your .env file (copy from .env.sample if needed):
set -a; source .env; set +aThen set the variables for the target environment. For production:
PG_PASSWORD="$PG_PROD_PASSWORD"
PG_USERNAME="$PG_PROD_USERNAME"
PG_DB_APP="$PG_PROD_DB_APP"
PG_DBNAME="$PG_PROD_DBNAME"For preview:
PG_PASSWORD="$PG_PREVIEW_PASSWORD"
PG_USERNAME="$PG_PREVIEW_USERNAME"
PG_DB_APP="$PG_PREVIEW_DB_APP"
PG_DBNAME="$PG_PREVIEW_DBNAME"Run the proxy in the background:
fly proxy 15432:5432 -a "$PG_DB_APP" &
PROXY_PID=$!
sleep 2Verify it's working:
PGPASSWORD="$PG_PASSWORD" psql -h localhost -p 15432 -U "$PG_USERNAME" -d postgres -c "SELECT 1"When done with all steps, stop the proxy:
kill $PROXY_PIDTODO: Automated backup infrastructure (pg_dump cron to S3) is not yet in place. For now, backups must be created manually before they are needed.
If the database is still accessible, dump it before proceeding:
PGPASSWORD="$PG_PASSWORD" pg_dump \
--format=custom --no-owner --no-acl \
-h localhost -p 15432 \
-U "$PG_USERNAME" "$PG_DBNAME" \
> /tmp/gallformers-backup.dumpIf the database is in a bad state, this may not be possible — proceed directly to restore from whatever backup is available.
TODO: Once automated backups are in place, document the S3 path and how to list/download available backups here.
The --clean flag on pg_restore doesn't work reliably with Fly Postgres due to extension and schema ownership conflicts. Drop and recreate instead.
PGPASSWORD="$PG_PASSWORD" psql \
-h localhost -p 15432 \
-U "$PG_USERNAME" -d postgres <<SQL
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = '$PG_DBNAME' AND pid <> pg_backend_pid();
DROP DATABASE $PG_DBNAME;
CREATE DATABASE $PG_DBNAME OWNER $PG_USERNAME;
SQLPGPASSWORD="$PG_PASSWORD" pg_restore \
-h localhost -p 15432 \
-U "$PG_USERNAME" -d "$PG_DBNAME" \
--no-owner --no-acl \
/tmp/gallformers-backup.dumpPGPASSWORD="$PG_PASSWORD" psql \
-h localhost -p 15432 \
-U "$PG_USERNAME" -d "$PG_DBNAME" \
-c "SELECT count(*) FROM species; SELECT count(*) FROM source; SELECT count(*) FROM image; SELECT count(*) FROM gall_traits;"Browse the site and check:
- Species pages load with correct data
- Search returns results
- Maps render
- Admin pages work (if not in read-only mode)
kill $PROXY_PID- Check
pg_restoreoutput for errors — some non-fatal warnings about extensions are normal - Try an older backup if available
- Rollback: see Rollback Deployment
- Document the incident and data loss window
- Notify affected users if data was lost
- Investigate root cause
- Verify the site is functioning correctly