Disaster recovery, one S3 backup.
bunqueue stores everything in a single SQLite file, which makes backups simple, but simple does not mean optional. Set up automated S3 backups, retention, and a tested recovery path.
Why Backup?
Section titled “Why Backup?”SQLite is crash-safe (WAL mode + fsync), but it can’t protect against:
- Disk failure - hardware dies, data is gone
- Accidental deletion -
rm -rfhappens - Corruption - filesystem bugs, power loss during write
- Migration errors - bad deploy wipes the data directory
S3 backup gives you point-in-time recovery with minimal effort.
Enabling S3 Backup
Section titled “Enabling S3 Backup”Configure via environment variables or a configuration file:
# RequiredBUNQUEUE_DATA_PATH=/var/lib/bunqueue/queue.dbS3_BACKUP_ENABLED=1S3_BUCKET=my-bunqueue-backupsS3_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLES3_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# OptionalS3_REGION=us-east-1 # Default: us-east-1S3_ENDPOINT= # Custom endpoint (MinIO, R2, etc.)S3_SESSION_TOKEN= # Temporary credentials, when usedS3_VIRTUAL_HOSTED_STYLE=false # Set true only when provider requires itS3_BACKUP_INTERVAL=21600000 # Every 6 hours (default)S3_BACKUP_RETENTION=7 # Keep the last 7 backups (default)S3_BACKUP_PREFIX=backups/ # Key prefix (default)Or pass them when starting the server:
S3_BACKUP_ENABLED=1 \S3_BUCKET=my-backups \S3_REGION=us-east-1 \bunqueue start --data-path ./data/queue.dbCompatible Storage Providers
Section titled “Compatible Storage Providers”Any S3-compatible storage works:
| Provider | S3_ENDPOINT | Notes |
|---|---|---|
| AWS S3 | (empty) | Default |
| Cloudflare R2 | https://<account>.r2.cloudflarestorage.com | No egress fees |
| MinIO | http://minio:9000 | Self-hosted |
| DigitalOcean Spaces | https://<region>.digitaloceanspaces.com | Simple setup |
| Backblaze B2 | https://s3.<region>.backblazeb2.com | Cheapest storage |
# Cloudflare R2 exampleS3_BACKUP_ENABLED=1S3_BUCKET=bunqueue-backupsS3_ENDPOINT=https://abc123.r2.cloudflarestorage.comS3_ACCESS_KEY_ID=your-r2-keyS3_SECRET_ACCESS_KEY=your-r2-secretS3_REGION=autoBackup Process
Section titled “Backup Process”The backup runs on a timer (default: every 6 hours):
- Flush and snapshot - drains pending buffered writes, then SQLite
VACUUM INTOcreates a standalone snapshot that includes committed WAL frames - Validate and compress - runs
PRAGMA integrity_check, computes SHA-256 over the original bytes, then gzip-compresses - Publish to S3 - uploads metadata first and the uniquely named payload second as the commit point
- Cleanup old backups - keeps only the most recent
S3_BACKUP_RETENTIONpayload/metadata pairs
Backup File Naming
Section titled “Backup File Naming”Backups are stored under the S3_BACKUP_PREFIX (default backups/) with this key pattern:
backups/ bunqueue-2026-01-15T00-00-00-000Z-<uuid>.db (gzip-compressed) bunqueue-2026-01-15T00-00-00-000Z-<uuid>.db.meta.json (checksum + sizes)Note that the .db object is gzip-compressed even though the key has no .gz suffix; the .meta.json sidecar records the compression flag and a SHA-256 checksum of the original database.
Disaster Recovery
Section titled “Disaster Recovery”The built-in restore command handles download, decompression, and validation for you:
-
Stop bunqueue
Terminal window systemctl stop bunqueue# or: docker stop bunqueue -
Find the backup to restore
Terminal window # Requires the same S3_* env vars plus BUNQUEUE_DATA_PATHbunqueue backup list -
Restore it
Terminal window bunqueue backup restore 'backups/bunqueue-2026-01-15T18-00-00-000Z-<uuid>.db' --forceThe restore is validate-before-replace: it verifies metadata, compressed/original sizes and SHA-256, checks the SQLite header, runs an integrity check on a temp file, quarantines stale WAL/SHM/journal sidecars, and only then atomically swaps it into place. On a pre-swap failure the live database is left untouched.
-
Restart bunqueue
Terminal window systemctl start bunqueue# or: docker start bunqueue
Prefer the CLI because it authenticates and validates the candidate. If an
emergency requires a manual restore, remember that the object is gzip-compressed
despite the .db key, verify the metadata checksum yourself, and remove stale
sidecars while the server is stopped:
aws s3 cp 's3://my-bunqueue-backups/backups/<exact-key>.db' ./backup.db.gzaws s3 cp 's3://my-bunqueue-backups/backups/<exact-key>.db.meta.json' ./backup.meta.jsongzip -dc backup.db.gz > /var/lib/bunqueue/queue.db.candidatesqlite3 /var/lib/bunqueue/queue.db.candidate 'PRAGMA integrity_check'mv /var/lib/bunqueue/queue.db /var/lib/bunqueue/queue.db.corruptedrm -f /var/lib/bunqueue/queue.db-wal /var/lib/bunqueue/queue.db-shm /var/lib/bunqueue/queue.db-journalmv /var/lib/bunqueue/queue.db.candidate /var/lib/bunqueue/queue.dbbunqueue will recover the queue state from the restored database, reloading pending jobs, cron schedules, and DLQ entries.
Backup Monitoring
Section titled “Backup Monitoring”Monitor backup health in production:
# Check the backup configurationbunqueue backup status
# List backups and check the most recent timestampbunqueue backup list
# Trigger a backup on demandbunqueue backup nowSet up alerts for:
- No backup in 2x the interval - backup process may be failing
- Backup size anomalies - sudden size changes may indicate issues
- S3 upload failures - check credentials and bucket permissions
Supplementary Strategies
Section titled “Supplementary Strategies”S3 backup covers most scenarios, but consider layering additional protection:
Filesystem Snapshots
# LVM snapshot (instant, zero downtime)lvcreate -s -n bunqueue-snap -L 1G /dev/vg0/bunqueue
# ZFS snapshotzfs snapshot tank/bunqueue@dailyCron-based local backup
# Copy SQLite file every hour (WAL must be checkpointed first)0 * * * * sqlite3 /var/lib/bunqueue/queue.db ".backup /backups/queue-$(date +\%H).db"Replication to secondary server
# rsync the database file periodically*/30 * * * * rsync -az /var/lib/bunqueue/ backup-server:/bunqueue-replica/Best Practices
Section titled “Best Practices”- Enable S3 backup from day one - don’t wait for your first data loss
- Test recovery regularly - a backup you can’t restore from is worthless
- Monitor backup health - alert on missed backups
- Use retention policies - keep the last 7-30 backups depending on your needs
- Consider R2 or B2 for cost-effective storage (no egress fees with R2)