feat: production docker-compose with health checks + resource limits (API-AI-077)
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 45s

- Add health checks: api (GET /health), worker, heavy-runtime, nginx
- Add resource limits (deploy.resources) for all 6 services
- Add heavy-runtime service (Rust AI Worker)
- Add INTERNAL_API_KEY + CREDENTIAL_ENCRYPTION_KEY env vars
- nginx health check via GET /health

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
wangdl 2026-06-18 13:53:24 +08:00
parent 5c7b8d1855
commit 094f00553a

View File

@ -1,6 +1,9 @@
version: '3.8' version: '3.8'
# ── 生产部署配置(含 health check、资源限制 ──
services: services:
# ── MySQL 8.0 ──
mysql: mysql:
image: mysql:8.0 image: mysql:8.0
container_name: zhixi-mysql container_name: zhixi-mysql
@ -22,7 +25,16 @@ services:
retries: 5 retries: 5
networks: networks:
- zhixi-net - zhixi-net
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
# ── Redis 7 ──
redis: redis:
image: redis:7-alpine image: redis:7-alpine
container_name: zhixi-redis container_name: zhixi-redis
@ -38,7 +50,16 @@ services:
retries: 5 retries: 5
networks: networks:
- zhixi-net - zhixi-net
deploy:
resources:
limits:
cpus: '0.5'
memory: 256M
reservations:
cpus: '0.1'
memory: 64M
# ── API Server (NestJS) ──
api: api:
build: build:
context: . context: .
@ -65,6 +86,8 @@ services:
JWT_SECRET: ${JWT_SECRET:-change_me_in_production} JWT_SECRET: ${JWT_SECRET:-change_me_in_production}
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-1h} JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-1h}
JWT_REFRESH_EXPIRES_IN: ${JWT_REFRESH_EXPIRES_IN:-7d} JWT_REFRESH_EXPIRES_IN: ${JWT_REFRESH_EXPIRES_IN:-7d}
INTERNAL_API_KEY: ${INTERNAL_API_KEY:-change_me_runtime_key}
CREDENTIAL_ENCRYPTION_KEY: ${CREDENTIAL_ENCRYPTION_KEY:-change_me_32_bytes_key!!}
DEV_SECRET: ${DEV_SECRET:-} DEV_SECRET: ${DEV_SECRET:-}
APPLE_BUNDLE_ID: ${APPLE_BUNDLE_ID:-cloud.longde.AIStudyApp} APPLE_BUNDLE_ID: ${APPLE_BUNDLE_ID:-cloud.longde.AIStudyApp}
APPLE_ISSUER: ${APPLE_ISSUER:-https://appleid.apple.com} APPLE_ISSUER: ${APPLE_ISSUER:-https://appleid.apple.com}
@ -84,9 +107,24 @@ services:
condition: service_healthy condition: service_healthy
redis: redis:
condition: service_healthy condition: service_healthy
healthcheck:
test: ['CMD', 'wget', '-qO-', 'http://localhost:3000/health']
interval: 15s
timeout: 5s
retries: 3
start_period: 30s
networks: networks:
- zhixi-net - zhixi-net
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
# ── API Worker (Prisma CRON / Queue) ──
worker: worker:
build: build:
context: . context: .
@ -102,12 +140,10 @@ services:
REDIS_PASSWORD: '' REDIS_PASSWORD: ''
REDIS_DB: '0' REDIS_DB: '0'
AI_PROVIDER: ${AI_PROVIDER:-mock} AI_PROVIDER: ${AI_PROVIDER:-mock}
AI_DEFAULT_TIER: ${AI_DEFAULT_TIER:-primary}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-} DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com} DEEPSEEK_BASE_URL: ${DEEPSEEK_BASE_URL:-https://api.deepseek.com}
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
MINIMAX_BASE_URL: ${MINIMAX_BASE_URL:-https://api.minimaxi.com}
JWT_SECRET: ${JWT_SECRET:-change_me_in_production} JWT_SECRET: ${JWT_SECRET:-change_me_in_production}
INTERNAL_API_KEY: ${INTERNAL_API_KEY:-change_me_runtime_key}
STORAGE_DRIVER: ${STORAGE_DRIVER:-cos} STORAGE_DRIVER: ${STORAGE_DRIVER:-cos}
STORAGE_COS_SECRET_ID: ${STORAGE_COS_SECRET_ID:-} STORAGE_COS_SECRET_ID: ${STORAGE_COS_SECRET_ID:-}
STORAGE_COS_SECRET_KEY: ${STORAGE_COS_SECRET_KEY:-} STORAGE_COS_SECRET_KEY: ${STORAGE_COS_SECRET_KEY:-}
@ -121,7 +157,53 @@ services:
condition: service_healthy condition: service_healthy
networks: networks:
- zhixi-net - zhixi-net
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
# ── Heavy Runtime (Rust AI Worker) ──
heavy-runtime:
build:
context: ../zhixi-heavy-runtime
dockerfile: Dockerfile
image: zhixi-heavy-runtime:latest
container_name: zhixi-heavy-runtime
restart: unless-stopped
environment:
RUST_LOG: ${RUNTIME_LOG_LEVEL:-info}
RUNTIME_INSTANCE_ID: heavy-runtime-1
API_INTERNAL_BASE_URL: http://api:3000
RUNTIME_SERVICE_TOKEN: ${INTERNAL_API_KEY:-change_me_runtime_key}
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-}
MAX_CONCURRENCY: '4'
POLL_INTERVAL_MS: '5000'
JOB_TIMEOUT_SECONDS: '120'
HTTP_TIMEOUT_SECONDS: '30'
depends_on:
- api
healthcheck:
test: ['CMD', 'wget', '-qO-', 'http://localhost:8080/health']
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
networks:
- zhixi-net
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
# ── NGINX ──
nginx: nginx:
image: nginx:1.25-alpine image: nginx:1.25-alpine
container_name: zhixi-nginx container_name: zhixi-nginx
@ -134,8 +216,21 @@ services:
- ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/conf.d:/etc/nginx/conf.d:ro
depends_on: depends_on:
- api - api
healthcheck:
test: ['CMD', 'wget', '-qO-', 'http://localhost/health']
interval: 15s
timeout: 5s
retries: 3
networks: networks:
- zhixi-net - zhixi-net
deploy:
resources:
limits:
cpus: '0.5'
memory: 128M
reservations:
cpus: '0.1'
memory: 32M
volumes: volumes:
mysql_data: mysql_data: