vitrinka.docs

Air-gapped deployment

What AIR_GAP=1 blanks, what deliberately stays reachable, what leaves the host, and the bundle that installs without a registry.

The switch

Set exactly AIR_GAP=1 to disable the configured public integrations at startup: the email provider, Telegram, GitHub, the AI channel and the AI backend (EVE_*, AI_BACKEND_*), managed AI, Plane, Stripe, web push, Sentry error reports and the OTLP trace exporter. The breached-password check, link-card previews and the MCP sign-in's client-ID metadata documents are switched off too, and single sign-on loses the public internet. The server clears their settings and logs what it disabled. Other values leave this switch off; a typo does not stop startup. Network isolation also requires your firewall and correctly configured internal endpoints.

What stays on purpose

ReachableWhy
S3 and the screenshot sidecarThey point at your own endpoints on your own network.
SMTPAn internal relay remains available for messages such as password resets. Use SMTP_* instead of an external provider key.
The issue connectors (Jira, Plane), a workspace's AI backend and an organization's AI endpointConfigured against your own tracker or model — but only one that deployment authority saved reaches anything (Outbound targets), and then only an internal address: loopback, private (RFC 1918, ULA), link-local, CGNAT or class E, never a cloud metadata or credential endpoint such as 169.254.169.254. Its HTTP client checks every address a host resolves to, so an atlassian.net URL in an air-gapped deployment gets a refusal, not a firewall timeout; a target an ordinary admin saved is refused outright, and so are the vendor-hosted AI providers, the ChatGPT and Claude subscriptions and a base URL naming a vendor's host.
Single sign-on (OIDC)AIR_GAP=1 opens nothing by itself and takes the public internet away: SSO reaches only an identity provider inside the internal ranges VITRINKA_SSO_PRIVATE_NETWORKS lists, on a connection deployment authority saved, and any other connection reaches nothing (an identity provider on your network). A listed range outside the internal network (loopback, RFC 1918, ULA, CGNAT, class E) refuses the boot; a public issuer is refused, and Check health in the SSO settings names AIR_GAP and the fix.
The transcription sidecarMeeting audio goes to a whisper.cpp service on your compose network and never leaves it.
The metrics listenerIt only answers scrapes from your own network and pushes nothing.

MCP connectors still sign in, without fetching anything: the OAuth metadata stops advertising client-ID metadata documents (CIMD) and the server never fetches one, so clients such as Claude Code register through dynamic client registration (POST /oauth/register) instead. A client that registered with a metadata document before the switch is refused at consent and at the token endpoint, naming the registration endpoint, and registers again there.

There is no server-side update ping to disable: /api/v1/version is an inbound beacon clients poll, and the update check itself lives in the CLI.

What leaves the host

The server sends nothing to the vendor: no telemetry, no update ping, no licence check, no usage or seat report. Outbound traffic starts from something you set up or something your people paste:

  • Integrations you configure — each stays off until its variable or setting exists: mail, single sign-on, the trackers, GitHub, the AI channel and backends, the key store, the audit export target, S3, the sidecars, web push, Stripe, and the observability exporters (observability settings).
  • Links your people paste — off under AIR_GAP=1: a pasted link keeps its bare URL. Otherwise a link card fetches the page it names, its preview image and favicon from public internet addresses only, checked on the resolved address at connect time and at every redirect, with no environment proxy; a link to a private, loopback or link-local address, or to a name that does not resolve, keeps its bare clickable URL and the card says why. With a capture sidecar set, it screenshots only the page that fetch reached — a failed fetch captures nothing — on a host vetted as public just before each capture. The capture keeps the page on that host and blocks requests to IP-literal, single-label, localhost and internal-suffix hosts, and a shot whose page loaded a response from a non-public address, or from one the capture could not determine, is discarded; the card keeps its title and preview image. The sidecar resolves names itself, though: a page can still make it send a request to an internal address through a public name that resolves privately, and WebSockets and fetches a worker starts are not observed. The response never reaches the board, but restrict the sidecar's own egress if the request alone matters to you.
  • Two defaults — setting a password runs the breached-password check, which sends five hex characters of the password's SHA-1 and compares locally, and an AI client that registers with a client-ID metadata document has that https document fetched, from public addresses only. AIR_GAP=1 turns both off.

Plan limits are enforced only with VITRINKA_METERED=1 or configured Stripe billing, and AIR_GAP=1 blanks Stripe: an air-gapped install left unmetered runs every organization on Enterprise limits (billing settings). Either way nothing is reported anywhere.

The bundle

The enterprise bundle ships image.tar, LOAD-AIRGAP.md, SHA256SUMS, the six CLI binaries and cli/latest.json. Verify, load, point VITRINKA_IMAGE at the loaded image, set the switch:

shasum -a 256 -c SHA256SUMS
docker load -i image.tar

Workstations get the CLI from the bundle's cli/ directory, and VITRINKA_RELEASE_FEED points vitrinka update at your internal mirror so the self-updater keeps working offline — it downloads a raw binary and its checksum, stages it beside the running one, executes the staged copy once, and only then swaps. The full install and upgrade routine is in Operations.

Licensing offline

The licence is a vendor-signed file verified inside the server process against the vendor key built into the release: no activation call, no licence server, no phone-home, and renewal is a file swap and a restart (Licensing). Keep the host clock right from your own time source — expiry is read against it, and a licence that looks issued in the future refuses the boot.

AIR_GAP=1 blanks the public integrations. It does not blank a misconfigured internal endpoint — an SMTP relay or S3 endpoint that points at the internet is still yours to fix — and it does not switch off an organization's own AI provider. On an air-gapped install that provider answers only at an endpoint on your network that deployment authority saved (Outbound targets) and never at a provider's public default endpoint, so point it at a model endpoint on your network, or leave it unset.

On this page