Deploy Postgres S3 Backups with Cleanup
PostgreSQL backups, configurable cleanup and S3 storage with tracing
Backup Cleanup
Just deployed
Backup CRON
Just deployed
Bucket
Bucket
Just deployed
Deploy and Host Postgres S3 Backups with Cleanup on Railway
Back up an existing PostgreSQL database to S3-compatible storage and manage retention with two independent Go applications. This template includes a streaming backup service, a cleanup service, and a dedicated Railway bucket. Backup operations include OpenTelemetry tracing.
About Hosting Postgres S3 Backups with Cleanup
Three resources are included:
- Backup CRON: streams a PostgreSQL dump through parallel gzip compression directly to concurrent S3 multipart uploads.
- Backup Cleanup: previews or deletes expired backups while preserving a configurable minimum number.
- Bucket: dedicated S3-compatible storage connected to both applications through generated reference variables.
Both applications live in one Go module but build independently. Each has its own entrypoint, binary, Dockerfile, runtime configuration and cron schedule. Their Dockerfiles copy only the corresponding application's source.
Deploy into the Railway project containing your existing PostgreSQL service and provide BACKUP_DATABASE_URL, for example ${{Postgres.DATABASE_URL}}. No database is created by the template. External databases must be reachable from the backup service.
Common Use Cases
- Scheduled logical PostgreSQL backups to object storage.
- Configurable age-based retention with minimum backup preservation.
- Backup tracing and dry-run cleanup previews.
Dependencies for Postgres S3 Backups with Cleanup Hosting
- An existing PostgreSQL database and connection URL.
- The included Railway bucket, or external S3-compatible storage configured on both services.
- Source repository.
Independent builds
Both services build from repository root /:
- Backup uses
RAILWAY_DOCKERFILE_PATH=docker/backup.Dockerfile. - Cleanup uses
RAILWAY_DOCKERFILE_PATH=docker/cleanup.Dockerfile.
Both run once and exit. Native Railway Cron controls when each starts. Neither service needs the other application running.
Important: cleanup does not delete backups by default.
DRY_RUN=trueonly logs which backups would be deleted. To enable actual deletion, open Backup Cleanup → Variables, setDRY_RUN=false, and apply/deploy the change. Subsequent runs can then delete eligible backups, including any run started by deployment. The cron schedule remains unchanged.
Configuration
All defaults can be changed. Cron schedules use UTC and are edited under each service's Settings → Cron Schedule.
| Setting | Default |
|---|---|
| Backup schedule | 0 2 1 * *: monthly, day 1 at 02:00 UTC |
| Cleanup schedule | 0 3 1 1,7 *: January/July, day 1 at 03:00 UTC |
RETENTION_DAYS | 183 days |
KEEP_MIN | Preserve at least 1 newest backup |
DRY_RUN | true: preview only |
PG_VERSION | 18; must be at least your PostgreSQL server major version |
RUN_ON_STARTUP | true: run the backup once and exit |
GZIP_CONCURRENCY | 20 compression workers |
UPLOAD_CONCURRENCY | 20 upload workers |
More compression/upload workers can improve throughput but increase memory use. Blocks and upload parts are each 5 MiB.
Edit retention values under Backup Cleanup → Variables. Review its logs, then set DRY_RUN=false to enable deletion. Cleanup inventories every page before deleting and preserves the newest KEEP_MIN objects, even when expired. A listing failure stops the run before any deletion. Keep the bucket dedicated to backups.
For external S3, configure endpoint, bucket, region and credentials on both services.
Tracing and validation
Enable Railway tracing on Backup CRON after deployment. The Go SDK reads platform-provided OTLP variables and service identity, emits backup.run, PostgreSQL pg_dump CLIENT spans and S3 operation spans, and flushes before exit. Credentials, connection URLs, bucket names and object keys are excluded from span attributes/events. Leave automatic instrumentation off. Cleanup writes structured Railway logs.
The repository includes tests for streaming integrity, multipart failures, cancellation, bounded buffering, tracing, retention boundaries, pagination, dry-run and configuration validation. All checks run from the repository root.
Regularly test restoring backups. Logical backups do not provide continuous point-in-time recovery. Storage accumulates until deletion is enabled and a cleanup run occurs.
Why Deploy Postgres S3 Backups with Cleanup on Railway?
Railway schedules both independent applications and provisions their storage. Generated reference variables connect the services to the backup bucket without storing credentials in source control.
Thanks to Brody and the original Railway template for the foundation of this project.
Template Content
Backup Cleanup
maxstue/postgres-s3-backups-cleanupBackup CRON
maxstue/postgres-s3-backups-cleanupBACKUP_DATABASE_URL
Required PostgreSQL connection URL; use the existing database service DATABASE_URL reference.
Bucket
Bucket