name: it0 services: # ===== Infrastructure ===== postgres: image: postgres:16-alpine container_name: it0-postgres restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-it0} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-it0_dev} POSTGRES_DB: ${POSTGRES_DB:-it0} ports: - "15432:5432" volumes: - postgres_data:/var/lib/postgresql/data - ./scripts/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-it0}"] interval: 10s timeout: 5s retries: 5 networks: - it0-network redis: image: redis:7-alpine container_name: it0-redis restart: unless-stopped ports: - "16379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 networks: - it0-network # ===== API Gateway ===== api-gateway: build: context: ../../packages/gateway container_name: it0-api-gateway restart: unless-stopped environment: - JWT_SECRET=${JWT_SECRET:-dev-jwt-secret} ports: - "18000:8000" - "18001:8001" depends_on: auth-service: condition: service_healthy agent-service: condition: service_healthy ops-service: condition: service_healthy inventory-service: condition: service_healthy monitor-service: condition: service_healthy comm-service: condition: service_healthy audit-service: condition: service_healthy billing-service: condition: service_healthy version-service: condition: service_healthy presence-service: condition: service_healthy referral-service: condition: service_healthy notification-service: condition: service_healthy healthcheck: test: ["CMD", "kong", "health"] interval: 10s timeout: 5s retries: 3 networks: - it0-network # ===== Backend Services (shared Dockerfile.service) ===== auth-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: auth-service SERVICE_PORT: 3001 container_name: it0-auth-service restart: unless-stopped ports: - "13001:3001" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - JWT_SECRET=${JWT_SECRET:-dev-jwt-secret} - JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-dev-jwt-refresh-secret} - AUTH_SERVICE_PORT=3001 healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3001/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - it0-network agent-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: agent-service SERVICE_PORT: 3002 container_name: it0-agent-service restart: unless-stopped ports: - "13002:3002" volumes: - ${HOME}/.claude:/home/appuser/.claude - ${HOME}/.claude.json:/home/appuser/.claude.json - claude_tenants:/data/claude-tenants - ${HOME}/.ssh/rwadurian_ed25519:/tmp/host-ssh-key:ro environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - AGENT_ENGINE_TYPE=claude_agent_sdk - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - ANTHROPIC_BASE_URL=${ANTHROPIC_BASE_URL} - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_BASE_URL=${OPENAI_BASE_URL} - AGENT_SERVICE_PORT=3002 - INVENTORY_SERVICE_URL=http://inventory-service:3004 - INTERNAL_API_KEY=${INTERNAL_API_KEY:-changeme-internal-key} - VAULT_MASTER_KEY=${VAULT_MASTER_KEY:-dev-vault-key} - AGENT_SERVICE_PUBLIC_URL=${AGENT_SERVICE_PUBLIC_URL} - IT0_DINGTALK_CLIENT_ID=${IT0_DINGTALK_CLIENT_ID:-} - IT0_DINGTALK_CLIENT_SECRET=${IT0_DINGTALK_CLIENT_SECRET:-} - IT0_BASE_URL=${IT0_BASE_URL:-https://it0api.szaiai.com} healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3002/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - it0-network ops-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: ops-service SERVICE_PORT: 3003 container_name: it0-ops-service restart: unless-stopped ports: - "13003:3003" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - OPS_SERVICE_PORT=3003 healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3003/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - it0-network inventory-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: inventory-service SERVICE_PORT: 3004 container_name: it0-inventory-service restart: unless-stopped ports: - "13004:3004" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - VAULT_MASTER_KEY=${VAULT_MASTER_KEY:-dev-vault-key} - INTERNAL_API_KEY=${INTERNAL_API_KEY:-changeme-internal-key} - INVENTORY_SERVICE_PORT=3004 healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3004/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy networks: - it0-network monitor-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: monitor-service SERVICE_PORT: 3005 container_name: it0-monitor-service restart: unless-stopped ports: - "13005:3005" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - MONITOR_SERVICE_PORT=3005 healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3005/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy networks: - it0-network comm-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: comm-service SERVICE_PORT: 3006 container_name: it0-comm-service restart: unless-stopped ports: - "13006:3006" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - TWILIO_ACCOUNT_SID=${TWILIO_ACCOUNT_SID} - TWILIO_AUTH_TOKEN=${TWILIO_AUTH_TOKEN} - TWILIO_PHONE_NUMBER=${TWILIO_PHONE_NUMBER} - COMM_SERVICE_PORT=3006 healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3006/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - it0-network audit-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: audit-service SERVICE_PORT: 3007 container_name: it0-audit-service restart: unless-stopped ports: - "13007:3007" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - AUDIT_SERVICE_PORT=3007 healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3007/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy networks: - it0-network billing-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: billing-service SERVICE_PORT: 3010 container_name: it0-billing-service restart: unless-stopped ports: - "13010:3010" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - BILLING_SERVICE_PORT=3010 - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET} - ALIPAY_APP_ID=${ALIPAY_APP_ID} - ALIPAY_PRIVATE_KEY=${ALIPAY_PRIVATE_KEY} - WECHAT_MCH_ID=${WECHAT_MCH_ID} - WECHAT_API_KEY_V3=${WECHAT_API_KEY_V3} - WECHAT_APP_ID=${WECHAT_APP_ID} - COINBASE_COMMERCE_API_KEY=${COINBASE_COMMERCE_API_KEY} - COINBASE_COMMERCE_WEBHOOK_SECRET=${COINBASE_COMMERCE_WEBHOOK_SECRET} healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3010/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - it0-network version-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: version-service SERVICE_PORT: 3009 container_name: it0-version-service restart: unless-stopped ports: - "13009:3009" volumes: - version_data:/data/versions environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - VERSION_SERVICE_PORT=3009 - DOWNLOAD_BASE_URL=https://it0api.szaiai.com/downloads/versions healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3009/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy networks: - it0-network presence-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: presence-service SERVICE_PORT: 3011 container_name: it0-presence-service restart: unless-stopped ports: - "13011:3011" environment: - DATABASE_URL=postgresql://${POSTGRES_USER:-it0}:${POSTGRES_PASSWORD:-it0_dev}@postgres:5432/${POSTGRES_DB:-it0} - REDIS_HOST=redis - REDIS_PORT=6379 - REDIS_DB=10 - APP_PORT=3011 - JWT_SECRET=${JWT_SECRET:-dev-jwt-secret} - PRESENCE_WINDOW_SECONDS=300 healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3011/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - it0-network referral-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: referral-service SERVICE_PORT: 3012 container_name: it0-referral-service restart: unless-stopped ports: - "13012:3012" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - REDIS_URL=redis://redis:6379 - REFERRAL_SERVICE_PORT=3012 - JWT_SECRET=${JWT_SECRET:-dev-jwt-secret} - INTERNAL_API_KEY=${INTERNAL_API_KEY:-changeme-internal-key} - APP_REFERRAL_BASE_URL=https://it0api.szaiai.com healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3012/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - it0-network notification-service: build: context: ../.. dockerfile: Dockerfile.service args: SERVICE_NAME: notification-service SERVICE_PORT: 3013 container_name: it0-notification-service restart: unless-stopped ports: - "13013:3013" environment: - DB_HOST=postgres - DB_PORT=5432 - DB_USERNAME=${POSTGRES_USER:-it0} - DB_PASSWORD=${POSTGRES_PASSWORD:-it0_dev} - DB_DATABASE=${POSTGRES_DB:-it0} - NOTIFICATION_SERVICE_PORT=3013 - JWT_SECRET=${JWT_SECRET:-dev-jwt-secret} healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3013/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: postgres: condition: service_healthy networks: - it0-network # ===== LiveKit Infrastructure ===== # NOTE: livekit-server, voice-agent, voice-service use host networking # to eliminate docker-proxy overhead for real-time audio (WebRTC UDP). # Bridge mode created 600+ docker-proxy processes for port-range mappings. livekit-server: image: livekit/livekit-server:latest container_name: it0-livekit-server restart: unless-stopped network_mode: host command: --config /etc/livekit.yaml volumes: - ./livekit.yaml:/etc/livekit.yaml:ro healthcheck: test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:7880 || exit 1"] interval: 10s timeout: 5s retries: 3 voice-agent: build: context: ../../packages/services/voice-agent container_name: it0-voice-agent restart: unless-stopped network_mode: host volumes: - ../../data/voice-models/huggingface:/root/.cache/huggingface - ../../data/voice-models/torch-hub:/root/.cache/torch/hub environment: - LIVEKIT_URL=ws://127.0.0.1:7880 - LIVEKIT_API_KEY=devkey - LIVEKIT_API_SECRET=devsecret - AGENT_SERVICE_URL=http://127.0.0.1:13002 - STT_PROVIDER=${STT_PROVIDER:-local} - TTS_PROVIDER=${TTS_PROVIDER:-local} - WHISPER_MODEL=${WHISPER_MODEL:-base} - WHISPER_LANGUAGE=${WHISPER_LANGUAGE:-zh} - KOKORO_VOICE=${KOKORO_VOICE:-zf_xiaoxiao} - DEVICE=${VOICE_DEVICE:-cpu} - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_BASE_URL=${OPENAI_BASE_URL} - OPENAI_STT_MODEL=${OPENAI_STT_MODEL:-gpt-4o-transcribe} - OPENAI_TTS_MODEL=${OPENAI_TTS_MODEL:-gpt-4o-mini-tts} - OPENAI_TTS_VOICE=${OPENAI_TTS_VOICE:-coral} depends_on: livekit-server: condition: service_healthy agent-service: condition: service_healthy # ===== Voice Service (LiveKit token + Twilio) ===== # NOTE: voice-service stays on bridge networking (single TCP port, no proxy overhead). # Only livekit-server and voice-agent need host mode (UDP port ranges). voice-service: build: context: ../../packages/services/voice-service container_name: it0-voice-service restart: unless-stopped ports: - "13008:3008" volumes: - ../../data/voice-models/huggingface:/root/.cache/huggingface - ../../data/voice-models/torch-hub:/root/.cache/torch/hub environment: - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY} - ANTHROPIC_BASE_URL=${ANTHROPIC_BASE_URL} - AGENT_SERVICE_URL=http://agent-service:3002 - WHISPER_MODEL=${WHISPER_MODEL:-base} - KOKORO_MODEL=${KOKORO_MODEL:-kokoro-82m} - KOKORO_VOICE=${KOKORO_VOICE:-zf_xiaoxiao} - DEVICE=${VOICE_DEVICE:-cpu} - STT_PROVIDER=${STT_PROVIDER:-local} - TTS_PROVIDER=${TTS_PROVIDER:-local} - OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_BASE_URL=${OPENAI_BASE_URL} - OPENAI_STT_MODEL=${OPENAI_STT_MODEL:-gpt-4o-transcribe} - OPENAI_TTS_MODEL=${OPENAI_TTS_MODEL:-gpt-4o-mini-tts} - OPENAI_TTS_VOICE=${OPENAI_TTS_VOICE:-coral} - LIVEKIT_API_KEY=devkey - LIVEKIT_API_SECRET=devsecret - LIVEKIT_WS_URL=ws://14.215.128.96:7880 healthcheck: test: ["CMD-SHELL", "python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:3008/docs')\""] interval: 30s timeout: 10s retries: 5 start_period: 120s depends_on: - agent-service networks: - it0-network # ===== Frontend ===== web-admin: build: context: ../../it0-web-admin container_name: it0-web-admin restart: unless-stopped ports: - "13000:3000" environment: - HOSTNAME=0.0.0.0 - API_BASE_URL=http://api-gateway:8000 - NEXT_PUBLIC_API_BASE_URL=/api/proxy - NEXT_PUBLIC_WS_URL=wss://it0api.szaiai.com healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://127.0.0.1:3000/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s timeout: 5s retries: 3 start_period: 15s depends_on: - api-gateway networks: - it0-network volumes: postgres_data: claude_tenants: version_data: networks: it0-network: driver: bridge