Self-hosting Open Live

Guide · September 2026 · 13 min read

Open Live is open source and you can run the whole thing yourself without paying us anything. This guide covers what the system is made of and under which licences, how to stand it up, every environment variable that matters, the one security decision self-hosting hands you, and an honest account of what you take on. It is written so you can make the build-or-buy call on real detail rather than on a marketing page.

Why we publish this. The reason to run Open Live on Open Source Cloud should be that operating it yourself is not worth your time, not that leaving is difficult. If this guide talks you into self-hosting, that is a legitimate outcome.

What it is made of

Component What it does Source Licence
open-live The API server. Productions, sources, templates, and the WebSocket controller channel. Eyevinn/open-live MIT
open-live-studio The browser production controller. Eyevinn/open-live-studio MIT
Strom The GStreamer flow engine that does the actual video work. This is the part that needs the GPU. Eyevinn/strom Dual MIT / Apache-2.0
CouchDB Stores productions, sources and templates. Upstream project Apache-2.0

Strom carries both LICENSE-MIT and LICENSE-APACHE, the usual Rust dual-licence, so you may take it under either. Do not repeat the shorthand that it is Apache-only; that is what GitHub's single-licence field displays, not what the repository grants.

Requirements

Standing it up locally

The API server repository ships a reference docker-compose.yml that brings up CouchDB 3 alongside open-live, which is the fastest way to see the thing run. From source:

pnpm install
cp .env.example .env
# fill in the values, then
pnpm dev        # hot reload
pnpm typecheck  # type-check without emitting
pnpm build      # compile to dist/
pnpm start      # run the compiled server

The REST API is documented in docs/openapi.yaml and served from the running instance at /documentation. The WebSocket controller channel has its own document in the repository, covering authentication, inbound message types and outbound broadcasts.

Configuration

Read from src/config.ts and src/main.ts rather than from the readme, which is stale on several of these.

Variable Purpose Default
PORT Port the API server listens on 3000
COUCHDB_URL CouchDB connection URL. Credentials may be embedded, or supplied separately via COUCHDB_USER and COUCHDB_PASSWORD, which are merged in when the URL carries no password of its own. required
PUBLIC_BASE_URL The externally reachable URL of this service. A hard production startup guard and a security control, not a convenience setting. See the warning below. unset, and the server refuses to start without it under NODE_ENV=production
API_KEY Static key protecting /api/v1 routes and the WebSocket controller. The second hard production guard. unset
TRUST_EXTERNAL_AUTH Declares that API_KEY is unset on purpose because something in front authenticates. Only meaningful when API_KEY is unset. false
SRT_PASSPHRASE_KEY Key used to encrypt SRT passphrases at rest. Fails closed in production on the encrypt and decrypt path. Precision worth keeping: this is not one of the two startup guards, so a deployment without it starts and then fails the moment a passphrase is handled. .env.example says the server will not start without it; that is overstated. unset
CORS_ORIGIN Allowed origin or origins. Accepts a comma-separated list or *. unset, and unset means cross-origin is disabled, not opened. The server sets origin: false and emits a [security] warning. A regression test guards this so it cannot silently become permissive. The readme and .env.example each state a different, wrong default.
TRUSTED_HOSTS Comma-separated allow-list of hostnames permitted when a WHIP callback URL is derived from the request because PUBLIC_BASE_URL is unset. A request whose derived host is not listed is rejected rather than persisted. empty
STROM_URL Base URL of the Strom pipeline engine http://localhost:7000
STROM_AUTH_MODE osc exchanges a Personal Access Token for a short-lived Service Access Token against OSC's token service. direct uses the API key as a bearer token directly, and is the mode a self-hosted Strom needs. osc
STROM_AUTH_TOKEN The token itself. STROM_TOKEN is still read as a legacy fallback, but this is the current name. unset
LOG_LEVEL trace to error info
STROM_PORT_LEASE_SIZE Consecutive SRT listener ports leased from a shared Strom 20
STROM_PORT_LEASE_CLIENT_ID Override for the lease client id sent to Strom hostname of PUBLIC_BASE_URL, else open-live-<hostname>
STROM_PORT_LEASE_DISABLED Skips port leasing entirely, for single-tenant Strom setups false

COUCHDB_NAME does nothing, whatever the readme says. The database name is hardcoded to open-live in src/db/index.ts, and the variable appears nowhere in the source. Setting it has no effect.

PUBLIC_BASE_URL is a security control. Without it the activation endpoint derives WHIP endpoint URLs from the X-Forwarded-Host request header, which an attacker can forge to redirect camera streams to a server they control. That is why it is a hard startup guard in production rather than a warning. If you deliberately leave it unset outside production, set TRUSTED_HOSTS.

Authentication is the decision self-hosting hands you

When API_KEY is unset, every API route is unauthenticated. Any client that can reach the service can create, modify, delete and activate productions.

