services: # ── FastAPI interpretation portal ───────────────────────────── portal: build: context: . dockerfile: Dockerfile ports: - "8000:8000" environment: HOST: "0.0.0.0" PORT: "8000" SECRET_KEY: "${SECRET_KEY:-change-me}" API_KEY_ENCRYPTION_KEY: "${API_KEY_ENCRYPTION_KEY}" SMTP_HOST: "${SMTP_HOST}" SMTP_PORT: "${SMTP_PORT:-587}" SMTP_USER: "${SMTP_USER}" SMTP_PASSWORD: "${SMTP_PASSWORD}" SMTP_FROM_EMAIL: "${SMTP_FROM_EMAIL:-noreply@voxbento.com}" BOOTH_ACCESS_TOKEN: "${BOOTH_ACCESS_TOKEN:-}" ADMIN_PASSWORD: "${ADMIN_PASSWORD:-}" JWT_SECRET: "${JWT_SECRET:-}" JWT_EXPIRY_SECONDS: "${JWT_EXPIRY_SECONDS:-86400}" DEFAULT_JITSI_ROOM: "${DEFAULT_JITSI_ROOM:-eventyay-stage-room}" JITSI_DOMAIN: "${JITSI_DOMAIN:-jitsi.voxbento.com}" JITSI_BASE_URL: "${JITSI_BASE_URL:-http://${JITSI_DOMAIN:-localhost:8080}}" JITSI_INTERNAL_BASE: "https://jitsi-web:443" # Browser-facing URLs — must be reachable from the user's machine MEDIAMTX_WHIP_BASE: "${MEDIAMTX_WHIP_BASE:-http://localhost:8889}" FLOOR_BOT_BASE: "http://floor-bot:8080" # Internal URL for Python health checks (Docker service name) MEDIAMTX_INTERNAL_BASE: "http://mediamtx:8888" # MediaMTX Control API (Docker internal) — creates alwaysAvailable paths MEDIAMTX_API_BASE: "http://mediamtx:9997" # Database — SQLite stored on a Docker volume for persistence. # Override with a PostgreSQL URL for production deployments: # DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/interpretation DATABASE_URL: "${DATABASE_URL:-sqlite+aiosqlite:////data/interpretation.db}" volumes: # Mount source for hot-reload — code changes take effect instantly - .:/app # Keep the container's .venv intact (don't override with host .venv) - /app/.venv # Persist database across container restarts - portal-data:/data # Persist Supertonic ONNX model weights (~400MB) across restarts - supertonic-cache:/root/.cache depends_on: - mediamtx restart: unless-stopped # In production, --reload is omitted to prevent high CPU usage and extra processes. # Alembic migration runs first to ensure the database schema is current. command: ["sh", "-c", "uv run alembic upgrade head && uv run uvicorn fastapi_app:app --host 0.0.0.0 --port 8000"] healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz', timeout=3)"] interval: 10s timeout: 5s retries: 3 start_period: 10s # ── Floor Bot Service ───────────────────────────────────────── floor-bot: build: context: ./floor-bot dockerfile: Dockerfile environment: # Pass the docker-internal RTSP base MEDIAMTX_RTSP_BASE: "rtsp://mediamtx:8554" depends_on: - mediamtx restart: unless-stopped # ── MediaMTX audio server ───────────────────────────────────── mediamtx: image: bluenviron/mediamtx:1 volumes: - ./mediamtx.yml:/mediamtx.yml:ro ports: - "8888:8888" # MediaMTX HTTP (used by Control API health checks) - "8889:8889" # WHIP/WHEP (WebRTC HTTP ingest + playback) - "8189:8189/udp" # WebRTC ICE/UDP mux - "9997:9997" # Control API (used by FastAPI for alwaysAvailable paths) - "8554:8554" # RTSP (for faster-whisper/ffmpeg integration) restart: unless-stopped # ── Self-hosted Jitsi Meet ──────────────────────────────────── # Interpreters watch/hear the speaker via this embedded Jitsi room. # The room name (DEFAULT_JITSI_ROOM) is created on the local instance. # # Ports: # 8443 → Jitsi web UI (HTTPS) # 8080 → Jitsi web UI (HTTP, for local dev — no self-signed cert) # 10000/udp → JVB media traffic jitsi-web: image: jitsi/web:stable-9823 ports: - "${JITSI_WEB_PORT:-8443}:443" - "${JITSI_WEB_HTTP_PORT:-8080}:80" environment: ENABLE_AUTH: "0" ENABLE_GUESTS: "1" PUBLIC_URL: "${JITSI_PUBLIC_URL:-http://localhost:8080}" # BOSH_RELATIVE=true makes config.js use '/http-bind' (relative path) # instead of hardcoding https:// which breaks HTTP setups BOSH_RELATIVE: "true" ENABLE_XMPP_WEBSOCKET: "0" XMPP_SERVER: jitsi-prosody TZ: "${TZ:-UTC}" XMPP_DOMAIN: meet.jitsi XMPP_AUTH_DOMAIN: auth.meet.jitsi XMPP_BOSH_URL_BASE: http://jitsi-prosody:5280 XMPP_MUC_DOMAIN: muc.meet.jitsi volumes: - jitsi-web-config:/config depends_on: - jitsi-prosody restart: unless-stopped jitsi-prosody: image: jitsi/prosody:stable-9823 environment: XMPP_DOMAIN: meet.jitsi XMPP_AUTH_DOMAIN: auth.meet.jitsi XMPP_MUC_DOMAIN: muc.meet.jitsi XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi JVB_AUTH_USER: jvb JVB_AUTH_PASSWORD: "${JVB_AUTH_PASSWORD:-changeme}" JICOFO_AUTH_USER: focus JICOFO_AUTH_PASSWORD: "${JICOFO_AUTH_PASSWORD:-changeme}" PROSODY_LOG_LEVEL: "warn" TZ: "${TZ:-UTC}" volumes: - jitsi-prosody-config:/config - jitsi-prosody-plugins:/prosody-plugins-custom restart: unless-stopped jitsi-jicofo: image: jitsi/jicofo:stable-9823 environment: XMPP_DOMAIN: meet.jitsi XMPP_AUTH_DOMAIN: auth.meet.jitsi XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi XMPP_MUC_DOMAIN: muc.meet.jitsi XMPP_SERVER: jitsi-prosody JICOFO_AUTH_USER: focus JICOFO_AUTH_PASSWORD: "${JICOFO_AUTH_PASSWORD:-changeme}" JICOFO_LOG_LEVEL: "WARNING" TZ: "${TZ:-UTC}" volumes: - jitsi-jicofo-config:/config depends_on: - jitsi-prosody restart: unless-stopped jitsi-jvb: image: jitsi/jvb:stable-9823 ports: - "${JVB_PORT:-10000}:10000/udp" environment: XMPP_AUTH_DOMAIN: auth.meet.jitsi XMPP_INTERNAL_MUC_DOMAIN: internal-muc.meet.jitsi XMPP_DOMAIN: meet.jitsi XMPP_SERVER: jitsi-prosody JVB_AUTH_USER: jvb JVB_AUTH_PASSWORD: "${JVB_AUTH_PASSWORD:-changeme}" JVB_PORT: "10000" JVB_STUN_SERVERS: "stun.l.google.com:19302" # DOCKER_HOST_ADDRESS tells JVB what IP to advertise in ICE candidates. # On macOS: set to your LAN IP (e.g. 192.168.1.x) — run: ipconfig getifaddr en0 # On Linux: set to your host IP — run: hostname -I | awk '{print $1}' DOCKER_HOST_ADDRESS: "${DOCKER_HOST_ADDRESS:-}" ENABLE_COLIBRI_WEBSOCKET: "true" JVB_LOG_LEVEL: "WARNING" TZ: "${TZ:-UTC}" volumes: - jitsi-jvb-config:/config depends_on: - jitsi-prosody restart: unless-stopped volumes: portal-data: supertonic-cache: jitsi-web-config: jitsi-prosody-config: jitsi-prosody-plugins: jitsi-jicofo-config: jitsi-jvb-config: