Troubleshooting
Common issues and how to diagnose them.
/readyz returns 503
Section titled “/readyz returns 503”The backend reports unhealthy. Check what’s failing:
docker compose psdocker compose logs backend | tail -50docker compose exec backend wget -qO- http://localhost:8095/readyz/readyz probes Postgres + Redis + LiveKit. The JSON response lists which
dependency is down. Usually:
- Postgres not ready yet (wait 10–20s after
docker compose up) - Redis password mismatch between
.envand the container - LiveKit signaling unreachable (check
LIVEKIT_HOSTenv var resolves)
LiveKit connection refused / WebSocket fails
Section titled “LiveKit connection refused / WebSocket fails”docker compose logs livekit | tail -30Two common causes:
- API key/secret mismatch.
infra/livekit.yamlhaskeys: { <KEY>: <SECRET> }and the backend readsLIVEKIT_API_KEY/LIVEKIT_API_SECRETfrom.env. They must match exactly. Regenerate both sides if unsure. - UDP ports blocked. WebRTC media flows over UDP 50000–50200 (configurable
in
infra/livekit.yaml). On a firewalled host these must be open. TCP fallback uses 7881 — slower but works through restrictive networks.
Cookie not set after login
Section titled “Cookie not set after login”Symptom: login API returns 200 OK but the browser immediately bounces back to
/login. The session cookie wasn’t accepted.
Causes:
DOMAINenv var doesn’t match the host you’re hitting (cookie domain mismatch)- You’re accessing via HTTP, not HTTPS —
Securecookies require TLS in modern browsers - You’re behind a proxy that strips
Set-Cookie— check Caddy logs
docker compose logs caddy | grep -i cookie401 Unauthorized on protected routes
Section titled “401 Unauthorized on protected routes”Access tokens have a 15-minute TTL. On expiry the studio auto-refreshes silently on the next request via the opaque refresh token (30 days sliding). If you see 401s in burst:
- Refresh token also expired → user must re-login
- Clock skew between client and server > 30s → fix NTP on the host
- Refresh token revoked (logged out elsewhere) → re-login
Postgres migration fails
Section titled “Postgres migration fails”docker compose exec backend goose -dir /app/migrations \ postgres "$DATABASE_URL" statusIf goose is out of sync (a migration was applied manually, or a previous run crashed mid-migration), you can:
# See the stategoose -dir migrations postgres "$DATABASE_URL" status
# Force to a known version (CAREFUL — does not run the SQL)goose -dir migrations postgres "$DATABASE_URL" versiongoose -dir migrations postgres "$DATABASE_URL" up-by-oneFor a fully botched dev DB, drop and recreate:
docker compose down postgresdocker volume rm commentary_postgres-datadocker compose up -d postgres backendCommentator kiosk shows “no permission” for microphone
Section titled “Commentator kiosk shows “no permission” for microphone”Browser blocked mic access. The kiosk is Chrome-only (uses AudioContext.sinkId
for output device routing). In Chrome:
- Click the lock icon in the address bar
- Site settings → Microphone → Allow
- Reload the page
On macOS / Windows also check OS-level mic permission for Chrome.
Caddy can’t get TLS cert
Section titled “Caddy can’t get TLS cert”docker compose logs caddy | grep -i acmeTypical issues:
- Port 80 blocked or already used by another service (Caddy needs :80 for HTTP-01)
- DNS hasn’t propagated yet — wait 5 minutes then
docker compose restart caddy - Rate-limit hit on Let’s Encrypt (5 certs per domain per week) — wait or
use the staging endpoint via
acme_ca https://acme-staging-v02.api.letsencrypt.org/directoryin the Caddyfile temporarily
Still stuck?
Section titled “Still stuck?”Open an issue with the output of:
docker compose psdocker compose logs --tail=100 backend caddy livekitScrub any secrets before posting.