# Self-hosted production Docker Compose — single file for everything. # # Usage: ./scripts/setup-selfhosted.sh <--gpu|--cpu|--hosted> [--ollama-gpu|--ollama-cpu] [--garage] [--caddy] # or: docker compose -f docker-compose.selfhosted.yml [--profile gpu] [--profile ollama-gpu] [--profile garage] [--profile caddy] up -d # # ML processing modes (pick ONE — required): # --gpu NVIDIA GPU container for transcription/diarization/translation (profile: gpu) # --cpu In-process CPU processing on server/worker (no ML container needed) # --hosted Remote GPU service URL (no ML container needed) # # Local LLM (optional — for summarization/topics): # --profile ollama-gpu Local Ollama with NVIDIA GPU # --profile ollama-cpu Local Ollama on CPU only # # Daily.co multitrack processing (auto-detected from server/.env): # --profile dailyco Hatchet workflow engine + CPU/LLM workers # # Other optional services: # --profile garage Local S3-compatible storage (Garage) # --profile caddy Reverse proxy with auto-SSL # # Prerequisites: # 1. Run ./scripts/setup-selfhosted.sh to generate env files and secrets # 2. Or manually create server/.env and www/.env from the .selfhosted.example templates services: # =========================================================== # Always-on core services (no profile required) # =========================================================== server: build: context: ./server dockerfile: Dockerfile image: monadicalsas/reflector-backend:latest restart: unless-stopped ports: - "127.0.0.1:1250:1250" - "51000-51100:51000-51100/udp" env_file: - ./server/.env environment: ENTRYPOINT: server # Docker-internal overrides (always correct inside compose network) DATABASE_URL: postgresql+asyncpg://reflector:reflector@postgres:5432/reflector REDIS_HOST: redis CELERY_BROKER_URL: redis://redis:6379/1 CELERY_RESULT_BACKEND: redis://redis:6379/1 # ML backend config comes from env_file (server/.env), set per-mode by setup script # HF_TOKEN needed for in-process pyannote diarization (--cpu mode) HF_TOKEN: ${HF_TOKEN:-} # WebRTC: fixed UDP port range for ICE candidates (mapped above) WEBRTC_PORT_RANGE: "51000-51100" # Hatchet workflow engine (always-on for processing pipelines) HATCHET_CLIENT_SERVER_URL: ${HATCHET_CLIENT_SERVER_URL:-http://hatchet:8888} HATCHET_CLIENT_HOST_PORT: ${HATCHET_CLIENT_HOST_PORT:-hatchet:7077} depends_on: postgres: condition: service_healthy redis: condition: service_started volumes: - server_data:/app/data worker: build: context: ./server dockerfile: Dockerfile image: monadicalsas/reflector-backend:latest restart: unless-stopped env_file: - ./server/.env environment: ENTRYPOINT: worker DATABASE_URL: postgresql+asyncpg://reflector:reflector@postgres:5432/reflector REDIS_HOST: redis CELERY_BROKER_URL: redis://redis:6379/1 CELERY_RESULT_BACKEND: redis://redis:6379/1 # ML backend config comes from env_file (server/.env), set per-mode by setup script HF_TOKEN: ${HF_TOKEN:-} # Hatchet workflow engine (always-on for processing pipelines) HATCHET_CLIENT_SERVER_URL: ${HATCHET_CLIENT_SERVER_URL:-http://hatchet:8888} HATCHET_CLIENT_HOST_PORT: ${HATCHET_CLIENT_HOST_PORT:-hatchet:7077} depends_on: postgres: condition: service_healthy redis: condition: service_started volumes: - server_data:/app/data beat: build: context: ./server dockerfile: Dockerfile image: monadicalsas/reflector-backend:latest restart: unless-stopped env_file: - ./server/.env environment: ENTRYPOINT: beat DATABASE_URL: postgresql+asyncpg://reflector:reflector@postgres:5432/reflector REDIS_HOST: redis CELERY_BROKER_URL: redis://redis:6379/1 CELERY_RESULT_BACKEND: redis://redis:6379/1 depends_on: postgres: condition: service_healthy redis: condition: service_started web: build: context: ./www dockerfile: Dockerfile image: monadicalsas/reflector-frontend:latest restart: unless-stopped ports: - "127.0.0.1:3000:3000" env_file: - ./www/.env environment: NODE_ENV: production NODE_TLS_REJECT_UNAUTHORIZED: "0" SERVER_API_URL: http://server:1250 KV_URL: redis://redis:6379 KV_USE_TLS: "false" NEXTAUTH_URL_INTERNAL: http://localhost:3000 depends_on: - redis redis: image: redis:7.2-alpine restart: unless-stopped ports: - "6379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 3s retries: 3 volumes: - redis_data:/data postgres: image: postgres:17-alpine restart: unless-stopped command: ["postgres", "-c", "max_connections=200"] environment: POSTGRES_USER: reflector POSTGRES_PASSWORD: reflector POSTGRES_DB: reflector volumes: - postgres_data:/var/lib/postgresql/data - ./server/docker/init-hatchet-db.sql:/docker-entrypoint-initdb.d/init-hatchet-db.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U reflector"] interval: 30s timeout: 3s retries: 3 # =========================================================== # Specialized model containers (transcription, diarization, translation) # Only the gpu profile is activated by the setup script (--gpu mode). # The cpu service definition is kept for manual/standalone use but is # NOT activated by --cpu mode (which uses in-process local backends). # Both services get alias "transcription" so server config never changes. # =========================================================== gpu: build: context: ./gpu/self_hosted dockerfile: Dockerfile profiles: [gpu] restart: unless-stopped ports: - "127.0.0.1:8000:8000" environment: HF_TOKEN: ${HF_TOKEN:-} volumes: - gpu_cache:/root/.cache deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/docs"] interval: 15s timeout: 5s retries: 10 start_period: 120s networks: default: aliases: - transcription cpu: build: context: ./gpu/self_hosted dockerfile: Dockerfile.cpu profiles: [cpu] restart: unless-stopped ports: - "127.0.0.1:8000:8000" environment: HF_TOKEN: ${HF_TOKEN:-} volumes: - gpu_cache:/root/.cache healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/docs"] interval: 15s timeout: 5s retries: 10 start_period: 120s networks: default: aliases: - transcription # =========================================================== # Ollama — local LLM for summarization & topic detection # Only started with --ollama-gpu or --ollama-cpu modes. # =========================================================== ollama: image: ollama/ollama:latest profiles: [ollama-gpu] restart: unless-stopped ports: - "127.0.0.1:11435:11435" volumes: - ollama_data:/root/.ollama environment: OLLAMA_HOST: "0.0.0.0:11435" OLLAMA_KEEP_ALIVE: "24h" deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] healthcheck: test: ["CMD", "curl", "-f", "http://localhost:11435/api/tags"] interval: 10s timeout: 5s retries: 5 ollama-cpu: image: ollama/ollama:latest profiles: [ollama-cpu] restart: unless-stopped ports: - "127.0.0.1:11435:11435" volumes: - ollama_data:/root/.ollama environment: OLLAMA_HOST: "0.0.0.0:11435" OLLAMA_KEEP_ALIVE: "24h" # keep model loaded to avoid reload delays healthcheck: test: ["CMD", "curl", "-f", "http://localhost:11435/api/tags"] interval: 10s timeout: 5s retries: 5 # =========================================================== # Garage — local S3-compatible object storage (optional) # =========================================================== garage: image: dxflrs/garage:v1.1.0 profiles: [garage] restart: unless-stopped ports: - "3900:3900" # S3 API - "3903:3903" # Admin API volumes: - garage_data:/var/lib/garage/data - garage_meta:/var/lib/garage/meta - ./data/garage.toml:/etc/garage.toml:ro healthcheck: test: ["CMD", "/garage", "stats"] interval: 10s timeout: 5s retries: 5 start_period: 5s # =========================================================== # Caddy — reverse proxy with automatic SSL (optional) # Maps 80:80 and 443:443 — only exposed ports in the stack. # =========================================================== caddy: image: caddy:2-alpine profiles: [caddy] restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy_data:/data - caddy_config:/config depends_on: - web - server # =========================================================== # Hatchet workflow engine + workers # Required for all processing pipelines (file, live, Daily.co multitrack). # Always-on — every selfhosted deployment needs Hatchet. # =========================================================== hatchet: image: ghcr.io/hatchet-dev/hatchet/hatchet-lite:latest restart: on-failure depends_on: postgres: condition: service_healthy ports: - "127.0.0.1:8888:8888" - "127.0.0.1:7078:7077" env_file: - ./.env.hatchet environment: DATABASE_URL: "postgresql://reflector:reflector@postgres:5432/hatchet?sslmode=disable&connect_timeout=30" SERVER_AUTH_COOKIE_INSECURE: "t" SERVER_GRPC_BIND_ADDRESS: "0.0.0.0" SERVER_GRPC_INSECURE: "t" SERVER_GRPC_BROADCAST_ADDRESS: hatchet:7077 SERVER_GRPC_PORT: "7077" SERVER_AUTH_SET_EMAIL_VERIFIED: "t" SERVER_INTERNAL_CLIENT_INTERNAL_GRPC_BROADCAST_ADDRESS: hatchet:7077 volumes: - hatchet_config:/config healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8888/api/live"] interval: 30s timeout: 10s retries: 5 start_period: 30s hatchet-worker-cpu: build: context: ./server dockerfile: Dockerfile image: monadicalsas/reflector-backend:latest profiles: [dailyco] restart: unless-stopped env_file: - ./server/.env environment: ENTRYPOINT: hatchet-worker-cpu DATABASE_URL: postgresql+asyncpg://reflector:reflector@postgres:5432/reflector REDIS_HOST: redis CELERY_BROKER_URL: redis://redis:6379/1 CELERY_RESULT_BACKEND: redis://redis:6379/1 HATCHET_CLIENT_SERVER_URL: http://hatchet:8888 HATCHET_CLIENT_HOST_PORT: hatchet:7077 depends_on: hatchet: condition: service_healthy volumes: - server_data:/app/data hatchet-worker-llm: build: context: ./server dockerfile: Dockerfile image: monadicalsas/reflector-backend:latest restart: unless-stopped env_file: - ./server/.env environment: ENTRYPOINT: hatchet-worker-llm DATABASE_URL: postgresql+asyncpg://reflector:reflector@postgres:5432/reflector REDIS_HOST: redis CELERY_BROKER_URL: redis://redis:6379/1 CELERY_RESULT_BACKEND: redis://redis:6379/1 HATCHET_CLIENT_SERVER_URL: http://hatchet:8888 HATCHET_CLIENT_HOST_PORT: hatchet:7077 depends_on: hatchet: condition: service_healthy volumes: - server_data:/app/data volumes: postgres_data: redis_data: server_data: gpu_cache: garage_data: garage_meta: ollama_data: caddy_data: caddy_config: hatchet_config: networks: default: attachable: true