Skip to main content

Deploy

Heldar is built to run as one binary at one URL. The composing server (heldar-server, the heldar-core binary) serves the JSON API, the recorded media, the metrics/health endpoints, and the built dashboard from a single process.

Three ways to run it, in order of effort:

  • Docker (pull & run): docker compose -f deploy/compose.yml up -d — or the quickstart one-liner. Prebuilt open images, no toolchain.
  • Native binary — build from source and run it (this page).
  • Flashed appliance — a native-systemd OS image for a dedicated DVR box (no Docker on the appliance; see infra/systemd/ in the repo).

One binary, one URL

Build the dashboard, then point the server at it with HELDAR_WEB_DIR:

cd apps/web && npm install && npm run build # writes apps/web/dist
# in .env:
HELDAR_WEB_DIR=./apps/web/dist

When HELDAR_WEB_DIR is set and the directory exists, the server serves the SPA as a fallback. The explicit routes take precedence and the SPA is only a fallback for everything else:

  • /api/* - the JSON API.
  • /media/recordings, /media/clips, /media/snapshots, /media/playback, /media/archives - static media served from the data dir.
  • /healthz (liveness), /readyz (readiness, runs SELECT 1), /metrics (Prometheus exposition).
  • everything else - the dashboard, with unknown client-routed paths falling back to index.html so deep links return 200.

If HELDAR_WEB_DIR is unset it defaults to apps/web/dist relative to the binary's working directory. When neither exists, the server runs API-only and logs that the dashboard is not served.

Ports

PortService
8000Heldar Core HTTP API + dashboard (HELDAR_API_HOST / HELDAR_API_PORT)
5173Vite dev server (development only; not used in the single-binary deploy)
8554 / 8888 / 8889MediaMTX RTSP / HLS / WebRTC
9997MediaMTX control API (loopback)

Live view is brokered through MediaMTX: camera credentials live only in the gateway's path config and never reach the browser, which only ever sees the non-credentialed HLS/WebRTC/RTSP URLs.

Authentication

Auth and RBAC are opt-in via HELDAR_AUTH_ENABLED (default false).

  • false - open API, suitable for a single-tenant LAN appliance. The admin surface is reachable without a token and acts as admin.
  • true - every request needs a session (login) or an X-API-Key. Five roles are enforced (admin / manager / guard / viewer / integration) across capabilities, and every mutation is written to an immutable audit log. On first run with no users, an admin is seeded from the bootstrap env.

Sessions use an HttpOnly, SameSite=Strict cookie. Set HELDAR_AUTH_COOKIE_SECURE=true behind TLS (keep false for plain-HTTP LAN or overlay access). Tune the session lifetime with HELDAR_SESSION_TTL_HOURS (default 12), and optionally expire idle sessions with HELDAR_SESSION_IDLE_TIMEOUT_MIN (default 0, i.e. no idle timeout).

Brute-force lockout is on by default: an account is locked after HELDAR_LOGIN_MAX_FAILURES (5) consecutive failed logins for HELDAR_LOGIN_LOCKOUT_MIN (15) minutes — refused even with the correct password, auto-unlocking when the window passes (an admin can clear it sooner via POST /api/v1/users/{id}/unlock).

Set HELDAR_AUTH_ENABLED=true for any multi-user or networked deployment.

:::tip Internet-exposed? Harden it first. For a deployment reachable from the public internet, the kernel fails loud on an unsafe configuration — it refuses to boot with auth off behind a rendezvous, and warns (or refuses, under HELDAR_STRICT_PROD=true) on a non-Secure cookie, no idle timeout, or plaintext camera credentials. Work through the Production hardening checklist — auth, TLS, camera-credential encryption (HELDAR_SECRET_KEY), and an optional Cloudflare Turnstile login challenge — before going live. :::

Storage and the data dir

Heldar uses SQLite only (WAL journal, embedded migrations). The default URL is sqlite://./data/heldar.db.

VarDefaultMeaning
HELDAR_DATABASE_URLsqlite://./data/heldar.dbSQLite only; a non-sqlite URL is rejected at boot
HELDAR_DATA_DIR./dataroot for the DB and media subdirs
HELDAR_RECORDINGS_DIR / CLIPS_DIR / SNAPSHOTS_DIR / FRAMES_DIRunder ./datamedia roots (created at boot)
HELDAR_MAX_RECORDINGS_GB20soft footprint cap; oldest unlocked segments are pruned past it
HELDAR_MIN_FREE_DISK_GB5hard host-protection floor; prunes unlocked segments while free space is below it

Recordings stay on the local disk and are served from there; nothing is pushed to a cloud by default. Evidence-locked segments are never deleted by retention. Both limits are also runtime-tunable without a restart — from the dashboard System page ("Recording limit" panel) or via GET/PUT /api/v1/system/retention (PUT is admin-only); a stored override shadows the env value, which remains the default.

Camera credentials live in this SQLite DB. Set HELDAR_SECRET_KEY (base64 of 32 bytes, e.g. openssl rand -base64 32) to encrypt them at rest with AES-256-GCM; unset keeps plaintext for a trusted LAN appliance. Existing plaintext credentials are sealed on the next boot — see the Production hardening guide.

CORS

HELDAR_CORS_ORIGINS controls cross-origin access. Empty or * allows all origins; otherwise it restricts to the configured list (the default allows the Vite dev server). In a single-binary deploy the dashboard is same-origin, so CORS is mostly relevant when a separate frontend or integration calls the API.

Operating a deployment

For sizing, commissioning, observability, and remote access — including bringing your own STUN/TURN via HELDAR_WEBRTC_ICE_SERVERS — see the Operate hub.