# 丹阳公益婚恋服务平台 - 本地完整环境 services: postgres: image: postgres:16-alpine container_name: danyang-match-postgres environment: POSTGRES_DB: danyang_match POSTGRES_USER: postgres POSTGRES_PASSWORD: ${DB_PASSWORD:?请在本地 .env 设置 DB_PASSWORD} ports: - "5434:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d danyang_match"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine container_name: danyang-match-redis ports: - "6381:6379" command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:?请在本地 .env 设置 REDIS_PASSWORD} volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:?请在本地 .env 设置 REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 minio: # pin 到确定版本:`latest` 是滚动 tag,MinIO 已多次改动镜像内容(如移除内嵌 console), # 用 `latest` 会让 CI 的 E2E 拉到一个与本地验证时不同的镜像 → 不可复现的偶发失败。 image: minio/minio:RELEASE.2025-09-07T16-13-09Z container_name: danyang-match-minio command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:?请在本地 .env 设置 MINIO_ACCESS_KEY} MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:?请在本地 .env 设置 MINIO_SECRET_KEY} ports: - "9003:9000" - "9004:9001" volumes: - minio_data:/data healthcheck: test: ["CMD", "mc", "ready", "local"] interval: 10s timeout: 5s retries: 5 backend: build: context: ../backend dockerfile: Dockerfile container_name: danyang-match-backend depends_on: postgres: condition: service_healthy redis: condition: service_healthy minio: condition: service_healthy environment: SPRING_PROFILES_ACTIVE: docker DB_HOST: postgres DB_PORT: 5432 DB_NAME: danyang_match DB_USER: postgres DB_PASSWORD: ${DB_PASSWORD:?请在本地 .env 设置 DB_PASSWORD} REDIS_HOST: redis REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD:?请在本地 .env 设置 REDIS_PASSWORD} MINIO_ENDPOINT: http://minio:9000 MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?请在本地 .env 设置 MINIO_ACCESS_KEY} MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?请在本地 .env 设置 MINIO_SECRET_KEY} JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET:?请在本地 .env 设置 JWT_ACCESS_SECRET} JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:?请在本地 .env 设置 JWT_REFRESH_SECRET} ENCRYPTION_KEY: ${ENCRYPTION_KEY:?请在本地 .env 设置 ENCRYPTION_KEY} WECHAT_APP_ID: dev-wechat-app-id WECHAT_APP_SECRET: ${WECHAT_APP_SECRET:-} ports: - "8085:8080" healthcheck: # 与 nginx 同理,用 127.0.0.1 而非 localhost:backend 当前恰好双栈监听(:::8080), # 故 localhost 解析成 ::1 也能通;但探针不应依赖「服务恰好绑了 IPv6」这一偶然事实。 test: ["CMD-SHELL", "wget -qO /dev/null http://127.0.0.1:8080/api/v1/health/live || exit 1"] interval: 30s timeout: 10s retries: 3 admin-web: build: context: ../admin-web dockerfile: Dockerfile container_name: danyang-match-admin depends_on: backend: condition: service_healthy nginx: image: nginx:alpine container_name: danyang-match-nginx depends_on: backend: condition: service_healthy admin-web: condition: service_started ports: - "6688:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro healthcheck: # 探针地址必须写 127.0.0.1,不能写 localhost。 # 实测:nginx 的 `listen 80` 只绑 IPv4(容器内 netstat = 0.0.0.0:80), # 而 /etc/hosts 中 `::1 localhost` 同样存在,busybox wget 解析 localhost 时优先取 ::1 # → 探针连 IPv6 回环被拒(Connection refused)→ 容器被标 unhealthy, # 但服务其实完全正常(宿主 curl http://localhost:6688/nginx-health = 200)。 # 这是一类「探针与被探对象脱钩」的假告警,还会连带拖垮 depends_on: service_healthy。 test: ["CMD-SHELL", "wget -qO /dev/null http://127.0.0.1/nginx-health || exit 1"] interval: 30s timeout: 10s retries: 3 volumes: postgres_data: redis_data: minio_data: