-
Notifications
You must be signed in to change notification settings - Fork 11
feat: Pre-deploy volume snapshots and backup/restore CLI for databases #4
Description
Haloy manages database containers end-to-end (create, deploy, health check, proxy, delete) but has no data protection. A bad deploy, wrong image version, broken migration, or bad config change can destroy data with no way to roll back. The preset: database sets protected: true, but that only prevents accidental deletion.
This adds two things: automatic volume snapshots before each database deploy, and CLI commands for manual backup and restore.
How it works
Before stopping the current database container during a replace strategy deploy, the daemon copies the volume data to a timestamped snapshot directory. Since replace stops the container first, the database has flushed its WAL and shut down cleanly, so the filesystem copy is consistent.
The copy runs through a temporary container:
docker run --rm -v SOURCE:/source -v DEST:/dest busybox cp -a /source/. /dest/
Snapshot metadata (app name, deployment ID, volume name, path, timestamp, size) goes into SQLite. Old snapshots get pruned by retention count.
New CLI commands follow the existing patterns (haloy deploy, haloy rollback, haloy logs):
haloy backup <app> # Snapshot the app's volumes haloy backup list <app> # List available snapshots haloy restore <app> <backup-id> # Restore from a specific snapshot haloy backup prune <app> # Remove old snapshots beyond retention
Config
targets: postgres: preset: database image: repository: postgres:18 port: 5432 volumes: - postgres-data:/var/lib/postgresql/data backup: pre_deploy_snapshot: true # default: true for database preset snapshot_retention: 3 # keep last 3 snapshots, default: 3
Server-level in haloyd.yaml:
backups: snapshot_dir: /var/lib/haloy/snapshots # default location
Implementation plan
Config (internal/config/)
deploy_config.go: AddBackupstruct toTargetConfigwithPreDeploySnapshot *boolandSnapshotRetention *intdeploy_config_validate.go: Validate retention > 0
Config loading (internal/configloader/)
loader.go: Set defaults inapplyPreset()for database preset (pre_deploy_snapshot: true,snapshot_retention: 3). HandleBackupfield merging inMergeToTarget()
Backup logic (new internal/backup/)
snapshot.go: Volume snapshot and restore via Docker SDK. Temporary container mounts source volume + snapshot destination, copies data. Restore does the reverse.prune.go: Delete oldest snapshots beyond retention count.
Storage (internal/storage/)
migrations.go: Addsnapshotstablemodels_snapshots.go(new): SQLite operations for snapshot metadata (id, app_name, volume_name, deployment_id, path, created_at, size_bytes)
Deploy integration (internal/deploy/)
deploy.go: Before the container stop/remove block forreplacestrategy (~line 37), snapshot all volumes if backup config is enabled. Prune old snapshots after successful deploy.
API (internal/api/)
routes.go: AddPOST /v1/backup/{appName},GET /v1/backup/{appName},POST /v1/restore/{appName}/{backupId},DELETE /v1/backup/{appName}/{backupId}handlers_backup.go(new): Backup/restore handlers, streaming progress via SSE (same pattern as deploy).
CLI (new)
cmd/backup.go:haloy backupandhaloy backup listcmd/restore.go:haloy restorecmd/backup_prune.go:haloy backup prune
Scope
This is a filesystem-level safety net for deploy-time failures, not a replacement for pg_dump, WAL archiving, or managed database backups.
- Snapshots are full volume copies, no dedup or compression
- Disk usage scales with
retention * volume_size - Pre-deploy snapshots cover deploy failures;
haloy backupcovers everything else - No scheduled backups or database-aware dumps (separate features if needed)
Testing
- Config validation (valid/invalid retention, preset defaults)
- Config merging (global backup config + target override)
- Snapshot metadata SQLite operations (create, list, delete, prune)
- Volume snapshot round-trip (create data, snapshot, modify, restore, verify)
- Existing deploy flow unaffected when
pre_deploy_snapshot: false