A serverless backup solution for PostgreSQL databases using AWS Lambda, with automatic daily, monthly, and yearly backup rotation to S3.
- ✅ Automated daily backups of your PostgreSQL database
- ✅ Intelligent backup rotation (daily, monthly, yearly)
- ✅ Daily backups retained for 7 days
- ✅ Monthly backups automatically transitioned to Glacier storage
- ✅ Yearly backups moved to Deep Archive for long-term retention
- ✅ Serverless architecture with AWS Lambda
- ✅ S3 bucket encryption and versioning enabled
- ✅ Uses pgx/v5 for efficient PostgreSQL connectivity
/
├── cmd/
│ └── lambda/
│ └── main.go # Lambda function entry point
├── serverless.yml # Serverless Framework configuration
├── Taskfile.yml # Task runner configuration
├── .env # Environment variables (not in repo)
├── .gitignore # Git ignore file
├── go.mod # Go module file
└── README.md # This file
- Go 1.21+
- Task
- Docker (for building PostgreSQL layer)
- AWS CLI configured
- Node.js + npm (for Serverless Framework)
- Clone and setup
git clone https://github.com/nicobistolfi/go-postgres-s3-backup.git
cd go-postgres-s3-backup
npm install -g serverless
- Configure database
echo "DATABASE_URL=postgresql://user:pass@host:5432/dbname" > .env
- Deploy
task deploy
That's it! Your PostgreSQL database will be backed up daily at 2 AM UTC.
- Daily Execution: The Lambda function runs daily at 2 AM UTC via EventBridge
- Database Backup: Connects to your PostgreSQL database and creates a SQL dump
- Daily Backup: Saves the backup to S3 under
daily/YYYY-MM-DD-backup.sql
- Monthly Backup: If no backup exists for the current month, copies the daily backup to
monthly/YYYY-MM-backup.sql
- Yearly Backup: If no backup exists for the current year, copies the daily backup to
yearly/YYYY-backup.sql
- Cleanup: Removes daily backups older than 7 days
- Lifecycle Management:
- Monthly backups transition to Glacier after 30 days
- Yearly backups transition to Deep Archive after 90 days
Trigger a backup:
task invoke
View logs:
task logs
Remove deployment:
task sls:remove
aws s3 ls s3://go-postgres-s3-backup-[stage]-backups/daily/
aws s3 ls s3://go-postgres-s3-backup-[stage]-backups/monthly/
aws s3 ls s3://go-postgres-s3-backup-[stage]-backups/yearly/
aws s3 cp s3://go-postgres-s3-backup-[stage]-backups/daily/2025-08-01-backup.sql ./
Start local PostgreSQL:
docker run --name my-postgres -e POSTGRES_PASSWORD=postgres -d -p 5432:5432 postgres
Restore backup to local instance:
docker exec -i my-postgres psql -U postgres -d postgres -W < [backup-file].sql
Connect and query:
docker exec -it my-postgres psql -U postgres
Variable | Description | Required |
---|---|---|
DATABASE_URL |
PostgreSQL connection string | Yes |
BACKUP_BUCKET |
S3 bucket name (auto-configured by Serverless) | Auto |
- Database credentials are stored as Lambda environment variables
- S3 bucket has encryption enabled (AES256)
- Public access to the S3 bucket is blocked
- IAM role follows least privilege principle
- Versioning is enabled on the S3 bucket
- Lambda runs only once per day (minimal compute costs)
- Daily backups are automatically deleted after 7 days
- Monthly backups move to cheaper Glacier storage
- Yearly backups move to Deep Archive for maximum cost savings
If your database is large and backups are timing out:
- Increase the timeout in
serverless.yml
(currently set to 300 seconds) - Consider increasing the Lambda memory allocation
Ensure your PostgreSQL database allows connections from AWS Lambda:
- Check your PostgreSQL connection pooling settings
- Verify the DATABASE_URL is correct
- Ensure your database is not hitting connection limits
Check the Lambda logs for errors:
task logs
This project is licensed under the MIT License - see the LICENSE file for details.