The project does not leave this to chance, and the behaviour is worth knowing exactly. With NODE_ENV=production and no API_KEY, the server refuses to start unless TRUST_EXTERNAL_AUTH=true declares that a trusted layer in front is handling it. Outside production it logs a prominent warning instead. The reference docker-compose.yml will not bring the stack up without a key either.

The reasoning behind that pair is worth borrowing: the deployment tier and the authentication architecture are different questions, so NODE_ENV alone cannot stand in for the second one. On Open Source Cloud the reverse proxy authenticates every request before it reaches the API, which is the case TRUST_EXTERNAL_AUTH exists for. Self-hosted, that proxy is the part you are replacing.

Generate a real key rather than inventing one:

openssl rand -base64 32

HTTP clients send it as Authorization: Bearer <API_KEY>. Note that /health, /ready and /api/v1/status stay exempt even when the key is set, so your probes keep working.

WebSocket clients do NOT pass the key as ?key=. That was removed on purpose and the server no longer accepts it. A static, non-expiring key in a URL leaks into proxy, CDN and browser DevTools access logs as permanent credentials. Both the readme and docs/controller-websocket.md still document the old form; the source and its tests are the authority, and a client built from the readme will get 401s.

The current mechanism works around the same browser limitation a different way. A browser cannot set arbitrary headers on a WebSocket handshake, but it can offer subprotocols. The client offers two: the plain marker openlive.bearer, and openlive.bearer.<key> carrying the key. The server reads the key from the second and echoes back only the plain marker, so the secret never appears in the handshake response header either.

new WebSocket(url, ["openlive.bearer", `openlive.bearer.${apiKey}`])

Pointing the studio at your backend

The studio reads one setting, and it is not a VITE_ variable. vite.config.ts sets envPrefix: ['OPEN_LIVE_'], which replaces Vite's default VITE_ prefix rather than adding to it, so a VITE_-prefixed variable is never exposed to the bundle at all. The variable is OPEN_LIVE_URL, and its default is http://localhost:3000.

The runtime value wins, so you do not need to rebuild to repoint it. src/lib/base.ts resolves in this order:

window._env_?.OPEN_LIVE_URL || import.meta.env.OPEN_LIVE_URL || "http://localhost:3000"

docker-entrypoint.sh writes window._env_ into env-config.js when the container starts, and the non-Docker pnpm start path does the same into dist/. Set OPEN_LIVE_URL in the environment and restart; a rebuild is not required on either path.

If the studio is talking to the wrong backend, check env-config.js in the served directory before you reach for a rebuild. Note also that docker-entrypoint.sh requires jq and fails closed without it, deliberately, rather than falling back to manual escaping.

Kubernetes: what you get, and what you do not

The product is described on this site as deployable on any CNCF-conformant Kubernetes cluster. The repositories themselves say only "any Kubernetes cluster". It is worth being precise about what the repositories actually ship, because it is not a chart.

The useful reference is the topology Open Source Cloud itself runs, because it is documented and it works: CouchDB (apache-couchdb), Strom (eyevinn-strom), and a parameter store (eyevinn-app-config-svc with valkey) injecting environment variables at runtime so no .env file sits on the server. Four services, not one, and the parameter store is the piece people forget when they plan this as a two-container deployment.

What you are taking on

Concern Self-hosted On Open Source Cloud
Licence cost None. MIT and dual MIT/Apache-2.0. None. Same software, same licences.
Authentication You build and operate it, or you run with an open API. Reverse proxy authenticates before the API.
GPU capacity You buy, size and keep a GPU host alive. This is the real cost and the real operational burden. Three options, and only two are for live production. The shared Frankfurt GPU on Professional is marked demo and evaluation only because capacity is not guaranteed. For live production it is either the Hosted GPU add-on, from 750 EUR/month, which our pricing section describes as guaranteed capacity though the platform pricing page lists no GPU add-on and no SLA is published behind the phrase, or bring your own Strom, which puts you back in the left column for this row.
CouchDB Yours to run, back up and restore. Provisioned by the create flow, password generated for you.
Kubernetes manifests You write and maintain them. Not applicable.
Upgrades Yours to schedule, test and roll back. Managed.
Leaving Not applicable. Same open source software, so the exit is a redeploy rather than a migration.

The honest summary: the API server and the studio are ordinary Node services and are not hard to run. Strom and the GPU host are the real work, and so is being the person who fixes a pipeline at 19:55 on a Saturday.

Pre-production checklist

Prefer not to operate it?

Same software, hosted and managed. Bring your own Strom alongside Personal at 15 EUR/month, evaluate on the shared Frankfurt GPU at 69 EUR/month, or take the Hosted GPU add-on from 750 EUR/month when you need guaranteed capacity for live production. Compare those against what you just read.