Architecture decisions

Runtime and trust boundaries

The web client is a responsive React application. A Fastify process serves the built portal, authenticated APIs, WebSockets, and tenant-scoped domain services. Signed sessions establish identity and tenant context; no data-plane operation accepts an unverified tenant selector. Persistent state is written atomically under .haildeck-data, and tenant IDs, membership, permissions, classifications, quotas, and key IDs are enforced at each resource boundary.

The production Compose stack places Caddy at the TLS edge, uses authenticated Redis for LiveKit coordination, LiveKit for realtime media, Egress for durable MP4 creation, and coturn for NAT traversal. Application data uses a persistent volume; recordings use the host-mounted .haildeck-data/recordings directory so both the application and Egress can access the same artifact.

Write and realtime model

Commands validate input, authorize against the verified principal, persist the tenant-scoped change, append ordered events, and emit realtime updates. Client operation IDs make message retries idempotent. Per-user cursors, outboxes, presence, typing state, and conflict responses support reconnect and offline operation.

Authentication and integrations

Owner initialization is available only before the first account exists. Normal authentication supports Argon2id passwords, WebAuthn passkeys, TOTP, one-use recovery codes, revocable sessions, invitations, role administration, and adaptive rate limiting. Automation uses one-time service tokens with explicit scopes. Outgoing webhooks are HTTPS-only, signed, auditable, and explicitly dispatched or retried by an administrator.

Client evolution

The web portal remains the release client, and the desktop wrapper packages it behind a narrow native capability boundary. The Expo/React Native client in apps/mobile reuses the shared schemas and API contracts while providing mobile-native navigation, OS-bound credential storage, an encrypted offline cache, native passkeys, and LiveKit media. Native device registrations contain APNs/FCM routing credentials, while provider payloads carry only opaque event IDs and require an authenticated API fetch for content.

Scale path

The bundled durable store is appropriate for the current internal single-node self-hosted deployment and retains multi-tenant boundaries. The domain services keep persistence behind stable interfaces so PostgreSQL, object storage, external KMS, a durable event bus, and a dedicated search engine can be introduced when horizontal scale or compliance requirements justify them.

source: docs/ARCHITECTURE.md — rendered at build time; the repository copy is authoritative.