# ============================================ # DPB Docker Compose 配置文件 # ============================================ # 使用方式: # 开发环境: docker compose --env-file .env up -d # 生产环境: docker compose --env-file .env -f docker-compose.yaml up -d # ============================================ services: # ==================== 数据库服务(PostgreSQL 16) ==================== postgres: container_name: postgres image: postgres:16 restart: unless-stopped platform: linux/amd64 environment: TZ: "Asia/Shanghai" POSTGRES_DB: "${DATABASE_NAME:-dpb}" POSTGRES_USER: "${DATABASE_USER:-dpb}" POSTGRES_PASSWORD: "${DATABASE_PASSWORD:?错误: 请设置 DATABASE_PASSWORD 环境变量}" # 仅绑定本机回环:数据库不应暴露到公网/局域网,只允许后端容器与运维本机访问 ports: - "127.0.0.1:${DATABASE_PORT:-5432}:5432" volumes: - pg_data:/var/lib/postgresql/data - /etc/localtime:/etc/localtime:ro networks: - app_network healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USER:-dpb}"] interval: 10s timeout: 10s retries: 10 start_period: 30s logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: memory: 1g reservations: memory: 256m # ==================== Redis 服务 ==================== redis: container_name: redis image: redis:7-alpine restart: unless-stopped platform: linux/amd64 environment: TZ: "Asia/Shanghai" # 仅绑定本机回环:Redis 不应暴露到公网/局域网 ports: - "127.0.0.1:${REDIS_PORT:-6379}:6379" volumes: - redis_data:/data # 挂载自定义 redis.conf(若存在则覆盖默认配置) - ./redis/conf/redis.conf:/usr/local/etc/redis/redis.conf:ro command: > redis-server /usr/local/etc/redis/redis.conf --requirepass ${REDIS_PASSWORD:?错误: 请设置 REDIS_PASSWORD 环境变量} networks: - app_network healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 10s logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: memory: 512m reservations: memory: 128m # ==================== 后端服务 ==================== backend: container_name: backend build: context: ../ dockerfile: ./docker/backend/Dockerfile args: DEPLOY_ENV: "${DEPLOY_ENV:-prod}" image: "backend:${BACKEND_IMAGE_TAG:-3.0.0}" restart: unless-stopped platform: linux/amd64 # 以非 root 运行(与 Dockerfile USER app 一致),降权容器内进程 user: "1001:1001" # 纵深防御:禁止提权、丢弃全部 capabilities(uvicorn 绑 8001 端口无需特权) security_opt: - no-new-privileges:true cap_drop: - ALL tmpfs: - /tmp environment: TZ: "Asia/Shanghai" # 运行环境,对应 backend/env/.env.{dev,prod} ENVIRONMENT: "${DEPLOY_ENV:-prod}" # PostgreSQL 连接配置(对应 backend/env/.env.prod 的 DATABASE_*) DATABASE_TYPE: "postgres" DATABASE_HOST: "postgres" DATABASE_PORT: "5432" DATABASE_USER: "${DATABASE_USER:-dpb}" DATABASE_PASSWORD: "${DATABASE_PASSWORD:?错误: 请设置 DATABASE_PASSWORD 环境变量}" DATABASE_NAME: "${DATABASE_NAME:-dpb}" # Redis 连接配置 REDIS_HOST: "redis" REDIS_PORT: "6379" REDIS_PASSWORD: "${REDIS_PASSWORD}" REDIS_DB_NAME: "1" REDIS_ENABLE: "true" # JWT 签名密钥:必须通过 docker/.env 配置独立随机值(settings 会在 prod 下拒绝默认密钥) SECRET_KEY: "${SECRET_KEY:?错误: 请设置 SECRET_KEY 环境变量(生产 JWT 密钥,勿用默认值)}" # ── 应用层配置:镜像已排除 backend/env,以下全部由 docker/.env 注入(勿再依赖 .env.prod)── # CORS 白名单:生产必须配置具体域名,为空时后端 prod 校验将拒绝启动 PROD_CORS_ORIGINS: "${PROD_CORS_ORIGINS:-}" # Host 头白名单(nginx 反代时后端据此校验 Host,必须配置真实域名,否则请求 403) ALLOWED_HOSTS: "${ALLOWED_HOSTS:?错误: 请设置 ALLOWED_HOSTS(如 [\"admin.example.com\",\"*.example.com\"])}" # 是否信任 nginx 透传的客户端 IP 头(经可信代理时必须 true) IP_TRUST_PROXY_HEADERS: "${IP_TRUST_PROXY_HEADERS:-true}" # 登录暴力破解防护 LOGIN_MAX_FAILURES: "${LOGIN_MAX_FAILURES:-5}" LOGIN_FAILURE_WINDOW_SECONDS: "${LOGIN_FAILURE_WINDOW_SECONDS:-900}" LOGIN_LOCKOUT_SECONDS: "${LOGIN_LOCKOUT_SECONDS:-900}" # 登录验证码 / 演示模式 / 开发默认密码(生产必须保持为空) CAPTCHA_ENABLE: "${CAPTCHA_ENABLE:-true}" DEMO_ENABLE: "${DEMO_ENABLE:-false}" DEV_DEFAULT_PASSWORD: "${DEV_DEFAULT_PASSWORD:-}" # SMTP 邮件 SMTP_HOST: "${SMTP_HOST:-}" SMTP_PORT: "${SMTP_PORT:-465}" SMTP_USER: "${SMTP_USER:-}" SMTP_PASSWORD: "${SMTP_PASSWORD:-}" SMTP_FROM: "${SMTP_FROM:-}" SMTP_FROM_NAME: "${SMTP_FROM_NAME:-客户服务}" SMTP_TLS: "${SMTP_TLS:-true}" EMAIL_CODE_EXPIRE_SECONDS: "${EMAIL_CODE_EXPIRE_SECONDS:-300}" # 大模型 OPENAI_BASE_URL: "${OPENAI_BASE_URL:-https://api.deepseek.com}" OPENAI_API_KEY: "${OPENAI_API_KEY:-}" OPENAI_MODEL: "${OPENAI_MODEL:-deepseek-chat}" # 日志级别 LOGGER_LEVEL: "${LOGGER_LEVEL:-INFO}" # 非 root 容器下禁止写 __pycache__(/home 已 chown 可写,此处避免 import 时缓存字节码) PYTHONDONTWRITEBYTECODE: "1" # 仅绑定本机回环:后端只经 nginx 对外,避免 8001 直连暴露(否则可绕过 Host 头/CORS/HTTPS 约束) ports: - "127.0.0.1:${BACKEND_PORT:-8001}:8001" # volumes: # ── 注意 ────────────────────────────────── # 生产环境禁用代码挂载:镜像已自包含(见 Dockerfile COPY ./backend/)。 # 仅本地开发需要热更新时取消注释下面这行(宿主机代码覆盖容器内代码)。 # ───────────────────────────────────────── # - ../backend:/home depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - app_network healthcheck: test: [ "CMD-SHELL", "python -c 'import urllib.request; urllib.request.urlopen(\"http://localhost:8001/common/health/check\")' || exit 1", ] interval: 15s timeout: 10s retries: 5 start_period: 40s logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: memory: 1g cpus: "1.0" reservations: memory: 256m # ==================== Nginx 服务 ==================== nginx: container_name: nginx image: nginx:1.25-alpine restart: unless-stopped platform: linux/amd64 environment: TZ: "Asia/Shanghai" ports: - "${HTTP_PORT:-80}:80" - "${HTTPS_PORT:-443}:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/web:/usr/share/nginx/html/web:ro - ./nginx/ssl:/etc/nginx/ssl:ro # 按需启用(取消注释): - ./nginx/app:/usr/share/nginx/html/app:ro - ./nginx/docs:/usr/share/nginx/html/docs:ro depends_on: backend: condition: service_started networks: - app_network healthcheck: test: ["CMD", "nginx", "-t"] interval: 30s timeout: 5s retries: 3 logging: driver: "json-file" options: max-size: "10m" max-file: "3" deploy: resources: limits: memory: 256m cpus: "0.5" reservations: memory: 64m # ==================== 持久化卷 ==================== volumes: pg_data: driver: local driver_opts: type: none device: ./postgres/data o: bind redis_data: driver: local driver_opts: type: none device: ./redis/data o: bind # ==================== 网络 ==================== networks: app_network: driver: bridge