# ============================================================ # 青叶 (QingYe) —— 生产环境 Docker Compose(自包含) # ------------------------------------------------------------ # 启动命令: # docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --build # # 说明: # * 此文件为「生产专用」,与开发用的 docker-compose.yml 完全独立。 # 生产服务器上请始终带 -f docker-compose.prod.yml,切勿运行裸 docker compose up。 # * 所有密钥通过 --env-file .env.prod 注入(密钥文件已在 .gitignore 中)。 # * 后端必须「单进程」运行:WebSocket 连接管理器为进程内存单例 # (见 backend/app/websocket/manager.py),多 worker / gunicorn 会导致 # 跨用户、跨标签页的实时消息丢失。镜像 Dockerfile.prod 已固定为单进程。 # * postgres / redis 不向主机暴露端口,仅容器内网互通。 # * backend / frontend 仅绑定 127.0.0.1,由宿主机 Nginx 反向代理对外。 # ============================================================ services: # ==================== PostgreSQL ==================== postgres: image: postgres:16-alpine container_name: qingye-postgres restart: always environment: POSTGRES_DB: ${POSTGRES_DB:-qingye} POSTGRES_USER: ${POSTGRES_USER:-qingye} # 密钥必填:缺失时 compose 会直接报错,绝不回退到弱口令 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?请在 .env.prod 中设置 POSTGRES_PASSWORD} volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-qingye} -d ${POSTGRES_DB:-qingye}"] interval: 5s timeout: 5s retries: 5 networks: - qingye-network # 生产环境不向宿主机映射 5432 端口(安全) # ==================== Redis ==================== # Redis 非可选:flash_service 原子计数 / draft_service 草稿等依赖它 redis: image: redis:7-alpine container_name: qingye-redis restart: always command: redis-server --appendonly yes volumes: - redisdata:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 5s retries: 5 networks: - qingye-network # 生产环境不向宿主机映射 6379 端口(安全) # ==================== Backend (FastAPI) ==================== backend: build: context: ./backend dockerfile: Dockerfile.prod container_name: qingye-backend restart: always ports: - "127.0.0.1:8000:8000" # 仅本机可达,由宿主机 Nginx 反向代理 environment: # DATABASE_URL 由上面的 POSTGRES_* 变量自动拼接,保持口令一致 DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-qingye}:${POSTGRES_PASSWORD:?请在 .env.prod 中设置 POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-qingye} REDIS_URL: redis://redis:6379/0 JWT_SECRET_KEY: ${JWT_SECRET_KEY:?请在 .env.prod 中设置 JWT_SECRET_KEY} JWT_REFRESH_SECRET_KEY: ${JWT_REFRESH_SECRET_KEY:?请在 .env.prod 中设置 JWT_REFRESH_SECRET_KEY} CORS_ORIGINS: ${CORS_ORIGINS:-https://www.e4s.world} ADMIN_PASSWORD: ${ADMIN_PASSWORD:?请在 .env.prod 中设置 ADMIN_PASSWORD} APP_ENV: production volumes: - upload_data:/app/uploads # 仅持久化用户上传文件,不挂载源码(运行镜像内已打包的代码) depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - qingye-network security_opt: - no-new-privileges:true # ==================== Frontend (Vue 3 构建产物 + Nginx) ==================== frontend: build: context: ./frontend dockerfile: Dockerfile.prod # 生产构建为「域名无关」:不传入任何 VITE_* 变量, # 前端代码会自动使用页面同源地址(/api/v1 与 wss://当前域名)。 container_name: qingye-frontend restart: always ports: - "127.0.0.1:8080:80" # 仅本机可达,由宿主机 Nginx 反向代理 depends_on: - backend networks: - qingye-network security_opt: - no-new-privileges:true # ==================== 数据卷 ==================== volumes: pgdata: redisdata: upload_data: # ==================== 网络 ==================== networks: qingye-network: driver: bridge