services: # PostgreSQL 数据库(使用最新版 17) postgres: image: m.daocloud.io/docker.io/pgvector/pgvector:pg17 container_name: life-echo-postgres ports: - "127.0.0.1:${POSTGRES_HOST_PORT:-5432}:5432" # 仅绑定 localhost,通过 SSH 隧道访问 environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} POSTGRES_DB: ${POSTGRES_DB:-life_echo} volumes: - postgres_data:/var/lib/postgresql/data restart: always healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 10s timeout: 5s retries: 5 networks: - life-echo-network logging: driver: "json-file" options: max-size: "10m" max-file: "3" # Redis 服务(业务 key DB/0;Celery broker/backend 由应用自动使用 DB/1) redis: image: m.daocloud.io/docker.io/library/redis:7-alpine container_name: life-echo-redis # ports: # - "6379:6379" # 不暴露到宿主机,仅在 Docker 网络内部访问 environment: REDIS_PASSWORD: ${REDIS_PASSWORD:-} volumes: - redis_data:/data command: > sh -c 'exec redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru $${REDIS_PASSWORD:+--requirepass "$$REDIS_PASSWORD"}' restart: always healthcheck: test: [ "CMD-SHELL", 'if [ -n "$$REDIS_PASSWORD" ]; then redis-cli -a "$$REDIS_PASSWORD" ping | grep -q PONG; else redis-cli ping | grep -q PONG; fi', ] interval: 10s timeout: 5s retries: 5 networks: - life-echo-network logging: driver: "json-file" options: max-size: "10m" max-file: "3" # FastAPI 应用 # 运行时统一读取 .env;部署时在远端将 .env.staging 或 .env.production 复制为 .env。 api: build: context: . dockerfile: Dockerfile image: life-echo-api:latest container_name: life-echo-api-prod # 默认仅绑定本机回环,交给宿主机 Caddy/反代;staging 如需 IP:port 直连,可在 .env 设置 LIFE_ECHO_API_HOST_BIND=0.0.0.0。 # 若与 Cosmetic 等共用主机且 8000 已被占用,在 .env 中设置 LIFE_ECHO_API_HOST_PORT=其它端口并在 Caddyfile / app env 中一致。 ports: - "${LIFE_ECHO_API_HOST_BIND:-127.0.0.1}:${LIFE_ECHO_API_HOST_PORT:-8000}:8000" env_file: - .env environment: - APP_ENV=${APP_ENV:-production} - REDIS_URL=redis://redis:6379/0 - CELERY_REDIS_URL=redis://redis:6379/1 - REDIS_PASSWORD=${REDIS_PASSWORD:-} volumes: - /root/apiclient_key.pem:/app/certs/apiclient_key.pem:ro restart: always depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "python", "-c", "import http.client; conn = http.client.HTTPConnection('localhost', 8000); conn.request('GET', '/health'); r = conn.getresponse(); exit(0 if r.status == 200 else 1)"] interval: 15s timeout: 5s retries: 6 start_period: 120s networks: - life-echo-network logging: driver: "json-file" options: max-size: "10m" max-file: "3" # Celery Worker(后台任务处理,禁用镜像内置的 8000 端口健康检查,改用 celery inspect ping) celery-worker: build: context: . dockerfile: Dockerfile image: life-echo-api:latest container_name: life-echo-celery-worker command: celery -A app.tasks.celery_app worker --loglevel=info --concurrency=4 -Q celery,memory_idle env_file: - .env environment: - APP_ENV=${APP_ENV:-production} - REDIS_URL=redis://redis:6379/0 - CELERY_REDIS_URL=redis://redis:6379/1 - REDIS_PASSWORD=${REDIS_PASSWORD:-} restart: always depends_on: postgres: condition: service_healthy redis: condition: service_healthy api: condition: service_healthy healthcheck: test: ["CMD-SHELL", "celery -A app.tasks.celery_app inspect ping --timeout 10 2>/dev/null | grep -q pong || exit 1"] interval: 30s timeout: 15s retries: 3 start_period: 30s networks: - life-echo-network logging: driver: "json-file" options: max-size: "10m" max-file: "3" celery-beat: build: context: . dockerfile: Dockerfile image: life-echo-api:latest container_name: life-echo-celery-beat command: celery -A app.tasks.celery_app beat --loglevel=info env_file: - .env environment: - APP_ENV=${APP_ENV:-production} - REDIS_URL=redis://redis:6379/0 - CELERY_REDIS_URL=redis://redis:6379/1 - REDIS_PASSWORD=${REDIS_PASSWORD:-} restart: always depends_on: postgres: condition: service_healthy redis: condition: service_healthy celery-worker: condition: service_started networks: - life-echo-network logging: driver: "json-file" options: max-size: "10m" max-file: "3" flower: build: context: . dockerfile: Dockerfile image: life-echo-api:latest container_name: life-echo-flower command: > sh -c 'celery -A app.tasks.celery_app flower --port=5555 --basic_auth=$${FLOWER_USER:-admin}:$${FLOWER_PASSWORD:-changeme}' ports: - "127.0.0.1:${FLOWER_HOST_PORT:-5555}:5555" env_file: - .env environment: - APP_ENV=${APP_ENV:-production} - REDIS_URL=redis://redis:6379/0 - CELERY_REDIS_URL=redis://redis:6379/1 - REDIS_PASSWORD=${REDIS_PASSWORD:-} - FLOWER_USER=${FLOWER_USER:-admin} - FLOWER_PASSWORD=${FLOWER_PASSWORD:-changeme} restart: always depends_on: redis: condition: service_healthy celery-worker: condition: service_started networks: - life-echo-network logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: life-echo-network: external: true name: api_life-echo-network volumes: postgres_data: driver: local redis_data: driver: local