From 97633cc585e8b5ff78a45405e9c66e40e265c180 Mon Sep 17 00:00:00 2001 From: Shane Date: Sat, 22 Aug 2026 07:41:58 -0400 Subject: [PATCH] feat: Dockerfile, docker-compose, livekit.yaml, supervisord.conf --- Dockerfile | 71 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 18 ++++++++++++ livekit.yaml | 16 +++++++++++ supervisord.conf | 38 +++++++++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 livekit.yaml create mode 100644 supervisord.conf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cd71c19 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,71 @@ +# syntax=docker/dockerfile:1 + +# ── Stage 1: Build Python agent deps with uv ─────────────────────────────── +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS build + +ENV PYTHONUNBUFFERED=1 +ENV UV_COMPILE_BYTECODE=1 + +WORKDIR /app + +# Install build tools for native extensions (azure-cognitiveservices-speech) +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc g++ python3-dev libasound2-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY agent/pyproject.toml ./agent/ +RUN mkdir -p /app/agent && cd /app/agent && \ + uv venv .venv && \ + uv pip install --python .venv/bin/python \ + "livekit-agents~=1.7" \ + "livekit-plugins-azure~=1.7" \ + "livekit-plugins-openai~=1.7" \ + "python-dotenv" + +# ── Stage 2: Runtime ─────────────────────────────────────────────────────── +FROM debian:bookworm-slim AS runtime + +# Install LiveKit server binary + supervisord + nginx +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl ca-certificates supervisor nginx python3.12 python3.12-venv \ + libasound2t64 libssl3t64 \ + && rm -rf /var/lib/apt/lists/* + +# Download LiveKit server (latest stable) +ARG LIVEKIT_VERSION=v1.13.2 +RUN curl -sSL "https://github.com/livekit/livekit/releases/download/${LIVEKIT_VERSION}/livekit_${LIVEKIT_VERSION}_linux_amd64.tar.gz" \ + | tar xz -C /usr/local/bin/ livekit \ + && chmod +x /usr/local/bin/livekit + +# Copy Python agent + venv from build stage +COPY --from=build /app/agent/.venv /opt/voice-agent/.venv +COPY agent/agent.py /opt/voice-agent/agent.py + +# Copy web frontend +COPY web/ /var/www/voice/ + +# Config files +COPY livekit.yaml /etc/livekit.yaml +COPY supervisord.conf /etc/supervisor/conf.d/voice.conf + +# Remove default nginx site, use our own +RUN rm -f /etc/nginx/sites-enabled/default && \ + cat > /etc/nginx/sites-available/voice << 'NGINX' +server { + listen 8090; + root /var/www/voice; + index index.html; + + location / { + try_files $uri $uri/ =404; + } +} +NGINX + && ln -sf /etc/nginx/sites-available/voice /etc/nginx/sites-enabled/voice + +# Create non-root user for agent +RUN useradd -m -s /bin/bash voiceuser + +EXPOSE 7880 7881 8090 50000-60000/udp + +CMD ["supervisord", "-n", "-c", "/etc/supervisor/conf.d/voice.conf"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..91f6387 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + voice: + build: . + container_name: voice + network_mode: host + environment: + LIVEKIT_URL: "http://localhost:7880" + LIVEKIT_API_KEY: "${LIVEKIT_API_KEY:-devkey}" + LIVEKIT_API_SECRET: "${LIVEKIT_API_SECRET:-devsecret}" + AZURE_SPEECH_KEY: "${AZURE_SPEECH_KEY:?Set AZURE_SPEECH_KEY in .env}" + AZURE_SPEECH_REGION: "${AZURE_SPEECH_REGION:-eastus}" + AZURE_TTS_VOICE: "${AZURE_TTS_VOICE:-en-US-AvaNeural}" + GEMMA_BASE_URL: "${GEMMA_BASE_URL:-http://192.168.86.2:8023/v1}" + GEMMA_MODEL: "${GEMMA_MODEL:-gemma-4-e4b}" + GEMMA_API_KEY: "${GEMMA_API_KEY:-not-needed}" + volumes: + - ./livekit.yaml:/etc/livekit.yaml:ro + restart: unless-stopped diff --git a/livekit.yaml b/livekit.yaml new file mode 100644 index 0000000..cdbd6fa --- /dev/null +++ b/livekit.yaml @@ -0,0 +1,16 @@ +port: 7880 +bind_addr: 0.0.0.0 + +rtc: + tcp_port: 7881 + udp_port: 7882 + port_range_start: 50000 + port_range_end: 60000 +# Leave empty to use host's public IP for TURN (LAN access) +# For LAN-only, this is fine — clients connect directly + +keys: + devkey: devsecret + +redis: + # No Redis — single node, in-memory room state diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 0000000..5c10d05 --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,38 @@ +[supervisord] +nodaemon=false +logfile=/var/log/supervisor/supervisord.log +pidfile=/run/supervisord.pid + +[program:livekit] +command=/usr/local/bin/livekit --config /etc/livekit.yaml +user=root +autostart=true +autorestart=true +stdout_logfile=/var/log/supervisor/livekit.log +stderr_logfile=/var/log/supervisor/livekit_err.log + +[program:agent] +command=/opt/voice-agent/.venv/bin/python /opt/voice-agent/agent.py start +user=voiceuser +autostart=true +autorestart=true +stdout_logfile=/var/log/supervisor/agent.log +stderr_logfile=/var/log/supervisor/agent_err.log +environment= + LIVEKIT_URL="%(ENV_LIVEKIT_URL)s", + LIVEKIT_API_KEY="%(ENV_LIVEKIT_API_KEY)s", + LIVEKIT_API_SECRET="%(ENV_LIVEKIT_API_SECRET)s", + AZURE_SPEECH_KEY="%(ENV_AZURE_SPEECH_KEY)s", + AZURE_SPEECH_REGION="%(ENV_AZURE_SPEECH_REGION)s", + AZURE_TTS_VOICE="%(ENV_AZURE_TTS_VOICE)s", + GEMMA_BASE_URL="%(ENV_GEMMA_BASE_URL)s", + GEMMA_MODEL="%(ENV_GEMMA_MODEL)s", + GEMMA_API_KEY="%(ENV_GEMMA_API_KEY)s" + +[program:web] +command=/usr/sbin/nginx -g "daemon off;" +user=root +autostart=true +autorestart=true +stdout_logfile=/var/log/supervisor/web.log +stderr_logfile=/var/log/supervisor/web_err.log