Architecture overview
This content is for the 1.0 version. Switch to the latest version for up-to-date documentation.
StreamHub is a management layer over a self-hosted LiveKit SFU. LiveKit handles the real-time media plane (WebRTC fan-out, ingest, recording); StreamHub adds everything a self-hosted AntMedia-style product needs around it: multi-tenant apps, a REST API (global + per-app), a React dashboard, per-app S3 recording, an embeddable player and browser SDK, signed webhooks, RBAC + quotas, and Prometheus metrics — all reachable behind a single domain with automatic TLS.
It runs single-node today. The data model and a node registry already exist so that scaling out to an origin + edge cluster (see Cluster) is additive, not a rewrite.
One-paragraph mental model
Section titled “One-paragraph mental model”A browser, OBS, an ESP32-CAM or a native SDK publishes into a room belonging to an app — over WebRTC, RTMP, WHIP, or an RTSP→RTMP relay (LiveKit has no native RTSP pull). LiveKit is the SFU that fans the media back out. streamhub-core (NestJS) is the brain: it mints tokens, drives ingress/egress through the LiveKit server SDK, persists state in SQLite (a small global database plus one database per app), records rooms to MP4 and uploads them to the app’s own S3 bucket as VODs, serves the React dashboard, HLS playlists, embeddable sample pages and the browser SDK, emits Prometheus metrics, and fires HMAC-signed callbacks on every event. A single reverse proxy (Caddy or nginx) fronts all of it on one TLS domain; WebRTC media (UDP) and RTMP ingest bypass the proxy and hit the server’s IP directly.
Request routing (single domain)
Section titled “Request routing (single domain)”Everything lives under one hostname. The reverse proxy only needs to send /rtc to LiveKit’s
signaling port and everything else to the core:
https://streamhub.example.com /rtc → livekit-server :7880 (wss signaling / WebRTC upgrade) /api/v1/* → streamhub-core :3020 (REST API, Bearer sk_ / JWT) /hls/* → streamhub-core :3020 (HLS playlists + segments) /sdk/* → streamhub-core :3020 (streamhub-adaptor browser SDK) /samples/* → streamhub-core :3020 (public per-app embed pages, auth-less) /metrics → streamhub-core :3020 (Prometheus, optional token) /* → streamhub-core :3020 (React SPA)Three things are not proxied — clients reach them on the server’s IP directly, because they carry real-time media or high-throughput ingest that a TLS-terminating HTTP proxy shouldn’t sit in front of:
| Traffic | Port | Protocol |
|---|---|---|
| WebRTC media (LiveKit’s single UDP mux port) | 7882/udp |
SRTP |
| RTMP ingest | 1935/tcp |
RTMP |
| WHIP ingest (WebRTC-HTTP ingest) | 8080/tcp |
HTTP/WebRTC |
See Services for the full port table and what runs behind each one.
One core image serves everything
Section titled “One core image serves everything”Historically the dashboard was a separate Laravel/Livewire app. That’s gone: streamhub-core is
a single Node process that serves the REST API and the compiled React SPA as static assets,
plus HLS, the SDK, and sample embeds. One build, one service, one port (3020, bound to
127.0.0.1).
The multi-stage deploy/Dockerfile builds all three pieces into one image:
- Builds
streamhub-web(the React SPA) → copied into the core image’s./webdirectory, served viaServeStaticModule. - Builds
streamhub-adaptor(the browser SDK, an IIFE bundle) → copied into<DATA_DIR>/sdkby the entrypoint at boot, not baked into the image build. - Compiles the NestJS core itself.
The practical consequence: SPA changes ship by rebuilding the core image. There’s no
separate frontend deploy or container — docker compose up -d --build (or the systemd
equivalent) picks up dashboard changes the same way it picks up API changes.
Auth planes
Section titled “Auth planes”StreamHub has three independent auth surfaces:
| Plane | Mechanism | Used by |
|---|---|---|
| REST API | sk_ bearer tokens — global or scoped to one app, optionally IP-allowlisted, seeded via deploy/seed-token.js |
External integrations, the API itself |
| Dashboard | JWT from POST /api/v1/auth/login, signed with STREAMHUB_JWT_SECRET; break-glass ADMIN_USER / ADMIN_PASS login |
The React SPA |
| Public playback | A public play-token for anonymous viewers | Embeddable /samples pages, public /play links |
RBAC (via casbin) and per-tenant quotas roll out gradually and are controlled by
STREAMHUB_AUTHZ_ENFORCE (off | log | on), so authorization can be observed in
production before it’s enforced.
Where to go next
Section titled “Where to go next”- Services — the Docker Compose services, ports, and resource caps
- Data model — per-app SQLite, the global/app split,
DATA_DIRlayout - Cluster — the origin + edge design and what’s live today vs. target
- Distribution — scaling playback past a single node’s NIC
- Edge compute grid — roadmap concept for GPU offload