Installing on your own host
One Go binary, one container, your infrastructure. The self-hosted bundle installs vitrinka on a single host with Docker Compose — and that is the only supported topology, deliberately.
The supported shape
Compose on one box, your reverse proxy for TLS, filesystem or your own S3 for blobs, your identity provider for SSO. Beyond a single host, the answer is a managed instance rather than scaling this yourself — that boundary is intentional, not an omission.
Install
Two required values, then bring it up. The binary migrates its own schema on boot.
cp .env.example .env
# edit .env: BASE_URL and VITRINKA_MASTER_KEY
openssl rand -hex 32 # generates a master key
docker compose up -dPut a proxy in front
Your reverse proxy terminates TLS and forwards to the published port (default 8080). BASE_URL must be the public URL your users actually reach. The app never terminates TLS itself.
Have the proxy overwrite X-Real-Ip with the client's address, and declare it: VITRINKA_TRUSTED_PROXIES=<proxy address>/32 (comma-separated CIDRs, the narrowest range that covers the proxy and nothing else). The rate limiter and every audit row take the client address from that header only when the request comes from loopback or a declared range; from any other peer the header is ignored and the connection's own address is used, so a caller cannot write an arbitrary address into the log. Undeclared, every proxied request shares the proxy's own rate-limit budget and the audit log records the proxy's address instead of the client's. A proxy on the Docker host that reaches the published port arrives through Docker's bridge gateway, not loopback, so declare that gateway. Any address inside a declared range can speak for any client, so never list a supernet.
BASE_URL must be https in production, or session cookies lose their Secure flag. The server warns loudly on boot when it isn't.
The first admin
Being first is not special: a new account with no workspace to join gets an organization and a first workspace of its own at its first sign-in. What runs the install is the platform console at /admin — plans, suspension, waitlist approvals, AI payment review (the admin console) — and it opens only to platform admins. Sign in once, then grant your account from the server binary:
docker compose exec vitrinka /vitrinka platform-admin grant you@example.comThe grant applies on your next page load, with no restart, and is audited as platform.admin.grant. It prints how the account signs in and when it was created: an address belongs to whoever registered it first, so check it is the account you meant. platform-admin revoke <email> and platform-admin list complete the verb. If you turn on forced SSO, create your account through your identity provider.
Then settle three things in .env and restart:
- Who may sign up. Until you act, anyone who reaches
BASE_URLcan open an account.VITRINKA_WAITLIST=1holds every new address, on every sign-in path including SSO, until it is approved in/admin; an address with a pending invitation passes, and existing accounts always sign in. Set it after your own grant: a waitlisted sign-in, yours included, waits for an approval only a platform admin can give. - Plan limits. With
VITRINKA_METEREDunset, every organization runs on Enterprise limits — 500 GiB of pooled storage, unlimited seats, 250,000 active boards per workspace — and with sign-up open, every new account's own organization gets that allowance too.VITRINKA_METERED=1enforces each organization's stored plan instead, set in/admin; a new organization starts on Free. - Who holds the deployment-wide settings. The deployment mailer, SSO email-domain claims, turning forced SSO on while claims are on, deployment-managed SSO connections (the only kind that reaches an identity provider on your internal network), and letting a tracker, AI backend or AI endpoint reach that network (Outbound targets) belong to platform admins.
VITRINKA_OPS_ORG=<slug>gives them to that organization's admins as well, and grants no data access. Name an organization that already exists — yours, for example, whose slug is usually your email's local part (jane@example.com→jane); a sign-up is never given that slug. Unset, an ordinary organization admin never qualifies, because any account that signs up can own an organization.
Upgrading an install where organization admins managed these settings? They lose the mailer, adding domain claims and turning forced SSO on while claims are on. Grant platform admin, or set VITRINKA_OPS_ORG, to keep them. The SMTP_* / RESEND_API_KEY environment keeps working as the fallback, and claims and forced connections saved before the upgrade stay as they were. A tracker, AI backend or AI endpoint on your internal network stops reaching it until deployment authority saves it once — after upgrading.
There is no migrate step
The schema migrates itself at startup. First boot creates everything; an upgrade applies whatever is pending. Every release migrates from every prior release — there are no upgrade hard stops.
Sizing
A container host that can run one image and mount one volume, plus a volume for /data and something to terminate TLS. Optionally an S3-compatible bucket if you would rather not keep blobs on disk.
CPU, RAM and disk figures are deliberately not stated: the supported topology is published, the sizing is not, and inventing a number would be worse than leaving it out. Ask, and it can be sized against your session volume and retention.