services: db: image: ankane/pgvector:latest container_name: pmflow-db restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB:-pmflow} POSTGRES_USER: ${POSTGRES_USER:-pmflow} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-pmflow_pass} TZ: Asia/Shanghai PGTZ: Asia/Shanghai ports: - "${DB_PORT:-5435}:5432" volumes: - pg_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-pmflow} -d ${POSTGRES_DB:-pmflow}"] interval: 5s timeout: 5s retries: 20 redis: image: redis:7-alpine container_name: pmflow-redis restart: unless-stopped command: [ "redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD:-pmflow_redis_pass}", "--maxmemory", "512mb", "--maxmemory-policy", "allkeys-lru" ] ports: - "${REDIS_PORT:-6379}:6379" volumes: - redis_data:/data healthcheck: test: ["CMD-SHELL", "redis-cli -a ${REDIS_PASSWORD:-pmflow_redis_pass} ping | grep PONG"] interval: 5s timeout: 3s retries: 20 backend: build: context: . dockerfile: backend/Dockerfile container_name: pmflow-backend restart: unless-stopped depends_on: db: condition: service_healthy redis: condition: service_healthy environment: TZ: Asia/Shanghai PGTZ: Asia/Shanghai DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:?missing_POSTGRES_USER}:${POSTGRES_PASSWORD:?missing_POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:?missing_POSTGRES_DB} REDIS_URL: redis://:${REDIS_PASSWORD:?missing_REDIS_PASSWORD}@redis:6379/0 PMFLOW_EXPECTED_DB_DIALECT: ${PMFLOW_EXPECTED_DB_DIALECT:?missing_PMFLOW_EXPECTED_DB_DIALECT} CORS_ORIGINS: ${CORS_ORIGINS:?missing_CORS_ORIGINS} LLM_PROVIDER: ${LLM_PROVIDER:?missing_LLM_PROVIDER} LLM_FALLBACK_PROVIDER: ${LLM_FALLBACK_PROVIDER:?missing_LLM_FALLBACK_PROVIDER} CONTROL_PLANE_ROUTING_ENABLED: ${CONTROL_PLANE_ROUTING_ENABLED:?missing_CONTROL_PLANE_ROUTING_ENABLED} CONTROL_PLANE_ROUTING_RULES_PATH: ${CONTROL_PLANE_ROUTING_RULES_PATH:?missing_CONTROL_PLANE_ROUTING_RULES_PATH} CONTROL_PLANE_AGENTS_DEFAULT_TIMEOUT_SECONDS: ${CONTROL_PLANE_AGENTS_DEFAULT_TIMEOUT_SECONDS:?missing_CONTROL_PLANE_AGENTS_DEFAULT_TIMEOUT_SECONDS} LLM_MIXED_ROUTING_ENABLED: ${LLM_MIXED_ROUTING_ENABLED:?missing_LLM_MIXED_ROUTING_ENABLED} LLM_ROUTING_DEFAULT_TIER: ${LLM_ROUTING_DEFAULT_TIER:?missing_LLM_ROUTING_DEFAULT_TIER} LLM_LOW_TO_HIGH_ESCALATION_ENABLED: ${LLM_LOW_TO_HIGH_ESCALATION_ENABLED:?missing_LLM_LOW_TO_HIGH_ESCALATION_ENABLED} LLM_DIRECT_EMERGENCY_ENABLED: ${LLM_DIRECT_EMERGENCY_ENABLED:?missing_LLM_DIRECT_EMERGENCY_ENABLED} LLM_REQUEST_TIMEOUT: ${LLM_REQUEST_TIMEOUT:?missing_LLM_REQUEST_TIMEOUT} AI_PMO_INTENT_LLM_ENABLED: ${AI_PMO_INTENT_LLM_ENABLED:-true} AI_PMO_INTENT_ARBITRATION_ENABLED: ${AI_PMO_INTENT_ARBITRATION_ENABLED:-true} AI_PMO_INTENT_CONFLICT_POLICY: ${AI_PMO_INTENT_CONFLICT_POLICY:-pending_manual} AI_PMO_INTENT_LLM_TIMEOUT_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_SECONDS:-30} AI_PMO_INTENT_LLM_TIMEOUT_COMPLEX_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_COMPLEX_SECONDS:-55} AI_PMO_INTENT_LLM_TIMEOUT_FALLBACK_SECONDS: ${AI_PMO_INTENT_LLM_TIMEOUT_FALLBACK_SECONDS:-45} AI_PMO_INTENT_LLM_TOTAL_BUDGET_SECONDS: ${AI_PMO_INTENT_LLM_TOTAL_BUDGET_SECONDS:-70} AI_PMO_INTENT_LLM_STAGE2_CONFIDENCE_THRESHOLD: ${AI_PMO_INTENT_LLM_STAGE2_CONFIDENCE_THRESHOLD:-0.72} AI_PMO_INTENT_LLM_STAGE2_MIN_REMAINING_SECONDS: ${AI_PMO_INTENT_LLM_STAGE2_MIN_REMAINING_SECONDS:-12} AI_PMO_INTENT_STAGE1_TIMEOUT_CAP_SECONDS: ${AI_PMO_INTENT_STAGE1_TIMEOUT_CAP_SECONDS:-8} AI_PMO_INTENT_LLM_MAX_TOKENS_LOW: ${AI_PMO_INTENT_LLM_MAX_TOKENS_LOW:-260} AI_PMO_INTENT_LLM_MAX_TOKENS_HIGH: ${AI_PMO_INTENT_LLM_MAX_TOKENS_HIGH:-360} LLM_HTTP_STATUS_RETRY_BUDGET: ${LLM_HTTP_STATUS_RETRY_BUDGET:-2} LLM_INTENT_HTTP_RETRY_BUDGET: ${LLM_INTENT_HTTP_RETRY_BUDGET:-0} LLM_INTENT_EMPTY_RETRY_BUDGET: ${LLM_INTENT_EMPTY_RETRY_BUDGET:-2} LLM_INTENT_STRUCTURED_REPAIR_ENABLED: ${LLM_INTENT_STRUCTURED_REPAIR_ENABLED:-true} AUTO_CREATE_TABLES: "false" DASHSCOPE_API_KEY: ${DASHSCOPE_API_KEY:?missing_DASHSCOPE_API_KEY} OPENAI_API_KEY: ${OPENAI_API_KEY:?missing_OPENAI_API_KEY} DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} OLLAMA_HOST: ${OLLAMA_HOST:?missing_OLLAMA_HOST} OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://host.docker.internal:11434} OLLAMA_MODEL: ${OLLAMA_MODEL:-llama3:latest} HTTP_PROXY: "" HTTPS_PROXY: "" ALL_PROXY: "" http_proxy: "" https_proxy: "" all_proxy: "" NO_PROXY: "localhost,127.0.0.1,db,redis,backend,frontend,api.edgefn.net,open.bigmodel.cn" no_proxy: "localhost,127.0.0.1,db,redis,backend,frontend,api.edgefn.net,open.bigmodel.cn" ports: - "${BACKEND_PORT:?missing_BACKEND_PORT}:8000" volumes: - ./backend/.control_plane:/app/backend/.control_plane:ro - ./logs:/app/logs healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8000/healthz >/dev/null || exit 1"] interval: 10s timeout: 5s retries: 15 frontend: build: context: ./frontend dockerfile: Dockerfile container_name: pmflow-frontend restart: unless-stopped depends_on: backend: condition: service_healthy ports: - "${FRONTEND_PORT:-5174}:80" volumes: pg_data: redis_data: