Snapshots
Norn automatically creates PostgreSQL database snapshots during deploys and exposes manual snapshot, retention, restore, and schema-migration controls as durable app operations. The web and macOS Apps views use the same versioned receipts as automated clients.
Automatic Snapshots
During the snapshot step of the deploy pipeline, Norn runs pg_dump on the app's database (from infrastructure.postgres.database). This happens before migrations run, providing a safety net for schema changes.
Snapshots are only created for apps that declare postgres infrastructure:
infrastructure:
postgres:
database: myapp
snapshots:
keep: 3
exportBucket: myapp-snapshotsListing Snapshots
CLI
norn snapshots myappDisplays a table of available snapshots with timestamps, source commit, created time, size, and filename.
Retention
norn snapshots myapp retention --keep 3
norn snapshots myapp retention --keep 3 --execute --yesRetention previews by default. The command marks the newest snapshots as keep and older snapshots as would-prune without deleting files. If --keep is omitted, Norn uses snapshots.keep from infraspec.yaml, falling back to 3. Add --execute --yes to delete older local snapshot files and print an applied retention receipt.
norn ops platform also reports per-app snapshot counts, policy keep counts, and over-limit totals.
API
curl -H "Authorization: Bearer $NORN_API_TOKEN" \
http://localhost:8800/api/v1/apps/myapp/snapshots
curl -X POST -H "Authorization: Bearer $NORN_API_TOKEN" \
-H "Idempotency-Key: snapshot-$(uuidgen)" \
http://localhost:8800/api/v1/apps/myapp/snapshotsThe Apps view marks the exact snapshots outside the selected keep window as will prune. Pruning is not sent until the operator reviews that preview and confirms it.
Restoring a Snapshot
CLI
norn snapshots myapp restore 20260825T143000 --yes
norn snapshots myapp restore 20260825T143000 --yes --pre-restoreThe current CLI uses the legacy synchronous route and therefore accepts the compact UTC timestamp column only when it identifies exactly one snapshot. For new automation and UI behavior, prefer the durable versioned route below and pass the exact inventory filename.
API
curl -X POST -H "Authorization: Bearer $NORN_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: restore-$(uuidgen)" \
-d '{"confirm":true}' \
http://localhost:8800/api/v1/apps/myapp/snapshots/myapp_manual_20260825T143000.dump/restoreUse the exact filename returned by the versioned inventory. A legacy compact UTC timestamp is still accepted when it identifies exactly one snapshot; Norn rejects ambiguous timestamps instead of restoring an arbitrary file.
WARNING
The versioned /api/v1 restore route always creates a fresh safety snapshot immediately before the destructive restore. Restore runs in a single PostgreSQL transaction with fail-fast semantics, so an error rolls back instead of leaving a knowingly partial schema. The older CLI flags remain for compatibility with the legacy route.
The accepted response is an app.snapshot-restore operation. Its terminal typed receipt includes the restored snapshot, safety snapshot, and database. It is safe to close either UI after queuing; the operation is reconstructed from PostgreSQL.
Standalone schema changes
When migrations and infrastructure.postgres are declared, either Apps view can queue that command without deploying application allocations. Norn prepares the requested source ref, creates a pre-migration snapshot, runs the declared command once, and records an app.migrate receipt:
curl -X POST -H "Authorization: Bearer $NORN_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: migrate-$(uuidgen)" \
-d '{"ref":"HEAD","confirm":true}' \
http://localhost:8800/api/v1/apps/myapp/migrationsSchema mutations and restores intentionally have one execution attempt. If the API stops after mutation begins, Norn records visible failure for operator review rather than assuming an unknown migration is replay-safe.
Serialization and recovery
All mutable app operations acquire a PostgreSQL advisory lock keyed by app. This prevents deploy, rollback, snapshot retention, restore, and migration work for the same app from overlapping across API replicas. A replica that cannot acquire the lock returns its claim to the queue without consuming an execution attempt. Every mutation requires an idempotency key and rejects reuse for a different request.
The web and macOS clients persist each unacknowledged intent before sending it. After a browser refresh, application relaunch, or ambiguous network failure, the client reuses the same request-bound key and receives the original receipt instead of creating a duplicate. The local intent is cleared only after Norn acknowledges the durable operation.
Remote Export And Import
Snapshots are stored first as local files under the Norn API working directory's snapshots/ folder. Apps can also declare snapshots.exportBucket to archive local dumps to S3-compatible object storage such as Garage.
Local files are node-local, not an HA snapshot catalog. A multi-replica production deployment must use managed database PITR or off-host object storage and route restore work to a replica that has imported the selected dump.
Database names are restricted to a portable 1-63 character identifier subset, snapshot inventory ignores symbolic links and non-regular files, and completed dumps are published with owner-only permissions without overwriting an existing filename.
snapshots:
keep: 5
exportBucket: myapp-snapshotsManual export uploads the latest local snapshot:
norn snapshots export myappList remote snapshots:
norn snapshots remote myappImport downloads a remote object key back into the local snapshots directory:
norn snapshots import myapp snapshots/myapp/myapp_db_abcdef_20260614T181100.dumpThe key must be inside that app's snapshots/<app>/ prefix and contain a valid inventory filename. Downloads are private and atomic, and never overwrite an existing local dump.
Remote export/import requires the platform S3 configuration used by managed object storage, including NORN_S3_ENDPOINT, NORN_S3_ACCESS_KEY, NORN_S3_SECRET_KEY, and provider-specific path-style settings when using Garage. Export and import actions emit Beacon events (snapshot.exported, snapshot.imported) so off-host backup movement is auditable.