Skip to content

Self-hosting

Run korva-vault as a shared service for your team. Docker Compose ships in-tree with optional Traefik for HTTPS. systemd unit for Linux servers, launchd plist for macOS dev machines.

Updated: 2026-04-30

The Vault is designed to run on your laptop by default, but nothing prevents you from running it as a shared service on a VPS, in Kubernetes, or behind your reverse proxy. This page documents the three official deployment shapes.

Terminal window
docker pull ghcr.io/alcandev/korva-vault:latest
docker compose up -d vault

The Dockerfile is multi-stage:

  1. Stage Beacon (Node 22-alpine): builds the React 19 SPA → beacon/dist.
  2. Stage Vault (Go 1.26-alpine): copies dist into vault/internal/ui/dist/ and compiles with -tags embedui.
  3. Stage Runtime (alpine 3.21): runs as korva (uid 1001), volume mount at /data, exposes 7437.

A health check is wired in:

Terminal window
wget -qO- http://localhost:7437/healthz | grep -q '"status":"ok"' || exit 1

(interval 30 s, timeout 5 s, retries 3, start period 10 s).

Docker Compose

The repo ships docker-compose.yml with two services:

services:
vault:
image: ghcr.io/alcandev/korva-vault:latest
ports: ["7437:7437"]
volumes:
- korva-data:/data
- ${KORVA_ADMIN_KEY_PATH}:/data/admin.key:ro
# Optional Traefik for auto-HTTPS via Let's Encrypt
# traefik:
# ...
volumes:
korva-data:

To run vault only:

Terminal window
docker compose up -d vault

To run vault + the licensing server (operators only, profile teams):

Terminal window
KORVA_LICENSING_SECRET=change-me \
KORVA_LICENSING_KEY_PEM="$(cat priv.pem)" \
docker compose --profile teams up -d

Traefik for automatic HTTPS

The docker-compose.yml ships with Traefik labels commented out. Uncomment to expose the Vault at vault.yourcompany.com with auto-renewing Let’s Encrypt certificates:

labels:
- "traefik.enable=true"
- "traefik.http.routers.vault.rule=Host(`vault.yourcompany.com`)"
- "traefik.http.routers.vault.entrypoints=websecure"
- "traefik.http.routers.vault.tls.certresolver=letsencrypt"
- "traefik.http.services.vault.loadbalancer.server.port=7437"

Clients then point at the public host:

Terminal window
KORVA_VAULT_HOST=vault.yourcompany.com korva ...

macOS LaunchAgent (developer machines)

Terminal window
cp scripts/korva-vault.plist ~/Library/LaunchAgents/dev.korva.vault.plist
launchctl load ~/Library/LaunchAgents/dev.korva.vault.plist
  • Label: dev.korva.vault
  • RunAtLoad, KeepAlive on crash
  • Logs at ~/.korva/logs/vault.{log,error.log}
  • Restart throttle: 10 s

systemd user service (Linux servers)

Terminal window
mkdir -p ~/.config/systemd/user
cp scripts/korva-vault.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now korva-vault
  • Restart on-failure (5 s delay), max 5 restarts in 60 s
  • Logs via journald: journalctl --user -u korva-vault -f
  • Hardening: NoNewPrivileges, PrivateTmp, ProtectSystem=strict, ProtectHome=read-only, ReadWritePaths=~/.korva

Required environment variables

VariableDefaultPurpose
KORVA_VAULT_HOST127.0.0.1 for local, 0.0.0.0 for sharedBind address
KORVA_VAULT_PORT7437Listen port
KORVA_VAULT_DB/data/vault.db (Docker)SQLite path
KORVA_CORS_ORIGINhttp://localhost:5173CORS origin allowed (Beacon dev)
KORVA_EMAIL_API_KEY / KORVA_EMAIL_FROMunsetResend API for Teams invites
KORVA_LICENSING_ENDPOINThttps://licensing.korva.devLicensing server URL
KORVA_HIVE_ENDPOINThttps://hive.korva.devHive cloud brain
KORVA_HIVE_DISABLEunsetSet to 1 to kill Hive worker

Reverse proxy considerations

The Vault expects to terminate TLS at the proxy and receive cleartext HTTP/1.1 internally. CORS is configurable via KORVA_CORS_ORIGIN. Rate limit (120 req/min/IP) is enforced inside the Vault, so you don’t need a separate WAF rule for protection.

Observability

  • Health probe: GET /healthz returns {"status":"ok"}.
  • Status snapshot: GET /api/v1/status — version, uptime, license tier, total observations.
  • Metrics: GET /api/v1/metrics — Prometheus format.

Wire any of those into your monitoring stack.

Where the data lives in Docker

The named volume korva-data maps to /data inside the container:

PathWhat
/data/vault.dbSQLite database
/data/admin.keyAdmin credential (mode 0600, mounted read-only)
/data/license.keyActive JWS license token

Snapshot the volume daily for backup; SQLite supports hot backups if you use VACUUM INTO.

Next