--- id: devops.docker.scenario.compose-dev-env title: 一条命令拉起开发环境:Go API + Python AI 服务 + PostgreSQL + Redis type: scenario domain: devops stack: [docker, go, fastapi, postgresql, redis] tags: [Compose, 开发环境, 多语言, 健康检查, 配置管理] level: intermediate prerequisites: [devops.docker.compose-network-volume] est_hours: 2 related: [devops.docker.interview, devops.nginx.index] week: [3, 13, 14] --- # 一条命令拉起开发环境:Go API + Python AI 服务 + PostgreSQL + Redis ## 业务背景与问题 一个企业知识库项目:Go 写业务 API(用户、权限、文档管理),Python FastAPI 写 AI 服务(RAG 检索、调用大模型),PostgreSQL(含 pgvector)存数据和向量,Redis 做缓存。新人入职配环境要一两天:Python 版本不一致、pgvector 扩展装不上、各自本机的端口冲突;测试环境和开发环境配置也对不上。 ## 约束 - 开发者使用 macOS、Windows(WSL2)、Linux 都有。 - 模型 API Key 不能出现在仓库里。 - 开发时改代码要能快速生效,不想每次都重新构建镜像。 - 同一套编排文件,尽量也能用于单机测试环境。 ## 方案设计 ```mermaid flowchart LR Dev[浏览器 / 前端 dev server] -->|:8080| GW[Nginx 网关] GW -->|/api| API[Go API] GW -->|/ai| AI[Python AI 服务] API --> DB[(PostgreSQL + pgvector)] AI --> DB API --> R[(Redis)] AI --> R ``` 1. **一个 `compose.yaml` 描述全部服务**,服务之间用服务名通信;只有网关映射端口到宿主机。 2. **数据库用带 pgvector 的镜像**,初始化 SQL 放在 `./db/init/` 挂载到 `/docker-entrypoint-initdb.d/`(只在数据卷为空的首次启动时执行)。 3. **健康检查 + 依赖条件**:数据库健康后才启动 API 和 AI 服务。 4. **配置分层**:`compose.yaml` 写公共配置;`compose.override.yaml`(开发时自动合并)挂载源码目录、开启热重载;密钥放在不提交的 `.env`,仓库里提供 `.env.example`。 5. **Makefile 封装常用命令**:`make up`、`make logs`、`make psql`、`make reset-db`。 ## 关键代码 ```yaml # compose.yaml(节选) services: gateway: image: nginx:1.27-alpine ports: ["8080:80"] volumes: - ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro depends_on: [api, ai] api: build: ./api environment: DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app?sslmode=disable REDIS_URL: redis://redis:6379/0 depends_on: db: { condition: service_healthy } ai: build: ./ai environment: DATABASE_URL: postgresql://app:${DB_PASSWORD}@db:5432/app LLM_API_KEY: ${LLM_API_KEY} # 来自不提交的 .env depends_on: db: { condition: service_healthy } db: image: pgvector/pgvector:pg17 environment: POSTGRES_USER: app POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: app volumes: - pgdata:/var/lib/postgresql/data - ./db/init:/docker-entrypoint-initdb.d:ro # 首次启动执行 CREATE EXTENSION vector 等 healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d app"] interval: 5s retries: 10 redis: image: redis:7-alpine volumes: pgdata: ``` ```yaml # compose.override.yaml:docker compose up 时自动合并,只用于本地开发 services: ai: volumes: - ./ai/app:/app/app # 挂载源码 command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] ``` 单个 Go 服务的完整 Dockerfile 和 compose 示例见 [examples/go-api-compose](../examples/go-api-compose/)。镜像标签仅作示例,以各镜像的官方页面为准。 ## 取舍 | 方案 | 优点 | 缺点 | | --- | --- | --- | | **Compose 全部容器化(本方案)** | 环境一致、上手快 | 在 macOS / Windows 上挂载源码的文件性能较差;镜像占磁盘 | | 只把依赖(数据库、Redis)放容器,应用本机跑 | 调试方便、热更新快 | 应用的语言版本仍需各自安装 | | 远程开发环境(云端开发机 / Dev Container) | 环境完全统一 | 需要网络和额外成本 | 实际团队里常见做法是两者结合:依赖一律容器化;应用代码可以选择容器内运行或本机运行。 ## 易错点 - 修改了 `db/init` 里的 SQL 却发现没执行:初始化脚本只在数据卷为空时执行,需要 `docker compose down -v` 重建(会清空数据)或写迁移脚本。 - `.env` 被提交到仓库,泄露模型 API Key。加入 `.gitignore`,提交前扫描(仓库里的 `tools/check_sensitive.py`)。 - 所有服务都映射端口,本机端口冲突;只映射网关。 - 开发环境的 `--reload`、源码挂载被带到测试环境:override 文件只用于开发,测试环境显式指定 `-f compose.yaml`。 - 容器内时区、字符集和生产不一致,导致时间相关的 bug 只在某个环境出现。统一使用 UTC 存储。 ## 面试怎么讲 > “项目是 Go 业务 API 加 Python AI 服务,依赖 PostgreSQL(pgvector)和 Redis。我用 Compose 把它们编排起来:服务之间用服务名通信,只有网关对外映射端口;数据库加了健康检查,应用等数据库就绪才启动;密钥放在不提交的 .env,开发时用 override 文件挂载源码热重载。新人 clone 后一条命令就能跑起来。” 追问准备:生产环境也用 Compose 吗(单机小规模可以,多机用 Kubernetes)?数据怎么备份?