A production-ready PostgreSQL backup service designed for Railway.app deployments with support for S3 and Google Cloud Storage.
- Multi-Storage Support: Back up to Amazon S3 or Google Cloud Storage
- Respawn Protection: Prevents frequent backups from container restarts
- Railway Integration: Works seamlessly with Railway's cron feature
- Monitoring: Prometheus metrics and health check endpoints
- Production Ready: Retry logic, graceful shutdown, panic recovery
- Cold Start Support: Automatic retries with exponential backoff for database connections
- Flexible Configuration: Environment variable based configuration
- Backup Management: Automatic cleanup of old backups based on retention policy
- PostgreSQL Version Support: Automatically detects and uses the correct pg_dump version for PostgreSQL 15, 16, and 17
-
Deploy to Railway using the template:
-
Configure environment variables (see Configuration section)
-
The service comes pre-configured with a daily 3 AM UTC backup schedule. To modify, go to Settings β Cron Schedule
docker run -e DATABASE_URL=postgres://... \
-e STORAGE_PROVIDER=S3 \
-e S3_BUCKET=my-bucket \
-e AWS_ACCESS_KEY_ID=... \
-e AWS_SECRET_ACCESS_KEY=... \
ghcr.io/imedwei/railway-postgres-backup:latest| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string |
STORAGE_PROVIDER |
Storage backend: S3 or GCS |
| Variable | Description | Required |
|---|---|---|
S3_BUCKET |
S3 bucket name | Yes |
AWS_ACCESS_KEY_ID |
AWS access key | Yes |
AWS_SECRET_ACCESS_KEY |
AWS secret key | Yes |
AWS_REGION |
AWS region | No (default: us-east-1) |
S3_ENDPOINT |
Custom S3 endpoint | No |
S3_PATH_STYLE |
Use path-style URLs | No (default: false) |
S3_PREFIX |
Key prefix for backups | No |
| Variable | Description | Required |
|---|---|---|
GCS_BUCKET |
GCS bucket name | Yes |
GOOGLE_PROJECT_ID |
GCP project ID | Yes |
GOOGLE_SERVICE_ACCOUNT_JSON |
Service account JSON | Yes |
GCS_PREFIX |
Object prefix for backups | No |
| Variable | Description | Default |
|---|---|---|
BACKUP_FILE_PREFIX |
Prefix for backup filenames | backup |
PG_DUMP_OPTIONS |
Additional pg_dump options | |
RESPAWN_PROTECTION_HOURS |
Minimum hours between backups | 23 |
FORCE_BACKUP |
Skip respawn protection | false |
RETENTION_DAYS |
Days to keep old backups | 0 (disabled) |
The service includes automatic retry logic for database connections to handle cold-start scenarios (e.g., when the database is still starting up).
| Variable | Description | Default |
|---|---|---|
DB_RETRY_MAX_ATTEMPTS |
Maximum number of connection retry attempts | 10 |
DB_RETRY_INITIAL_DELAY |
Initial delay between retries (seconds) | 2 |
DB_RETRY_MAX_DELAY |
Maximum delay between retries (seconds) | 60 |
DB_RETRY_BACKOFF_FACTOR |
Exponential backoff factor | 2.0 |
PSQL_RETRY_MAX_ATTEMPTS |
Maximum retries for psql commands | 5 |
PSQL_RETRY_INITIAL_DELAY |
Initial delay for psql retries (seconds) | 2 |
PSQL_RETRY_MAX_DELAY |
Maximum delay for psql retries (seconds) | 30 |
HEALTH_CHECK_RETRY_MAX_ATTEMPTS |
Maximum retries for health checks | 3 |
HEALTH_CHECK_RETRY_INITIAL_DELAY |
Initial delay for health check retries (seconds) | 1 |
HEALTH_CHECK_RETRY_MAX_DELAY |
Maximum delay for health check retries (seconds) | 5 |
| Variable | Description | Default |
|---|---|---|
METRICS_PORT |
Port for metrics/health endpoints | (disabled) |
When METRICS_PORT is set, the following endpoints are available:
/metrics- Prometheus metrics/health- Health check with detailed status/ready- Readiness probe/live- Liveness probe
postgres_backup_attempts_total- Total backup attemptspostgres_backup_duration_seconds- Backup duration by phasepostgres_backup_size_bytes- Size of last backuppostgres_database_size_bytes- Current database sizepostgres_backup_storage_operations_total- Storage operationspostgres_backup_rate_limit_blocked_total- Rate limited backupspostgres_backup_last_success_timestamp- Last successful backup time
The service includes respawn protection to prevent excessive backups when Railway restarts containers. By default, backups are only allowed once every 23 hours. This can be configured with RESPAWN_PROTECTION_HOURS or overridden with FORCE_BACKUP=true.
The service automatically detects your PostgreSQL server version and uses the appropriate pg_dump client:
- PostgreSQL 17: Uses
pg_dump17 - PostgreSQL 16: Uses
pg_dump16 - PostgreSQL 15: Uses
pg_dump15 - PostgreSQL < 15: Uses
pg_dump15(backward compatible)
This ensures maximum compatibility and prevents version mismatch errors during backups.
- Go 1.24.3+
- Task (taskfile.dev)
- PostgreSQL client tools (pg_dump)
- AWS CLI (for S3 testing)
- gcloud CLI (for GCS testing)
# Clone the repository
git clone https://github.com/imedwei/railway-postgres-backup
cd railway-postgres-backup
# Install dependencies
go mod download
# Run tests
task test
# Build
task build
# Run locally
export DATABASE_URL=postgres://localhost/mydb
export STORAGE_PROVIDER=S3
export S3_BUCKET=my-backup-bucket
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
./bin/backup# Run all tests
task test
# Run tests with coverage
task test:coverage
# Run specific package tests
go test ./internal/storage/....
βββ cmd/backup/ # Main application entry point
βββ internal/
β βββ backup/ # Backup orchestration and PostgreSQL
β βββ config/ # Configuration management
β βββ health/ # Health check implementation
β βββ metrics/ # Prometheus metrics
β βββ ratelimit/ # Respawn protection
β βββ server/ # HTTP server for metrics
β βββ storage/ # Storage backends (S3, GCS)
β βββ utils/ # Utility functions
βββ Dockerfile # Multi-stage Docker build
βββ Taskfile.yml # Task automation
βββ go.mod # Go module definition
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
task test) - Format your code (
task fmt) - Commit your changes (small, focused commits)
- Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - see LICENSE file for details
- Based on railwayapp-templates/postgres-s3-backups
- Built for Railway.app deployments