Skip to content
VaultTerm
Browse docs

self-hosting

Install with Docker Compose

Connected install: configure .env, build and start the stack, verify health, and put a TLS reverse proxy in front.

Updated Jun 23, 2026

This is the connected install — a host with Docker and outbound network access. For an install with no network, see Air-gapped install.

Prerequisites

  • Docker Engine with the Compose plugin (docker compose).
  • The VaultTerm repository on the host (the app image is built locally on a connected install).

Configure .env

All files live in deploy/onprem/. Copy the template and edit it; docker compose reads .env automatically.

cd deploy/onprem
cp .env.example .env

At minimum, set these before first boot. Generate each secret with openssl rand -hex 32.

VariableWhat it is
POSTGRES_PASSWORDpassword for the bundled Postgres
JWT_SECRETsession signing key, at least 32 bytes; boot fails under production with a weak or default value
DEV_MASTER_KEY32-byte hex master key for the local key provider; wraps every stored credential
APP_BASE_URLthe public HTTPS URL where users reach the portal, including any base path
RP_ID / RP_ORIGINWebAuthn registrable domain (host only) and full origin; needs HTTPS or localhost
POSTGRES_PASSWORD=<strong-db-password>
JWT_SECRET=<openssl rand -hex 32>
DEV_MASTER_KEY=<openssl rand -hex 32>
APP_BASE_URL=https://vault.example.com
RP_ID=vault.example.com
RP_ORIGIN=https://vault.example.com

DEV_MASTER_KEY is the one value you cannot regenerate: losing it makes vault contents unrecoverable. Back it up out-of-band. See Core and database config for the database and URL variables, and Backups and recovery for the master key.

Start the stack

docker compose up -d --build
docker compose logs -f app        # watch migrations apply, then "starting VaultTerm"
curl -fsS http://localhost:4000/health

The app container applies database migrations on boot, then serves the API and web portal on :4000. A successful /health response means the stack is up.

Put a reverse proxy in front

The stack serves plain HTTP on :4000. Terminate TLS in a reverse proxy — WebAuthn and secure cookies require HTTPS, and localhost is the only exempt origin. A ready nginx sample ships in the bundle at deploy/onprem/reverse-proxy.example.conf, configured for post-quantum hybrid TLS key exchange (X25519MLKEM768), which protects the captured session against later quantum decryption and falls back to classical key exchange for clients that don’t offer the hybrid group.

server {
    listen 443 ssl;
    http2 on;
    server_name vault.example.com;

    ssl_certificate     /etc/ssl/vaultterm/fullchain.pem;
    ssl_certificate_key /etc/ssl/vaultterm/privkey.pem;
    ssl_protocols TLSv1.3 TLSv1.2;
    ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1;

    location / {
        proxy_pass http://127.0.0.1:4000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 1h;
    }
}

The hybrid group requires nginx built against OpenSSL 3.5+; older OpenSSL silently serves classical-only key exchange. The WebSocket headers above are required for the interactive terminal, port-forwarding, and multiplayer sessions.

Keep the admin plane private

Proxy :4000 only. The admin/platform plane on :4100 binds to loopback by default and must stay off the public internet — reach it over an SSH tunnel or a trusted private interface. Do not add a public proxy location for it.

Next