Fork/adaptation of livekit/livekit focused on self-hosting the LiveKit Server on a Google Cloud Platform (GCP) VM.
This repository does not contain the Go source code of the SFU. It packages the official configuration (Docker + Caddy + Redis) with GCP scripts, based on the workflow recommended by the LiveKit documentation for VMs.
docker-compose.yaml— LiveKit Server, Redis, Caddy, and Token Serverconfig/— configuration templates with placeholderstoken-server/— API for automatic JWT token generationscripts/gcp/— VM creation, firewall, and cloud-initsystemd/livekit-docker.service— automatic startup on the VM
Local (dev):
- Linux (
docker-composeusesnetwork_mode: host— does not work on Docker Desktop for Mac/Windows) - Docker + Docker Compose
- Free ports:
7880,6379,3001(and UDP50000–60000for WebRTC media)
GCP (production):
- GCP account with billing enabled
- Google Cloud SDK (
gcloud) installed and authenticated - Two DNS records pointing to the VM's IP:
livekit.yourdomain.com— WebSocket signaling (WSS)livekit-turn.yourdomain.com— TURN/TLS
Full workflow for testing on your machine without Caddy/TLS.
cp .env.example .envEdit .env with your credentials and local URLs:
LIVEKIT_API_KEY=APIxxxxx
LIVEKIT_API_SECRET=your_secret_here
LIVEKIT_URL=ws://localhost:7880
TOKEN_SERVER_PORT=3001
CORS_ORIGIN=http://localhost:3000Use
ws://(no TLS) for local. If the frontend runs on a different port (e.g., Vue CLI on8080), setCORS_ORIGINto the exact browser origin (http://localhost:8080).localhostand127.0.0.1are different origins for CORS.
If you don't have an API key/secret yet:
chmod +x scripts/*.sh
./scripts/generate-keys.shIn config/livekit.yaml.template, change:
rtc:
use_external_ip: false # true only on GCPOptional (simplifies local testing):
turn:
enabled: falseGenerate the configuration files:
./scripts/configure.shRequired before starting Docker. This script reads
.envand generateslivekit.yamlandcaddy.yamlin the project root from the templates inconfig/. Thedocker-compose.yamlmounts these two files into the containers — without them, thelivekitandcaddyservices won't start. Run it again whenever you change.envor the templates inconfig/.
docker compose up redis livekit token-server -dCheck status and logs:
docker compose ps
docker compose logs -f livekit
docker compose logs -f token-servercurl http://localhost:3001/health
curl "http://localhost:3001/api/connection-details?roomName=test&participantName=Joao&identity=user-1&accessLevel=0"The response should include serverUrl, roomName, participantName, and participantToken.
Test CORS (replace with your frontend's origin):
curl -i -H "Origin: http://localhost:3000" \
"http://localhost:3001/api/connection-details?roomName=test&participantName=Joao"The Access-Control-Allow-Origin header must match the CORS_ORIGIN from .env.
In the RUXAILAB project's .env:
VUE_APP_LIVEKIT_ENABLED=true
VUE_APP_LIVEKIT_TOKEN_SERVER_URL=http://localhost:3001
VUE_APP_LIVEKIT_URL=ws://localhost:7880The client fetches tokens automatically from GET /api/connection-details — no need to use livekit-cli.
In the meet project, configure .env.local:
LIVEKIT_API_KEY=<same key>
LIVEKIT_API_SECRET=<same secret>
LIVEKIT_URL=ws://localhost:7880
NEXT_PUBLIC_CONN_DETAILS_ENDPOINT=http://localhost:3001/api/connection-detailscd ../meet && pnpm install && pnpm dev# Stop everything
docker compose down
# Start again
docker compose up redis livekit token-server -d
# Restart a service (after changing .env)
docker compose restart token-server
# Rebuild token-server (after changing code or .env)
docker compose up token-server -d --build --force-recreate
# Live logs
docker compose logs -f token-servercd token-server
npm install
# Load variables from the project root .env
export $(grep -v '^#' ../.env | xargs)
npm run devcp .env.example .envEdit .env with your domain, GCP project, and zone.
chmod +x scripts/*.sh scripts/gcp/*.sh
./scripts/generate-keys.shCopy the key and secret into .env:
LIVEKIT_API_KEY=APIxxxxx
LIVEKIT_API_SECRET=your_secret_here./scripts/configure.shRequired. Generates
livekit.yamlandcaddy.yamlin the project root from the templates inconfig/. These files are mounted bydocker-compose.yaml(and shipped to the VM via cloud-init), so they must exist before starting the services. Run it again whenever you change.envor the templates.
./scripts/gcp/create-vm.shThe script:
- Creates firewall rules (TCP 80/443/7881, UDP 3478, UDP 50000–60000)
- Reserves a static IP
- Creates the Ubuntu 22.04 VM with cloud-init
- Installs Docker and starts LiveKit via systemd
In the meet project, configure .env.local:
LIVEKIT_API_KEY=<same key>
LIVEKIT_API_SECRET=<same secret>
LIVEKIT_URL=wss://livekit.yourdomain.com
NEXT_PUBLIC_CONN_DETAILS_ENDPOINT=https://livekit.yourdomain.com/api/connection-detailsIn production, expose the token-server behind Caddy or a reverse proxy with HTTPS.
Node.js service that generates JWTs automatically — replaces manual use of livekit-cli.
GET /api/connection-details?roomName=<room>&participantName=<name>&identity=<id>&accessLevel=<0-3>
| Parameter | Required | Description |
|---|---|---|
roomName |
yes | Room name/ID (in RUXAILAB: testId) |
participantName |
yes | Participant's display name |
identity |
no | Stable participant ID (RUXAILAB: userId). If omitted, generates participantName__<suffix> via cookie (compatible with LiveKit Meet) |
accessLevel |
no | RUXAILAB role: 0 moderator, 1 evaluator, 2 participant, 3 observer. Omit = full publish (Meet / legacy) |
metadata |
no | Optional metadata attached to the JWT token |
LiveKit grants by accessLevel (RUXAILAB):
accessLevel |
Role | canPublish |
canSubscribe |
canPublishData |
|---|---|---|---|---|
0 |
Moderator | ✅ | ✅ | ✅ |
1 |
Evaluator | ✅ | ✅ | ✅ |
2 |
Participant | ✅ | ✅ | ❌ |
3 |
Observer | ❌ | ✅ | ❌ |
| omitted | Meet / legacy | ✅ | ✅ | ✅ |
Also available: GET /health → { "status": "ok" }.
Response:
{
"serverUrl": "ws://localhost:7880",
"roomName": "my-room",
"participantName": "João",
"participantToken": "eyJ..."
}If you prefer to provision the VM yourself:
./scripts/configure.sh
scp -r . user@VM_IP:/opt/livekit/
ssh user@VM_IP 'cd /opt/livekit && sudo ./scripts/install.sh'| Port | Protocol | Use |
|---|---|---|
| 80 | TCP | ACME / Let's Encrypt |
| 443 | TCP | WSS + TURN/TLS |
| 7881 | TCP | WebRTC fallback (ICE/TCP) |
| 3478 | UDP | TURN/UDP |
| 50000–60000 | UDP | WebRTC media |
In config/livekit.yaml.template, use use_external_ip: true on GCP and false locally.
# Service status
sudo systemctl status livekit-docker
# Logs
sudo docker compose -f /opt/livekit/docker-compose.yaml logs -f
# Restart
sudo systemctl restart livekit-dockerFor manual debugging, without the token server:
curl -sSL https://get.livekit.io/cli | bashGenerate a test token:
livekit-cli create-token \
--api-key "$LIVEKIT_API_KEY" \
--api-secret "$LIVEKIT_API_SECRET" \
--join --room test-room --identity user1 \
--valid-for 24h- Original repository: https://github.com/livekit/livekit
- VM documentation: https://docs.livekit.io/transport/self-hosting/vm/
- Docker image:
livekit/livekit-server
The configuration follows the Apache 2.0 license of the LiveKit project.