# ============================================================================= # Harmony Full Stack Docker Compose (Self-Hosted Supabase) # ============================================================================= # Complete self-hosted stack including Supabase # # Prerequisites: # 1. Clone Supabase docker: git clone https://github.com/supabase/supabase # 2. Configure Supabase .env (copy from supabase/docker/.env.example) (Add IMGPROXY_MAX_ANIMATION_FRAMES: 120 to supabase's imgproxy's service in the .env # 3. Build frontend: npm run build-only # 4. Configure Harmony env files # # Usage: # # Start Supabase first (in supabase/docker directory): # docker compose up -d # # # Then start Harmony: # docker compose -f docker-compose.full.yml up -d # # Alternatively, if Supabase is already running, this will connect to it # via the supabase_default network. # ============================================================================= version: "3.8" services: # --------------------------------------------------------------------------- # Federation Backend (split: server + worker) # --------------------------------------------------------------------------- # Separate containers for process isolation. The worker handles BullMQ # (Redis-backed) queues and delivery without blocking the HTTP server. # # FEDERATION_MODE=unified (the image default) runs both roles in one # process. A profile only adds services and cannot replace this pair, so # this file has no single-process variant. # --------------------------------------------------------------------------- federation-server: build: context: ./federation-backend dockerfile: Dockerfile container_name: harmony-federation-server restart: unless-stopped ports: - "3001:3001" env_file: - ./federation-backend/.env environment: - NODE_ENV=production - PORT=3001 - FEDERATION_MODE=server - SUPABASE_URL=http://supabase-kong:8000 - USE_BULLMQ_QUEUE=true # Least-privilege LISTEN role, password LISTENER_PASSWORD. Created by # self-host/bootstrap.sh; on this stack, by the SQL under "Listener role" # in docs/self-host/docker-compose.md. Without it the worker # picks jobs up through the 60 s sweep. - FEDERATION_LISTENER_URL=postgresql://harmony_listener:${LISTENER_PASSWORD:-}@supabase-db:5432/postgres - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379 healthcheck: test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3001/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})", ] interval: 60s timeout: 10s retries: 3 start_period: 15s depends_on: redis: condition: service_healthy networks: - harmony - supabase_default federation-worker: build: context: ./federation-backend dockerfile: Dockerfile container_name: harmony-federation-worker restart: unless-stopped env_file: - ./federation-backend/.env environment: - NODE_ENV=production - FEDERATION_MODE=worker - SUPABASE_URL=http://supabase-kong:8000 - USE_BULLMQ_QUEUE=true - FEDERATION_LISTENER_URL=postgresql://harmony_listener:${LISTENER_PASSWORD:-}@supabase-db:5432/postgres - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379 # Worker serves no HTTP; the image HEALTHCHECK probes :3001/health. healthcheck: disable: true depends_on: redis: condition: service_healthy networks: - harmony - supabase_default # --------------------------------------------------------------------------- # Bot Gateway (Optional) # --------------------------------------------------------------------------- bot-gateway: build: context: ./bot-gateway dockerfile: Dockerfile container_name: harmony-bot-gateway restart: unless-stopped ports: - "3002:3002" env_file: - ./bot-gateway/.env environment: - NODE_ENV=production - PORT=3002 - SUPABASE_URL=http://supabase-kong:8000 networks: - harmony - supabase_default profiles: - bots # --------------------------------------------------------------------------- # Redis (shared by LiveKit, federation-backend, and app caching) # --------------------------------------------------------------------------- redis: image: redis:7-alpine container_name: harmony-redis restart: unless-stopped volumes: - redis-data:/data command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:?Set REDIS_PASSWORD in .env} healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 3 networks: - harmony # --------------------------------------------------------------------------- # LiveKit (Voice/Video) # --------------------------------------------------------------------------- # Uses UDP mux (port 7882) - all WebRTC media on a single port. # Scaling limits are CPU/bandwidth, not port count. # For large deployments, run LiveKit on a dedicated VPS. # Multi-node clustering is automatic via shared Redis. # --------------------------------------------------------------------------- livekit: image: livekit/livekit-server:latest container_name: harmony-livekit restart: unless-stopped ports: - "7880:7880" - "7881:7881" - "7881:7881/udp" - "7882:7882/udp" - "3478:3478/udp" - "3478:3478/tcp" volumes: - ./webrtc/livekit.yaml:/livekit.yaml:ro command: --config /livekit.yaml depends_on: redis: condition: service_healthy networks: - harmony profiles: - voice # --------------------------------------------------------------------------- # Bull Board - Queue Monitoring Dashboard (Optional) # --------------------------------------------------------------------------- # Activate with: docker compose --profile monitoring up -d # Access at http://your-server:3003 with HTTP basic auth credentials # --------------------------------------------------------------------------- bull-board: build: ./bull-board container_name: harmony-bull-board restart: unless-stopped ports: - "127.0.0.1:3003:3003" environment: - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379 - BULL_BOARD_USER=${BULL_BOARD_USER:-admin} # bull-board exits at start without a password. `:?` would refuse the # whole file, whatever profiles are active. - BULL_BOARD_PASSWORD=${BULL_BOARD_PASSWORD:-} - BULL_BOARD_BASE_PATH=${BULL_BOARD_BASE_PATH:-} - PORT=3003 depends_on: redis: condition: service_healthy networks: - harmony profiles: - monitoring # --------------------------------------------------------------------------- # Nginx Reverse Proxy # --------------------------------------------------------------------------- nginx: image: nginx:alpine container_name: harmony-nginx restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./dist:/usr/share/nginx/html:ro - ./docs/.vitepress/dist:/usr/share/nginx/docs:ro - ./dev/nginx-harmony.conf:/etc/nginx/conf.d/harmony.conf:ro - ./dev/nginx-docs.conf:/etc/nginx/conf.d/docs.conf:ro - /etc/letsencrypt:/etc/letsencrypt:ro networks: - harmony depends_on: federation-server: condition: service_started networks: harmony: driver: bridge supabase_default: external: true volumes: redis-data: