Skip to content

Deployment guide

How to run the Kneo Agent Dashboard in production. The dashboard is one deploy unit — a FastAPI BFF that also serves the built SPA (browser UI) same-origin; there is no separate frontend to host (ADR-008). It reaches the Kneo Agent Platform through kneo-client over /v1.

For a ~10-minute first run see the Quickstart; for the full list of settings see the Environment-variable reference.

Two ways to run it

The container and the PyPI wheel are equivalent — both bundle the BFF and the built SPA (ADR-011), so pip install gives the full dashboard without Docker.

docker run --rm -p 8090:8090 \
  -e KNEO_URL="https://kneo-serv.internal" \
  -e KNEO_API_KEY="…" \
  -e KNEO_DASH_AUTH_MODE=oidc \
  -e KNEO_DASH_SESSION_SECRET="$(openssl rand -base64 32)" \
  -e KNEO_DASH_OIDC_ISSUER="https://idp.example/realms/kneo" \
  -e KNEO_DASH_OIDC_CLIENT_ID=kneo-dash \
  -e KNEO_DASH_OIDC_CLIENT_SECRET="…" \
  -e KNEO_DASH_OIDC_REDIRECT_URL="https://dash.example/api/callback" \
  -e KNEO_DASH_OIDC_ROLE_MAP='{"kneo-admins":"admin","kneo-ops":"operator"}' \
  -v kneo-dash-state:/var/lib/kneo-dash \
  ghcr.io/kneo-agent/kneo-dash:latest

The image serves on :8090 as a non-root user (uid 10001), bundles the SPA at /app/static (KNEO_DASH_SPA_DIR is preset), and defaults the state store to a SQLite file under /var/lib/kneo-dashmount a volume there so it survives restarts (or point KNEO_DASH_DB_URL at Postgres). Pin by digest in production.

pip install kneo-dash            # or 'kneo-dash[postgres]' for an external DB
export KNEO_URL="https://kneo-serv.internal" KNEO_API_KEY="…"
export KNEO_DASH_AUTH_MODE=oidc KNEO_DASH_SESSION_SECRET="$(openssl rand -base64 32)"
export KNEO_DASH_OIDC_ISSUER="https://idp.example/realms/kneo" \
       KNEO_DASH_OIDC_CLIENT_ID=kneo-dash KNEO_DASH_OIDC_CLIENT_SECRET="…" \
       KNEO_DASH_OIDC_REDIRECT_URL="https://dash.example/api/callback" \
       KNEO_DASH_OIDC_ROLE_MAP='{"kneo-admins":"admin","kneo-ops":"operator"}'
kneo-dash                        # serves UI + API on http://127.0.0.1:8090

The published wheel bundles the browser UI — the BFF serves it same-origin, so pip install is the full dashboard, identical to the container. (A pip install -e . dev checkout that hasn't built the SPA runs API-only — /api/* with no UI.)

The kneo-dash console script runs the server (serve, the default). It also exposes the out-of-band kneo-dash recover break-glass path used after a restore — see the backup & recovery guide.

Compose (with a TLS-terminating proxy)

examples/docker-compose.prod.yml runs the dashboard behind an nginx reverse proxy (examples/nginx.conf) that terminates TLS and forwards to :8090. Copy examples/.env.example, fill in the platform + OIDC values, and docker compose -f examples/docker-compose.prod.yml up -d. The OIDC redirect URL must be the public https://…/api/callback the browser reaches.

State store: SQLite vs Postgres

SQLite (default) Postgres
When single replica; the common case multi-replica / HA
Config unset KNEO_DASH_DB_URL (container → /var/lib/kneo-dash/state.db on a volume) KNEO_DASH_DB_URL=postgresql://… + pip install 'kneo-dash[postgres]'
Support supported best-effort

Schema migrations run automatically on startup (forward-only); a database whose schema is newer than the running image fails fast rather than risk a partial downgrade. Keep the image and the database in step across upgrades.

State-store growth & disk sizing

The store is append-heavy — launch history and the audit log grow over time (sessions are purged automatically; see KNEO_DASH_SESSION_PURGE_INTERVAL_SECONDS). Size the state volume for steady growth and monitor it:

  • kneo_dash_store_state_bytes (on the /metrics surface) reports the complete SQLite footprint — the main DB plus its -wal/-shm sidecars. Alert on sustained growth, not an absolute number. (The series is absent on Postgres — size the database with your normal PG tooling there.)
  • kneo_dash_store_rows{table="audit_log"|"launches"} and kneo_dash_sessions_active (0.8.0) report the row counts behind that footprint — watch these to decide when to prune.
  • Retention/pruning ships (0.8.0): the operator-safe kneo-dash prune CLI bounds launch-history/audit growth — export-before-delete (versioned + checksummed) with a self-audit record. Size the volume for steady-state between prunes, and prune on the row-count signal above.

Health probes

Three endpoints, with HTTP codes chosen so a degraded dependency is never mistaken for a dead process — the split exists specifically to avoid a restart-loop footgun:

Endpoint Use as Behaviour
GET /api/livez liveness probe 200 iff the process is up — no dependency checks. Never fails on a store/OIDC hiccup, so an orchestrator won't restart a healthy process.
GET /api/readyz readiness / traffic gate 200 when the state store answers and the instance isn't in recovery mode; 503 otherwise (the proxy de-routes it).
GET /api/healthz dashboards / humans always 200 + a JSON body reporting liveness, readiness, and dependency status (incl. OIDC). A degraded dependency shows in the body, never as a 503 — so it can't be misused as a liveness probe.

Point the orchestrator's liveness probe at /api/livez and its readiness probe at /api/readyz. /api/healthz is for humans and monitoring, not probes.

Post-deploy verification

"Did it come up correctly?" The Dashboard ships no operator smoke script — this is a manual curl + UI checklist. Run it after every deploy/upgrade.

1. Probes (curl). From a shell that can reach the BFF:

curl -fsS https://dash.example.com/api/livez    # → 200 {"status":"alive"} — the process is up
curl -fsS https://dash.example.com/api/readyz    # → 200 when the store answers + NOT in recovery
  • readyz 503 = the state store is unreachable or the instance is in recovery (a detected restore). Check GET /api/healthz (always 200) for the JSON breakdown; if it's recovery, run kneo-dash recover --status and reconcile before serving.

2. UI golden path (logged in). Sign in (OIDC) and walk one run end-to-end — each step with its expected result:

  • [ ] Login → you land on the Overview with your role's nav (a wrong/empty role = an OIDC role-map problem — see security hardening).
  • [ ] Launch a spec — Load → Deploy → Run — Deploy goes green, Run enables, and you route to the new run's detail.
  • [ ] Trace tail — open the run's Trace tab; live events stream (SSE) as it executes.
  • [ ] Audit read — open Audit (needs audit.read) and confirm the run/launch shows in the platform timeline.

If all four pass, the platform connection, auth, capability enforcement, SSE, and audit path are all live. This mirrors the end-to-end operator flow (a scripted version is a future tutorial); keep it in your runbook.

Operational notes

  • Run as non-root. The image already does (uid 10001); keep it that way behind your orchestrator's security context.
  • Authentication. Use KNEO_DASH_AUTH_MODE=oidc for any shared/production deploy — the default static mode is unauthenticated and refuses to start without KNEO_DASH_DEV_MODE=1. See the security-hardening checklist.
  • Same-origin only. The shipped image serves the SPA same-origin; credentialed cross-origin hosting is out of scope (ADR-009). Leave KNEO_DASH_CORS_ORIGINS empty.

See also