Skip to content

Join a cluster (edge node)

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.

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:

Terminal window
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.

On a fresh Ubuntu 24.04/26.04 LTS x86_64 box:

Terminal window
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-west

The legacy flag names --cluster-token/--origin-ip/--origin-url are still accepted as aliases.

  1. Same gates as a standalone install — OS/arch check, port preflight, Docker install — but no nginx/certbot: an edge node never terminates TLS itself.

  2. POST /api/v1/cluster/join on the origin, authenticated with the header X-Cluster-Token: <token> (a dedicated, lower-privilege, separately-revocable credential — not the admin sk_ token). The origin registers the node in its nodes registry 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.

  3. Writes the edge’s .envLIVEKIT_REDIS_ADDRESS/LIVEKIT_REDIS_PASSWORD (the origin’s redis) and LIVEKIT_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-deps matters: an edge must not start a local redis (Compose’s depends_on would 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.

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.

Terminal window
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.