Appearance
Backups & disaster recovery
Imaginook backs itself up with two Artisan commands and prunes by retention. This page covers what is backed up, how to restore, the volume-snapshot layer, and the recurring restore drill.
What's backed up
imaginook:backup (scheduled nightly at 02:30) writes a set to the backup disk (config('imaginook.backup.disk'), local by default; set BACKUP_DISK=s3 for off-site object storage):
database.sql.gz— apg_dumpof the whole database (--clean --if-exists, so it restores into a fresh cluster).sites.tar.gz— the user-site trees (sites+buttons+avatarsdisks).
When BACKUP_ENCRYPTION_KEY is set, every archive is encrypted at rest with openssl (AES-256); files then end in .enc. Sets older than BACKUP_RETENTION_DAYS (default 14) are pruned automatically, and each run writes a .last-backup.json health manifest that the admin diagnostics panel shows. A failed run raises a backup.failed audit event, which fans out to the outbound webhook so you hear about it.
Off-site + snapshots (defence in depth)
The nightly logical backup is the primary recovery path. For a hosted instance, layer on:
- Off-site archives — point
BACKUP_DISKat an S3-compatible bucket in a different region/provider than the host. - Block-storage snapshots — enable your provider's volume snapshots for the Postgres data volume and the user-site volume, on a schedule at least as frequent as the backup (e.g. daily, retained 7–30 days). Snapshots give a fast, crash-consistent rollback; the logical dump gives a portable, verifiable, cross-version restore. Keep both.
Restoring
A restore replaces the database and site trees. Take a fresh snapshot first.
bash
# List sets and confirm the newest is restorable — non-destructive (a DR drill):
php artisan imaginook:restore --latest --verify
# Restore a specific set (destructive; requires --force):
php artisan imaginook:restore --set=20260711-023000 --forceRestore fetches the set from the backup disk, decrypts it (needs the same BACKUP_ENCRYPTION_KEY), verifies the dump's gzip integrity, applies it with psql -v ON_ERROR_STOP=1, and extracts each site tree back into its configured root. After a restore, rebuild the nginx host map and caches:
bash
php artisan imaginook:nginx-sync # rebuild host→webroot maps from the DB
php artisan config:cache && php artisan route:cacheThe restore drill
Backups you have never restored are not backups. Run the drill on a schedule (monthly is a good default) and record the result:
php artisan imaginook:restore --latest --verify— confirms the newest set is present, decryptable and gzip-intact without touching production.- Once a quarter, do a real restore into a throwaway instance (a scratch VM or the all-in-one image with a copy of the set) and smoke-test
/up,/readyz, and a couple of sites. Note the wall-clock time to restore. - Log the drill date, set label, and outcome in your ops record.
If --verify ever fails, treat it as an incident: the most recent backups are not recoverable, and you need to fix the backup pipeline before you need it for real.