vitrinka.docs

Operations

Upgrades, backups, air-gapped installs, the operator verbs and the admin console — the things you do after it is running.

Upgrading

Back up /data first, point at the new tag, and bring it up. Migrations run on boot.

# edit .env: VITRINKA_VERSION=vX.Y.Z
docker compose up -d

On boot the server upgrades every workspace database that is behind the schema, VITRINKA_MIGRATE_WORKERS at a time (default min(8, CPUs)). /readyz answers 503 with the sweep's progress until it finishes, while /healthz stays up, so point your proxy's readiness check at /readyz.

  • No hard stops — every release migrates from every prior release, so you can skip versions.
  • You never edit shipped files — release notes may add optional .env keys; nothing you are given is meant to be modified.
  • Server before clients — deploy the server before shipping clients that call new endpoints.

Backups

/data — the vitrinka-data volume — holds the SQLite databases and, unless you use S3, the blobs. Back it up on your normal schedule and before every upgrade.

docker compose stop vitrinka
docker compose run --rm --no-deps --entrypoint sh -v "$PWD:/backup" vitrinka \
  -c 'cd /data && tar czf /backup/vitrinka-data-$(date +%F).tgz .'
docker compose start vitrinka

Run it through compose, as above. A bare docker run -v vitrinka-data:… mounts a different, unrelated global volume rather than the project-scoped one the app actually uses — and you would back up nothing.

Your licence is not in /data

It lives in the bundle's ./license/ directory, mounted read-only into the container. Back up /data for your data and keep the bundle directory for your licence — they are two separate things to preserve.

Air-gapped installs

The bundle ships an image.tar, a LOAD-AIRGAP.md and the CLI binaries. Load the image into your internal registry or straight into Docker, point VITRINKA_IMAGE at it, and set AIR_GAP=1 to blank every integration that would reach the public internet — the email provider, Telegram, GitHub, the AI channel and the AI backend (EVE_*, AI_BACKEND_*), managed AI, Plane, Stripe, web push, Sentry, the OTLP trace exporter, the breach check and link-card previews — while S3, SMTP, the capture and transcription sidecars and the metrics listener keep working against your own network. A tracker, workspace AI backend or organization AI endpoint reaches that network only once deployment authority has saved it (Outbound targets); so does an SSO connection, and only inside the ranges VITRINKA_SSO_PRIVATE_NETWORKS lists (an identity provider on your own network). MCP clients register at POST /oauth/register, because no client-ID metadata document is fetched. With Stripe blanked, plans are metered only when VITRINKA_METERED=1 is set. The server never phones home for a version: the update check lives in the CLI, and VITRINKA_RELEASE_FEED below points it at your mirror.

shasum -a 256 -c SHA256SUMS    # verify the bundle first

The CLI, without a registry

The bundle carries the CLI too — six static binaries, one per platform, plus cli/latest.json. Workstations need it to capture sets and drive the work queue, and npm install -g assumes a registry your estate may not have.

Point $VITRINKA_RELEASE_FEED at your own internal mirror and vitrinka update keeps working offline. The bundle's cli/README.md carries the install instructions and the PowerShell path.

Operator verbs

The server binary carries the maintenance commands. Every destructive one is a dry-run report until -execute is passed. shred-workspace refuses to execute without -server-stopped, and reconcile-storage without -offline. Stop the server for rewrap-deks as well, because it does not check. seal-existing is built to run beside a live server (-batch, -pace), and so is platform-admin.

VerbWhat it doesDetails
vitrinka backup -data /data -out <file>.vtbkConsistent copies of every database, sealed into one archive; blobs excluded.Backups
vitrinka restore-backup -in <file>.vtbk -out <empty dir>Unseals an archive into an empty directory, never over a live DATA_DIR.Backups
vitrinka seal-existing -data /data [-execute | -verify]Seals legacy plaintext rows and objects in place; -verify is the gate before strict mode.Encryption
vitrinka rewrap-deks -data /data -old-kek … -new-kek … [-execute]Rotates the deployment key by rewrapping every workspace key; data is untouched.Encryption
vitrinka shred-workspace -data /data <workspace> -server-stopped [-execute]Destroys a workspace's data key and tombstones it.Retention and deletion
vitrinka inventory -data /data -out <manifest>Read-only census of every referenced blob against one listing of the store; prints a redacted summary.Precedes migrate-blobs.
vitrinka migrate-blobs -data /data -manifest <manifest> [-execute]Copy-only move of legacy objects into their workspace prefixes; never deletes a source.Idempotent; re-run after a partial failure.
vitrinka reconcile-storage -data /data -workspace <ws> -backend fs|s3 [-execute -offline]Recounts a workspace's storage accounting after raw offline maintenance, taking a sealed backup first.Run before restarting writers.
vitrinka audit-export-key -data /dataPrints the public key that signs audit export batches.Audit export
vitrinka audit-verify -data /data (or -dir <dir> -key <key>)Checks every hash link and signature of an export and exits non-zero on the first broken link.Audit export
vitrinka platform-admin grant <email> · revoke <email> · listOpens or closes the admin console for an account that has signed in once; applies on its next request, audited.The first admin
vitrinka smtp-test -to <you@example.com>Sends one test mail through the SMTP_* relay without starting the server.Mail
vitrinka license-inspect <file>Parses and verifies a licence file without starting the server.Licensing
vitrinka healthcheckThe container health probe compose runs.

Run them through compose so they see the same /data, keys and blob backend as the server:

docker compose stop vitrinka
docker compose run --rm --no-deps -v "$PWD/backups:/backups" vitrinka backup -data /data -out /backups/vitrinka-$(date +%F).vtbk
docker compose start vitrinka

backup refuses an -out inside DATA_DIR. The sealed safety snapshots the maintenance verbs take before they rewrite data — reconcile-storage -execute, for one — go to VITRINKA_BACKUP_DIR, default <DATA_DIR>-backups. Inside the container that sibling path is not on the volume, so mount a directory and point the variable at it; a path inside DATA_DIR is refused.

The admin console

/admin is the platform console. It answers 404 to anyone who is not a platform admin; grant the flag with platform-admin (the first admin).

  • Stats — accounts, workspaces, organizations by plan and pending waitlist entries.
  • Workspaces — search by slug or name. The picker sets the stored plan of a workspace's organization. While plans are not metered, each row also shows the Enterprise plan it runs on, and a note says the stored plan takes effect once metering is on — so you can stage plans before you set VITRINKA_METERED=1. Suspend a workspace — it then serves nothing until it is unsuspended — and review AI payments, where a managed-AI shortfall can be closed without charging, with a written reason.
  • Users — search by email or name; platform admins carry a ★.
  • Waitlist — with VITRINKA_WAITLIST=1, every new address that tries to sign in lands here. Approve it, and its next sign-in creates the account; an address with a pending invitation needs no approval.

A plan change is audited as org.plan, a suspension as workspace.suspend.

Licensing

Drop your vendor-signed license.json into ./license/; compose mounts it read-only at /run/license, which is where the server looks. How the server verifies it, what it does with a file it cannot use, what expiry pauses and how renewal works are on Licensing.

On this page