Backups
One sealed archive of every database, produced without touching the sources, restored only into an empty directory.
The sealed archive
vitrinka backup takes a consistent VACUUM INTO copy of the control database and of every workspace database — reads only, the sources are never mutated — tars them with a manifest, and seals the stream under a key from its own environment: VITRINKA_KEK, falling back to VITRINKA_MASTER_KEY. The result is one enc2f file: framed AES-256-GCM chunks under a fresh wrapped data key (Encryption). With the OpenBao key store the server runs without VITRINKA_KEK, and without VITRINKA_MASTER_KEY once the key store has adopted it, so give the backup run VITRINKA_MASTER_KEY in its environment and keep that key with your recovery material — restore-backup needs the same one.
vitrinka backup -data /data -out /backups/vitrinka-2026-08-24.vtbk
vitrinka restore-backup -in /backups/vitrinka-2026-08-24.vtbk -out /restoreA stolen archive is ciphertext. Because the sealed columns inside it stay under their workspace keys, a workspace that is later crypto-shredded is unreadable inside every archive taken after the shred (Retention and deletion).
What it excludes
Blobs are deliberately not in the archive. They are already per-workspace ciphertext at rest, so replicating the blob store replicates ciphertext; with BLOB_BACKEND=fs back up DATA_DIR/blobs alongside, with S3 the bucket is the durable copy.
The licence file is not in DATA_DIR either — it lives in the bundle's ./license/ directory (Operations).
Restoring
restore-backup unseals and unpacks into an empty directory only. It never overwrites a live DATA_DIR; pointing a stopped server at the restored tree, or copying files over after your own diff, stays a deliberate operator step.
Operator safety snapshots go to VITRINKA_BACKUP_DIR. A path that resolves inside DATA_DIR is refused, so a backup can never be swept up by the thing it protects.
The compose-level snapshot
The simplest self-hosted routine — stop, tar /data, start — is in Operations. It captures databases and filesystem blobs in one archive but is not sealed by the application; keep it on an encrypted volume.
Continuous replication
The hosted service uses Litestream to replicate its databases to object storage. The deployment config sets a 10-second sync interval, daily snapshots and validation every six hours. Object expiry is controlled separately by the storage policy; these intervals do not guarantee a particular recovery window. Sealed columns remain encrypted in the replica. Database metadata, including identifiers, timestamps and addresses, depends on transport security and bucket access controls. The sealed archive above provides a separate backup method.
Standby and write fencing
For a two-host deployment the server has a standby role and a write fence. VITRINKA_ROLE=standby boots a process that answers the health probes and nothing else: /healthz is 200, /readyz and every other route 503. It opens no database and runs no background work, so a replica being followed into its DATA_DIR is never touched; keeping that replica current is the job of the replication you run. Promotion is clearing the role and restarting.
VITRINKA_FENCE_URL is the write fence: before its first write at boot a writer fetches that small document and refuses to start unless it names VITRINKA_FENCE_HOST. An unreachable fence refuses too, and a standby never consults it. The fence is read at boot, so stop the old writer before the document names the new one. Both are in the configuration reference.