vibe-live docs

Deploying vibe-live: the built-in engine

No media server at all (F59). The app's own RTMP listener takes the publish, an ffmpeg this project compiles makes the renditions and the thumbnail, and the files are served from the app's own routes -- one process, one image, nothing beside it to configure or keep alive.

Everything in all-in-one.md about required variables, sign-in, notifications, federation, data and validating the install applies here too and is not repeated. What differs is the media half, and one thing is genuinely missing: see What it does not do.

There is no compose file for this engine. compose.yaml runs OvenMediaEngine and compose.livekit.yaml runs LiveKit, because each of those needs a second service wired to the first; this one needs a container and a port. The image is the deployment.

How the pieces fit

Ports

Why Symptom when it is closed
80/tcp, 443/tcp the site, and the ACME challenge if something in front gets its own certificate as in all-in-one.md
1936/tcp RTMP from encoders the settings page shows an address that refuses connections
10000/udp WebRTC media, only with VIBE_LIVE_WEBRTC_UDP_PORT=10000 streams negotiate and deliver nothing
3478/tcp WebRTC over TCP, for networks that block UDP -- only with the UDP port set those viewers fall back to HLS; those streamers cannot go live from the browser

With WebRTC on, the same five ports as every other engine, with the same jobs: all-in-one.md's OvenMediaEngine and livekit.md's LiveKit use 10000/udp and 3478/tcp too, so the router and firewall do not change with the engine. Without WebRTC, three: the site and RTMP. The UDP port is whatever VIBE_LIVE_WEBRTC_UDP_PORT names -- 10000 is only a convention, and the one to keep. 3478 is VIBE_LIVE_WEBRTC_TCP_PORT's default.

Configuration

Nothing is required beyond all-in-one.md's list, and VIBE_LIVE_OME_ADMISSION_SECRET is not in it -- that variable is required only when the engine is ome.

Variable Notes
VIBE_LIVE_ENGINE builtin. Unset infers it: no LiveKit settings, no OvenMediaEngine backend URLs, and an ffmpeg on PATH means this engine. Set it explicitly anyway if the deployment might grow an engine later -- it then refuses a second one rather than quietly switching.
VIBE_LIVE_MEDIA_DIR Where segments, playlists and thumbnails are written and served from. Unset is a directory under the system temp dir, which works and is not where you want it: name a tmpfs mount.
VIBE_LIVE_WEBRTC_UDP_PORT Turns on the app's WebRTC server. The ONE UDP port every viewer's media uses -- publish it, open it in the firewall, forward it from the router. Use 10000, the port every other engine uses.
VIBE_LIVE_WEBRTC_TCP_PORT ICE over TCP, with the UDP port set. Unset is 3478; 0 turns it off.
VIBE_LIVE_WEBRTC_PUBLIC_IPS Comma-separated addresses viewers are told to reach that port at: your public IP, plus your LAN address if viewers at home go through a router with no UDP hairpin. Unset advertises this host's own interfaces -- right on a LAN, wrong behind NAT.
VIBE_LIVE_MAX_TRANSCODES How many publishes may transcode at once; one past it is refused before ffmpeg starts. Unset is floor(CPUs / 1.25), sized from a measured ~1.15 cores per 1080p60 channel -- four on a six-core node. Every channel being watched over WebRTC is one more transcode under this cap.
VIBE_LIVE_RENDITIONS The owner's selection, as on every engine. Each one is an encode made only while somebody is watching it, so this is the load lever here.
VIBE_LIVE_DVR_WINDOW Caps how far back viewers can seek through a live stream, 1m to 24h; unset is the whole stream. Seeking is on unless an admin turns it off, and each live channel then records its source as sent -- copied, not re-encoded, so almost no CPU -- into VIBE_LIVE_RECORDINGS_DIR for as long as it is live: roughly the streamer's bitrate times the stream's length (6 Mbit/s for an hour is about 2.7 GB), deleted when it ends unless kept. The cap bounds that.
VIBE_LIVE_RECORDINGS_DIR Where recordings go: each live stream's, for seeking, and past broadcasts when an admin allows them and a streamer keeps theirs. Unset is recordings beside the SQLite database (/data/recordings in the image), so it is backed up with /data.
VIBE_LIVE_RTMP_LISTEN_ADDR The listener itself, :1936 unless you move it.
VIBE_LIVE_RTMP_PORT The port streamers are told, when a router publishes 1936 somewhere else. It changes the address on the settings page and nothing else.
VIBE_LIVE_TURN_* Engine-independent: it advertises ICE servers to the browser. This engine bundles no TURN of its own; ICE over TCP on 3478 serves a viewer whose network blocks UDP but lets TCP out, and one whose network allows only 443 needs a TURN server here or falls back to HLS.

VIBE_LIVE_OME_* and VIBE_LIVE_LIVEKIT_* do nothing here, and setting either group alongside VIBE_LIVE_ENGINE=builtin is refused at startup: one engine per deployment.

Building and running it

CI publishes this target as forgejo.gmatte.xyz/gmatte/vibe-live:ffmpeg-<version> (and ffmpeg-<commit sha>, ffmpeg-latest) since 2026-09-28. Pin the version or the SHA; to build it yourself instead:

docker build --target all-in-one-ffmpeg -t vibe-live:ffmpeg .

It is Alpine with the binary, a static ffmpeg, and the GPL notices and written source offer those two oblige (deploy/ffmpeg/SOURCE-OFFER.txt). Then, with your variables in .env and TLS terminated by whatever you already run:

docker run -d --name vibe-live \
  --env-file .env \
  -p 8080:8080 -p 1936:1936 \
  --tmpfs /media \
  -v vibe-live-data:/data \
  -e VIBE_LIVE_MEDIA_DIR=/media \
  vibe-live:ffmpeg

Add -p 10000:10000/udp -p 3478:3478 -e VIBE_LIVE_WEBRTC_UDP_PORT=10000 for WebRTC. The app serves plain HTTP; put the same reverse proxy in front of it that all-in-one.md describes, and note that publishing from the browser needs a secure context, so a self-signed certificate is the minimum on a LAN.

To adapt compose.yaml instead -- worth it for the caddy service alone -- three changes: build target: all-in-one-ffmpeg, drop the 3333 port mapping and keep 10000/udp and 3478 only if you want WebRTC (and then set VIBE_LIVE_WEBRTC_UDP_PORT=10000), and set VIBE_LIVE_ENGINE=builtin with a tmpfs at VIBE_LIVE_MEDIA_DIR. VIBE_LIVE_OME_ADMISSION_SECRET can go.

What it does not do

Everything else the other engines do, this one does: force-stop, viewer counts, thumbnails on the cards, the rendition menu, Chromecast, and its own figures in /internal/metrics on the internal listener.

Validating the install

scripts/validate-setup.sh https://your.instance

It is engine-aware. On this engine it asks /api/v1/instance which WebRTC transport the deployment has and probes accordingly: with the UDP port set it checks that /app/<channel>/whep reaches the app's WebRTC server and then says plainly that it cannot test UDP from outside; without it, it reports "no WebRTC on this engine" as information rather than a fault. The HLS, Chromecast and thumbnail routes are checked the same way as on every other shape.