vitrinka.docs

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 /restore

A 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.

On this page