This Docker Compose bundle runs Scope as a Live Runner behind an orchestrator, with Caddy handling public TLS and Watchtower keeping container images current.
This differs from earlier orchestrator configurations in that the orchestrator now requires a functioning SSL endpoint for live runner traffic. This example includes Caddy which can acquire certificates automatically and handle renewals without a separate TLS service.
Cloudflare Tunnels are not recommended for this setup due to the buffering (latency) they introduce.
Scope instances register with the orchestrator at runtime. The orchestrator advertises the runner’s capabilities and routes sessions to it.
sequenceDiagram
participant Client
participant Orchestrator as go-livepeer orchestrator
participant Scope as Scope live runner
Scope->>Orchestrator: Register live runner at startup
Client->>Orchestrator: Request live video-to-video session
Orchestrator->>Scope: Start live runner session
Scope-->>Orchestrator: Processed media/events
Orchestrator-->>Client: Live session output
docker-compose.ymlruns Scope, go-livepeer, Caddy, and Watchtower.env.examplelists required runtime configuration.go-livepeer.conf.templateis the single source of truth for the orchestrator config. It is rendered into/tmp/go-livepeer.confinside the go-livepeer container at startup, and thenlivepeer -configruns that rendered file.Caddyfileterminates TLS forDOMAINand proxies to go-livepeer.
-
Copy the example environment and edit the values:
cp env.example .env
-
Point DNS for
DOMAINat the Docker host and make sure ports80and443are reachable from the public internet. -
Put the Livepeer Ethereum keystore and password file in the persistent
livepeer-datavolume. The defaults expect:/root/.lpData/arbitrum-one-mainnet/keystore /root/.lpData/.eth_secret
- The Compose file does not create the Livepeer keystore or
.eth_secretfor you. Those must already exist beforego-livepeerstarts. - By default,
go-livepeerreads both from the persistentlivepeer-datavolume mounted at/root/.lpData. - Scope keeps shared runner data in the persistent
scope-shared-datavolume mounted at/workspace/shared. This is where model weights and shared LoRAs live across container restarts and replacements. - Scope keeps per-session assets, logs, and plugins under
/tmp/.daydream-scope/assetsinside the container. That session data is intentionally ephemeral. - If you already manage these files on the host, you can replace the named
volume with bind mounts and point
ETH_KEYSTORE_PATH/ETH_PASSWORD_FILEat those mounted paths instead. - If you keep the named volume, populate it before first startup, for example
by copying the keystore directory and
.eth_secretinto the volume with a one-off container or temporary mount. - Keep the password file readable by the
go-livepeerprocess inside the container and avoid storing either secret in the repo or other ephemeral container filesystems.
-
Validate the Compose file:
docker compose config
-
Start the stack:
docker compose up -d
-
Make sure Docker itself starts on boot on the host. On Linux hosts this usually means enabling the Docker service with:
sudo systemctl enable docker sudo systemctl start dockerOn Docker Desktop, enable the setting to start Docker when the host logs in. The Compose services already use
restart: unless-stopped, so once the Docker daemon comes back after a reboot, the stack will come back automatically.
Both the Scope live runner and go-livepeer are configured with:
TICKET_EV=800000000000
PRICE_PER_UNIT=5
PIXELS_PER_UNIT=995328000000This corresponds to $0.50/hour which is what the gateways will pay.
Caddy exposes https://${DOMAIN} and forwards traffic to go-livepeer:8935.
Scope registers to go-livepeer over the internal Docker network at
http://go-livepeer:8935. go-livepeer keeps its public client-facing
serviceAddr on https://${DOMAIN}, but uses
liveRunnerAddr=${LIVE_RUNNER_ADDR} for live-runner heartbeat, trickle, and
control-plane callbacks. In the default setup this is the internal Docker URL
http://go-livepeer:8935. The runner itself is still reached
internally at http://scope-live-runner:8989. Scope stores persistent shared
runner data at /workspace/shared and session-specific data under
/tmp/.daydream-scope/assets.
Scope currently uses a single GPU per live runner instance. On multi-GPU hosts, run one Scope runner instance per GPU instead of expecting one instance to use multiple GPUs concurrently.
Each Scope runner instance needs its own service name and runner URL. Select the
GPU for each runner with the CUDA_VISIBLE_DEVICES environment variable, and
set it on the Scope runner itself.
For example, an abbreviated two-runner Docker Compose setup would vary these runner-specific settings:
services:
scope-live-runner-0:
command:
# ...same runner command as scope-live-runner...
- --runner-url
- http://scope-live-runner-0:8989
environment:
CUDA_VISIBLE_DEVICES: "0"
scope-live-runner-1:
command:
# ...same runner command as scope-live-runner...
- --runner-url
- http://scope-live-runner-1:8989
environment:
CUDA_VISIBLE_DEVICES: "1"If you run Scope outside Docker Compose or publish runner ports on the host,
make sure each runner binds a distinct listener, such as 8989 and 8990, and
set each runner URL to the address where that specific runner can be reached.
Watchtower uses nickfedor/watchtower and runs with label filtering enabled.
Only containers with this label are eligible for automatic updates:
com.centurylinklabs.watchtower.enable: "true"The Watchtower container itself is explicitly labeled false.
-
All services in
docker-compose.ymluserestart: unless-stopped, so they restart automatically after container crashes and host reboots. -
Keep Docker configured to start automatically with the host. On Linux, verify this with
systemctl is-enabled dockerandsystemctl status docker. On Docker Desktop, verify the startup setting is enabled. If Docker does not start at boot, the Compose restart policy cannot bring the services back. -
Do not store the Livepeer keystore or password file inside ephemeral container filesystems. Keep them in the persistent
livepeer-datavolume so go-livepeer can recover cleanly after restarts. -
The Caddy TLS state is stored in the persistent
caddy-dataandcaddy-configvolumes. Preserve those volumes so certificates and account state survive restarts. -
After planned maintenance or host reboot, confirm recovery with:
docker compose ps docker compose logs --since=10m go-livepeer scope-live-runner caddy watchtower
-
If you use external monitoring, alert on failed container restarts,
443reachability, and inability to connect to the advertisedPUBLIC_SERVICE_ADDR.
After startup, check:
docker compose logs go-livepeer
docker compose logs scope-live-runner
docker compose logs caddy
docker compose logs watchtowerExpected signs of health:
- go-livepeer starts with
orchestrator true,useLiveRunners true, andnetwork arbitrum-one-mainnet. - Scope logs show live-runner registration against
http://go-livepeer:8935. https://${DOMAIN}reaches the go-livepeer service through Caddy.- Watchtower logs show only labeled services are monitored.
Test the operator-facing flow with a real Scope client:
-
Find the public service URI being advertised by the orchestrator. This should match
PUBLIC_SERVICE_ADDRand be reachable over HTTPS. -
Start Scope separately with the orchestrator URL pointed at that service:
LIVEPEER_ORCH_URL=<service-uri> uv run daydream-scope
-
Open the Scope UI and choose Cloud mode.
-
Confirm the UI connects successfully and a request can be routed through the orchestrator-backed cloud path.
If this test fails, re-check the go-livepeer, scope-live-runner, and Caddy
logs, and confirm that DNS, TLS, and the advertised service URI all match.
On-chain registration, bonding, funding, and service URI transactions are still operator-managed steps outside this Compose bundle.