services: mcspeak: build: . container_name: mcspeak restart: unless-stopped # Must exceed: 3s handler drain + TTS_SHUTDOWN_TIMEOUT + 5s safety margin. # Default: 3 + 30 + 5 = 38s. Increase if TTS_SHUTDOWN_TIMEOUT > 30. stop_grace_period: 38s env_file: .env # Publish to localhost only — Claude Code (and any local MCP client) talks # to 127.0.0.1:8371. External HTTPS access still flows through the caddy # label below if caddy-docker-proxy is running. ports: - "127.0.0.1:8371:8371" environment: # Override for Docker networking (container DNS instead of IPs) TTS_PIPER_HOST: piper-tts TTS_ORPHEUS_URL: http://llama-server:8081 # PipeWire client config XDG_RUNTIME_DIR: /run/user/1000 # PulseAudio socket for media ducking (bypass XDG_RUNTIME_DIR ownership check) PULSE_SERVER: unix:/run/user/1000/pulse/native # Force unbuffered Python output so stderr shows up in docker logs immediately PYTHONUNBUFFERED: "1" volumes: # Kokoro ONNX models (read-only) - ./models:/app/models:ro # HuggingFace cache for SNAC model download (lazy-loaded on first Orpheus call) - hf-cache:/home/tts/.cache/huggingface # Persistent data (voice assignments, etc.) - tts-data:/data # PipeWire socket for audio playback through host speakers - /run/user/1000/pipewire-0:/run/user/1000/pipewire-0 # PulseAudio compat socket for media ducking (volume control) - /run/user/1000/pulse:/run/user/1000/pulse depends_on: llama-server: condition: service_healthy # required: false lets mcspeak start without llama-server when the # with-orpheus profile isn't active (kokoro-only mode is the default). required: false networks: - caddy - mcspeak-internal labels: caddy: mctalkbox.l.supported.systems caddy.reverse_proxy: "{{upstreams 8371}}" llama-server: # Opt-in: only starts when `docker compose --profile with-orpheus up`. # Default `make up` runs kokoro-only without GPU dependencies. profiles: ["with-orpheus"] build: context: . dockerfile: llama-server.Dockerfile container_name: orpheus-llama-server restart: unless-stopped deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] volumes: # GGUF model file (set ORPHEUS_GGUF_PATH in .env, e.g. from Ollama blob # storage). The /dev/null fallback lets compose parse cleanly when the # with-orpheus profile is inactive; activating the profile without # setting ORPHEUS_GGUF_PATH will produce a clear "model load failed" at # llama-server startup rather than a confusing compose parse error. - ${ORPHEUS_GGUF_PATH:-/dev/null}:/models/orpheus.gguf:ro command: >- --host 0.0.0.0 --port 8081 --model /models/orpheus.gguf --n-gpu-layers 999 --ctx-size 4096 --flash-attn --cont-batching networks: - mcspeak-internal healthcheck: test: ["CMD", "curl", "-sf", "http://127.0.0.1:8081/health"] interval: 15s timeout: 5s start_period: 120s retries: 5 volumes: hf-cache: tts-data: networks: caddy: external: true # Private per-stack network for mcspeak ↔ llama-server. Auto-created by # compose, isolated from other stacks (no DNS leak via shared caddy). mcspeak-internal: internal: true