# Calls for a local Warp: a LiveKit SFU and the LiveKit JWT service # (lk-jwt-service), layered over docker-compose.yml. # # cd deploy/compose # docker compose -f docker-compose.yml -f calls.yml --profile web up -d --build # # Then open http://localhost:8080 in a few browsers (or private windows), # sign in as different users, and press the voice or video call button in # a room: in a room of two it rings the other person. # # How the pieces find each other: # # browser ── /.well-known/matrix/client ──▶ warp rtc_foci: http://localhost:8070 # browser ── openid/request_token ────────▶ warp an OpenID token # browser ── POST /sfu/get ───────────────▶ jwt {url, jwt} for the LiveKit room # jwt ───── /_matrix/federation/v1/openid/userinfo ─▶ warp (https://:8448) # browser ── ws://localhost:7880 + UDP 7882 ─▶ livekit # # The JWT service checks a token by asking the user's homeserver over the # federation port, with TLS. To make that work with the default server name # "localhost", warp gets a federation listener on :8448 with a self-signed # certificate, and the JWT service and LiveKit share warp's network # namespace, so that "localhost" means warp for all three. This is a local # setup: in production, give each its own hostname and real certificates # (see docs/calls.md). services: # Makes the self-signed certificate for warp's federation listener once. calls-cert: image: alpine:3.21 restart: "no" entrypoint: ["/bin/sh", "-c"] command: - >- test -f /tls/tls.crt || (apk add --no-cache openssl >/dev/null && openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -subj "/CN=$${WARP_SERVER_NAME}" -addext "subjectAltName=DNS:$${WARP_SERVER_NAME}" -keyout /tls/tls.key -out /tls/tls.crt && chmod 644 /tls/tls.key /tls/tls.crt) environment: WARP_SERVER_NAME: ${WARP_SERVER_NAME:-localhost} volumes: - warp-tls:/tls warp: depends_on: calls-cert: condition: service_completed_successfully environment: WARP_RTC_LIVEKIT_SERVICE_URL: ${WARP_LIVEKIT_JWT_PUBLIC_URL:-http://localhost:8070} WARP_LISTEN_FEDERATION_ADDRESS: ":8448" WARP_TLS_CERT_FILE: /tls/tls.crt WARP_TLS_KEY_FILE: /tls/tls.key volumes: - warp-tls:/tls:ro # LiveKit and the JWT service live in this container's network # namespace, so their ports are published here. ports: - "8070:8070" # lk-jwt-service - "7880:7880" # LiveKit signalling (WebSocket) - "7881:7881" # LiveKit media over TCP, the fallback - "7882:7882/udp" # LiveKit media over UDP livekit: image: livekit/livekit-server:v1.13.7 restart: unless-stopped network_mode: service:warp depends_on: - warp environment: # node_ip is what LiveKit tells browsers to send media to. 127.0.0.1 # works for browsers on this machine; set LIVEKIT_NODE_IP to the # machine's LAN address to call from other devices. LIVEKIT_CONFIG: | port: 7880 bind_addresses: [""] rtc: tcp_port: 7881 udp_port: 7882 use_external_ip: false node_ip: ${LIVEKIT_NODE_IP:-127.0.0.1} room: auto_create: false keys: ${LIVEKIT_KEY:-warp}: ${LIVEKIT_SECRET:-warp-local-livekit-secret-change-me-0123456789} logging: level: info livekit-jwt: image: ghcr.io/element-hq/lk-jwt-service:0.8.0 restart: unless-stopped network_mode: service:warp depends_on: - warp - livekit environment: LIVEKIT_JWT_BIND: ":8070" # The URL handed to browsers, and the one the service creates rooms # through: both reach LiveKit at localhost here. LIVEKIT_URL: ${LIVEKIT_PUBLIC_URL:-ws://localhost:7880} LIVEKIT_KEY: ${LIVEKIT_KEY:-warp} LIVEKIT_SECRET: ${LIVEKIT_SECRET:-warp-local-livekit-secret-change-me-0123456789} # Users of this server may create call rooms on the SFU. LIVEKIT_FULL_ACCESS_HOMESERVERS: ${WARP_SERVER_NAME:-localhost} # warp's federation certificate is self-signed. LIVEKIT_INSECURE_SKIP_VERIFY_TLS: YES_I_KNOW_WHAT_I_AM_DOING web: environment: # The browser connects to the JWT service and to LiveKit directly. WARP_WEB_CALL_ORIGINS: >- ${WARP_LIVEKIT_JWT_PUBLIC_URL:-http://localhost:8070} ${LIVEKIT_PUBLIC_URL:-ws://localhost:7880} volumes: warp-tls: