Join a cluster (edge node)
This content is for the 1.0 version. Switch to the latest version for up-to-date documentation.
A fresh box can join an existing StreamHub install as a media/edge node — it runs LiveKit, ingress, and egress, but no core and no database of its own; the control plane (API, dashboard, SQLite, jobs) stays on the origin.
Origin prerequisite
Section titled “Origin prerequisite”The origin must expose its redis to the edge on a private address before an edge can share its coordination state. Do this once, on the origin:
curl -fsSL https://www.streamhub.studio/install.sh | sudo bash -s -- \ --cluster-redis-bind <origin-private-ip>ufw allow from <edge-ip> to any port 6379 proto tcp--cluster-redis-bind <ip> makes redis also listen on that (private!) address, protects it with
a generated password, and advertises redis://:<pass>@<ip>:6379 to nodes that join afterward.
Repeat the ufw allow from line for each edge IP you add. Never expose 6379 to the public
internet — a private network or VPN between origin and edges is strongly recommended.
Join, on the edge
Section titled “Join, on the edge”On a fresh Ubuntu 24.04/26.04 LTS x86_64 box:
curl -fsSL https://www.streamhub.studio/install.sh | sudo bash -s -- \ --join \ --master-token clt_... # from the origin's install summary / its .env STREAMHUB_CLUSTER_TOKEN --master-ip 10.0.0.10 # the origin's IP — edges share its redis --master-url https://media.example.com # origin's API base (default: http://<master-ip>:3020) --node-name edge-ams-1 --region eu-westThe legacy flag names --cluster-token/--origin-ip/--origin-url are still accepted as
aliases.
What join does
Section titled “What join does”-
Same gates as a standalone install — OS/arch check, port preflight, Docker install — but no nginx/certbot: an edge node never terminates TLS itself.
-
POST /api/v1/cluster/joinon the origin, authenticated with the headerX-Cluster-Token: <token>(a dedicated, lower-privilege, separately-revocable credential — not the adminsk_token). The origin registers the node in itsnodesregistry and hands back the LiveKit API key/secret (which must be identical cluster-wide) and the redis URL — including the password, when the origin was set up with--cluster-redis-bind. No keys are ever shared by hand. -
Writes the edge’s
.env—LIVEKIT_REDIS_ADDRESS/LIVEKIT_REDIS_PASSWORD(the origin’s redis) andLIVEKIT_WEBHOOK_URL(pointed at the origin’s core, since no core runs on an edge) — then starts only the media services:Terminal window docker compose up -d --no-deps livekit ingress egress--no-depsmatters: an edge must not start a local redis (Compose’sdepends_onwould otherwise bring one up) — it coordinates entirely through the origin’s shared redis.
LiveKit’s shared-redis coordination is what gives WebRTC session affinity: a room is served
by exactly one node, chosen by LiveKit itself, and any signaling client that lands on a different
node in the cluster gets routed to the room’s owner. Media (7882/udp, RTMP 1935,
WHIP 8080) always goes straight to that owning node’s public IP.
Re-runs
Section titled “Re-runs”Joining is idempotent and safe to re-run: an already-joined edge re-registers whenever the origin answers (picking up rotated keys automatically) and keeps its existing config, with a warning, if the origin is temporarily unreachable.
Verify
Section titled “Verify”curl -s https://<origin-domain>/api/v1/cluster/nodes \ -H "Authorization: Bearer $STREAMHUB_TOKEN"Lists every registered node with its last_seen_at and a derived stale flag (true once a node
hasn’t reported in over 90s). From the dashboard’s cluster manager (or
PATCH /api/v1/cluster/nodes/{id}) you can also mark a node draining — it stops receiving new
rooms while letting existing ones finish — or disabled.
For the full target design (origin + edge topology, the WebRTC-vs-HLS/CDN reality check for mass audiences, and what’s still roadmap), see the Architecture docs.