How to Add Automatic Database Backups: 3 Approaches
Learn how to add automatic database backups using managed platforms, Docker sidecars, or cron scheduling. Step-by-step setup for PostgreSQL, MySQL, and testing strategies.
- Database backups prevent total data loss; most teams skip them until failure hits
- Three approaches: managed platforms ($50-300/month, no ops), Docker sidecars (free, requires monitoring), and cron scheduling (cheapest, fragile)
- Always test a restore before trusting your backup; an untested backup is a hope, not a backup
- Store backups offsite (S3 or Backblaze) to survive server failure; follow the 3-2-1 rule (3 copies, 2 media types, 1 offsite)
- Implement daily backups as baseline; hourly only if losing one hour of transactions is catastrophic
You need automatic backups before your first paying customer. Most developers skip them because "it won't happen to me." It will. This tutorial walks you through three proven approaches: managed platform backups, Docker sidecars, and host-level cron scheduling. Pick one based on your team size and ops tolerance.
Why Database Backups Matter (and Why Most Developers Skip Them)
A database failure without backups means you lose everything. Not some data. All of it. Your customers see a 500 error forever, your revenue stops, and you spend weeks explaining what happened.
Backups solve two problems: Recovery Time Objective (RTO) -- how fast you get back online -- and Recovery Point Objective (RPO) -- how much data you lose. A backup from yesterday means you lose a full day of transactions. A backup every hour loses at most one hour. The tradeoff is storage cost and complexity.
You have three realistic options. Managed platforms like Northflank handle everything automatically. Docker sidecar containers cost nothing but require ops discipline. Host-level cron scripts are the cheapest but demand shell scripting and manual verification. Most teams at 10-100 people pick managed first because the time cost of DIY outweighs the monthly fee.
Three Ways to Add Automatic Backups: A Quick Decision Tree
Here's the honest comparison:
Managed Platform Backups (Northflank, Dokploy on Opsily/Ship)
- Cost: $50-300/month depending on retention and storage
- Setup time: 15 minutes
- Ops burden: None (vendor handles it)
- Tradeoff: You're locked into one provider
Docker Sidecar Containers (fradelg/mysql-cron-backup, prodrigestivill/postgres-backup-local)
- Cost: $0 (open source) plus storage
- Setup time: 1-2 hours
- Ops burden: Monitor container health, test restores, manage secrets
- Tradeoff: Runs in your cluster; if the cluster dies, backups might too
Host-Level Cron (DIY shell scripts on Hetzner/Coolify)
- Cost: $0 (your own shell scripting)
- Setup time: 3-4 hours for production-ready setup
- Ops burden: High (shell scripts, permissions, monitoring, retention cleanup)
- Tradeoff: Cheapest but fragile; one bad variable means silent failure
If you have a DevOps engineer, sidecars are smart. If you don't, a managed platform saves $50k/year in engineer time. If you're bootstrapped, start with cron and upgrade when you can afford to.
Approach 1: Platform-Managed Backups (Northflank Example)
Northflank offers automated backups as a built-in feature. You configure retention and schedule in the UI, and Northflank handles snapshots and dumps automatically.
Here's how it works: Northflank takes two types of backups. Snapshots are instant copies of your entire database state (faster, larger, filesystem-level). Dumps are logical exports (slower, smaller, database-level exports like pg_dump). For PostgreSQL, Northflank defaults to dumps; for MySQL and MongoDB, both are available.
Step 1: Navigate to your database in Northflank. Click "Backups." Step 2: Enable "Automatic backups." Choose your schedule: hourly, daily, or weekly. Most teams use daily for databases under 50GB, hourly for mission-critical data.
Step 3: Set retention. The default is 7 backups (one week for daily). Production should keep at least 30 days (30 daily backups). Some teams keep 90-day retention for compliance.
Step 4: Test a restore. Click "Restore" on an old backup, choose a target database, and verify data integrity. Never ship without doing this once.
The trade-off: Northflank charges for storage beyond your main database size. A 5GB database backed up daily for 30 days costs roughly $20-40/month in backup storage. It's predictable and vendor-locked, but you're not waking up at 3am debugging backup scripts.
Dockerized platforms like Dokploy on Opsily/Ship offer similar native backup support. If you're deploying via Ship, check the platform docs for backup scheduling--it may be built in.
Approach 2: Docker Sidecar Containers (PostgreSQL + MySQL)
This approach runs a backup container alongside your database. It wakes up on a cron schedule, dumps the database, compresses it, and uploads to S3 or local storage.
Two popular images: prodrigestivill/postgres-backup-local for PostgreSQL and fradelg/mysql-cron-backup for MySQL. Both are open source and actively maintained.
Here's a working docker-compose.yml for PostgreSQL with automatic backups:
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
postgres-backup:
image: prodrigestivill/postgres-backup-local:latest
environment:
POSTGRES_HOST: postgres
POSTGRES_DB: myapp
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_BACKUP_DIR: /backups
BACKUP_KEEP_DAYS: 30
BACKUP_KEEP_WEEKS: 12
BACKUP_KEEP_MONTHS: 6
SCHEDULE: "@daily"
volumes:
- backup_data:/backups
depends_on:
- postgres
volumes:
postgres_data:
backup_data:
For MySQL, use fradelg/mysql-cron-backup:
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: myapp
MYSQL_USER: appuser
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
mysql-backup:
image: fradelg/mysql-cron-backup:latest
environment:
MYSQL_HOST: mysql
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_USER: appuser
MYSQL_PASSWORD: ${DB_PASSWORD}
BACKUP_DIR: /backups
MAX_BACKUPS: 30
CRON_TIME: "0 2 * * *"
volumes:
- backup_data:/backups
depends_on:
- mysql
volumes:
mysql_data:
backup_data:
The SCHEDULE or CRON_TIME field controls when backups run. @daily or 0 2 * * * runs once per day at 2am. 0 */6 * * * runs every 6 hours. BACKUP_KEEP_DAYS: 30 auto-deletes backups older than 30 days--critical for not burning through disk space.
Store DB_PASSWORD and DB_ROOT_PASSWORD in a .env file or secrets manager. Never hardcode passwords in compose files.
The critical step: Test that backups are actually running. Shell into the backup container and check the /backups directory:
docker-compose exec postgres-backup ls -la /backups
You should see .sql.gz files with timestamps. If the directory is empty after 24 hours, your backup container failed silently.
This approach keeps backups inside your cluster. If the entire cluster dies, so do backups. That's why step 7 (offsite storage) matters.
Approach 3: Host-Level Cron (DIY on Hetzner or Coolify)
If you're already running databases on a bare Hetzner machine or inside Coolify, cron scheduling is the oldest and most direct approach.
Create a shell script /opt/backup/backup-postgres.sh:
#!/bin/bash
DB_USER="appuser"
DB_NAME="myapp"
DB_HOST="localhost"
BACKUP_DIR="/var/backups/postgres"
RETENTION_DAYS=30
# Create timestamp
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${TIMESTAMP}.sql.gz"
# Create backup
pg_dump -U $DB_USER -h $DB_HOST $DB_NAME | gzip > $BACKUP_FILE
# Delete old backups
find $BACKUP_DIR -type f -name "${DB_NAME}_*.sql.gz" -mtime +$RETENTION_DAYS -delete
# Log result
echo "Backup completed: $BACKUP_FILE" >> /var/log/db-backups.log
Make it executable and add to crontab:
chmod +x /opt/backup/backup-postgres.sh
crontab -e
Add this line to run daily at 2am:
0 2 * * * /opt/backup/backup-postgres.sh
For MySQL, replace pg_dump with mysqldump:
mysqldump -u $DB_USER -p$DB_PASSWORD $DB_NAME | gzip > $BACKUP_FILE
The RETENTION_DAYS=30 line auto-deletes backups older than 30 days using find. Without it, you'll burn through disk space in weeks.
Pitfalls: Make sure the backup directory exists and the running user (usually postgres or mysql) has write permissions. Test manually before trusting cron:
/opt/backup/backup-postgres.sh
ls -la /var/backups/postgres
Cron doesn't have your shell environment. If your script uses environment variables, source them explicitly in the cron job or the script.
The Part Nobody Tests: Verifying Your Backups Work
A backup you haven't restored is a hope, not a backup. Most teams discover their backups are broken when a real failure happens. By then, it's too late.
Here's the test procedure for each approach:
Platform-managed (Northflank/Dokploy): Click the restore button in the UI. Choose an old backup and restore to a temporary database. Run a simple query to verify data is there. Check the timestamp on a record--confirm it matches what you expect.
Docker sidecars: Shell into the backup container, verify files exist in the backup volume, and restore manually to a test container.
docker-compose exec postgres-backup sh
cd /backups
ls -la
Then restore the latest backup to a test database:
zcat /backups/myapp_*.sql.gz | psql -U appuser -d myapp_test
Host-level cron: Check the backup directory exists and contains recent files:
ls -la /var/backups/postgres/
ls -lh myapp_*.sql.gz | head -5
Restore to a test database to verify integrity:
gunzip -c /var/backups/postgres/myapp_20260820_020000.sql.gz | psql -U appuser -d myapp_test
Also verify the cron job is actually running. Check /var/log/db-backups.log for recent entries. If the log is empty or the last entry is days old, your cron job failed.
Make this a monthly checklist item: test a restore. Do it quarterly in production if possible (restore to a clone, not the live database).
Offsite Storage: Protecting Against Entire-Server Failure
Local backups (on the same server or cluster) solve most data loss problems. They don't solve server hardware failure, ransomware, or datacenter fires.
Store backups offsite using S3-compatible storage. AWS S3 costs roughly $0.023/GB/month. Backblaze B2 costs $0.006/GB/month. MinIO (self-hosted) is free but requires ops.
For Docker sidecars, use a backup container that uploads to S3 after creating a dump. The nfrastack/container-db-backup tool handles both backup creation and S3 upload in one container.
For host-level cron, add an S3 upload step to your script:
# After backup is created
aws s3 cp $BACKUP_FILE s3://my-backups/postgres/
For platform-managed backups (Northflank), check if native S3 export is available. Most modern platforms support it.
Implement the 3-2-1 backup rule: keep 3 copies of your data, on 2 different media types, with 1 offsite. Example: one live database, one local daily backup, one S3 backup 100 miles away.
FAQ: Common Questions on Database Backups
How often should you back up your database?
Daily is the baseline for most apps. Hourly backups make sense if losing one hour of transactions is catastrophic (e.g., financial transactions). Backups cost money and storage--optimize for your RPO tolerance, not paranoia.
What's the difference between snapshots and dumps?
Snapshots are filesystem-level point-in-time copies (fast, large). Dumps are logical exports (slow, portable, smaller). Snapshots backup instantly; dumps require database locking. Use dumps for cross-platform migrations; use snapshots for speed.
Can I restore a single table from a backup?
Yes, if it's a dump. Extract the SQL file, search for the CREATE TABLE statement, and restore just that section. Snapshots require restoring the entire database and extracting the table afterwards. This is why dumps are common in production.
How do you restore a database from a backup?
Depends on the approach. Managed platforms provide a UI button. DIY requires psql < backup.sql for PostgreSQL or mysql < backup.sql for MySQL. Always restore to a test database first.
Do I need to test database backups?
Yes. An untested backup is a security theater prop. Test quarterly at minimum. If you can't restore a backup in 15 minutes, your backup strategy is broken.
What's point-in-time recovery?
The ability to restore your database to any moment in time (within retention), not just the latest backup. PostgreSQL does this with WAL archiving; MySQL with binlog. Platforms like Northflank offer it. DIY requires extra work.
How do you know backups are actually working?
Check the backup directory for recent files. Verify file timestamps. Test a restore. Log backup runs and alert if a backup hasn't completed in 24 hours. Silence is failure.
What happens if my backup container crashes?
Backups stop silently. Most containers have no alerting by default. Add a health check: if a backup hasn't completed in the last 25 hours, the container is unhealthy. Set up a monitoring alert on container status.
The Bottom Line
Automatic backups are non-negotiable for production. Choose managed platforms if you have the budget ($50-300/month); choose sidecars if you have a DevOps person; choose cron only if you're fully bootstrapped and willing to monitor closely.
Test a restore the day you implement backups. Make it a repeating calendar reminder. The backup that saves your business is the one you've actually tested.
When you're ready to scale backups with confidence, Opsily/Ship offers managed database hosting with built-in automated backups--no monitoring, no scripts, just a monthly fee. Check our production readiness checklist for where database backups fit in your launch plan.