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} OPENAI_BASE_URL: ${OPENAI_BASE_URL:?missing_OPENAI_BASE_URL} OPENCLAW_ROUTING_ENABLED: ${OPENCLAW_ROUTING_ENABLED:?missing_OPENCLAW_ROUTING_ENABLED} OPENCLAW_ROUTING_RULES_PATH: ${OPENCLAW_ROUTING_RULES_PATH:?missing_OPENCLAW_ROUTING_RULES_PATH} OPENCLAW_AGENTS_DEFAULT_TIMEOUT_SECONDS: ${OPENCLAW_AGENTS_DEFAULT_TIMEOUT_SECONDS:?missing_OPENCLAW_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_ROUTING_LOW_PROVIDER: ${LLM_ROUTING_LOW_PROVIDER:?missing_LLM_ROUTING_LOW_PROVIDER} LLM_ROUTING_LOW_MODEL: ${LLM_ROUTING_LOW_MODEL:?missing_LLM_ROUTING_LOW_MODEL} LLM_ROUTING_HIGH_PROVIDER: ${LLM_ROUTING_HIGH_PROVIDER:?missing_LLM_ROUTING_HIGH_PROVIDER} LLM_ROUTING_HIGH_MODEL: ${LLM_ROUTING_HIGH_MODEL:?missing_LLM_ROUTING_HIGH_MODEL} LLM_ROUTING_SAFETY_PROVIDER: ${LLM_ROUTING_SAFETY_PROVIDER:?missing_LLM_ROUTING_SAFETY_PROVIDER} LLM_ROUTING_SAFETY_MODEL: ${LLM_ROUTING_SAFETY_MODEL:?missing_LLM_ROUTING_SAFETY_MODEL} LLM_DIRECT_EMERGENCY_ENABLED: ${LLM_DIRECT_EMERGENCY_ENABLED:?missing_LLM_DIRECT_EMERGENCY_ENABLED} LLM_REQUEST_TIMEOUT: ${LLM_REQUEST_TIMEOUT:?missing_LLM_REQUEST_TIMEOUT} AUTO_CREATE_TABLES: "false" GLM_API_KEY: ${GLM_API_KEY:?missing_GLM_API_KEY} GLM_BASE_URL: ${GLM_BASE_URL:?missing_GLM_BASE_URL} GLM_MODEL: ${GLM_MODEL:?missing_GLM_MODEL} OPENAI_API_KEY: ${OPENAI_API_KEY:?missing_OPENAI_API_KEY} OPENAI_MODEL: ${OPENAI_MODEL:?missing_OPENAI_MODEL} DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY} OLLAMA_HOST: ${OLLAMA_HOST:?missing_OLLAMA_HOST} OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:?missing_OLLAMA_BASE_URL} OLLAMA_MODEL: ${OLLAMA_MODEL:?missing_OLLAMA_MODEL} PYTHONUNBUFFERED: "1" command: [ "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--reload-dir", "app", "--reload-exclude", "*/__pycache__/*", "--reload-exclude", "*.pyc", "--reload-exclude", "alembic/versions/*" ] volumes: - ./backend:/app/backend - ./.openclaw:/app/.openclaw:ro - ./logs:/app/logs ports: - "${BACKEND_PORT:?missing_BACKEND_PORT}:8000" healthcheck: test: ["CMD-SHELL", "curl -fsS http://localhost:8000/healthz >/dev/null || exit 1"] interval: 10s timeout: 5s retries: 15 frontend: image: node:20-alpine container_name: pmflow-frontend restart: unless-stopped depends_on: backend: condition: service_healthy working_dir: /app environment: CHOKIDAR_USEPOLLING: "true" VITE_DEV_API_PROXY_TARGET: "http://backend:8000" command: ["/bin/sh", "-c", "npm ci && npm run dev -- --host 0.0.0.0 --port 5174"] volumes: - ./frontend:/app - frontend_node_modules:/app/node_modules ports: - "${FRONTEND_PORT:-5174}:5174" volumes: pg_data: redis_data: frontend_node_modules: