一、为什么本地开发需要 Compose
一个真实 Web 项目往往不止一个进程:API 服务、数据库、缓存、反向代理、消息队列和监控。手工执行多个 docker run 时,参数很快会失控:忘记挂载数据卷会丢库,忘记加入网络会无法解析服务名,环境变量不一致又会导致“在我机器上正常”。
Compose 将这些参数声明在 YAML 中,提供:
- 一条命令启动和停止整个应用;
- 服务名自动提供 DNS;
- 网络、卷和环境变量可版本化;
- 新成员不需要逐项安装数据库;
- 开发、测试和生产可通过覆盖文件复用配置。
Compose v2 是 Go 实现并集成到 Docker CLI 中,命令格式为 docker compose;旧版 Python Compose 使用 docker-compose,两者配置大部分兼容,但应优先使用 v2。
二、实战 1:FastAPI、PostgreSQL、Redis
目录结构:
1 2 3 4 5 6 7
| demo/ ├── compose.yaml ├── .env └── app/ ├── Dockerfile ├── requirements.txt └── main.py
|
compose.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| services: api: build: ./app environment: DATABASE_URL: postgresql://app:app@db:5432/app REDIS_URL: redis://redis:6379/0 depends_on: db: condition: service_healthy redis: condition: service_healthy networks: [backend] ports: - "8000:8000"
db: image: postgres:16-alpine environment: POSTGRES_USER: app POSTGRES_PASSWORD: app POSTGRES_DB: app volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U app -d app"] interval: 5s timeout: 3s retries: 10 networks: [backend]
redis: image: redis:7-alpine command: ["redis-server", "--appendonly", "yes"] volumes: - redisdata:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 10 networks: [backend]
volumes: pgdata: redisdata:
networks: backend: driver: bridge
|
应用文件:
1 2 3 4 5 6
| FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY main.py . CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
1 2 3
| fastapi==0.115.6 uvicorn[standard]==0.34.0 redis==5.2.1
|
1 2 3 4 5 6 7 8 9 10 11
| import os from fastapi import FastAPI from redis import Redis
app = FastAPI() redis = Redis.from_url(os.environ["REDIS_URL"], decode_responses=True)
@app.get("/") def index(): redis.incr("requests") return {"service": "api", "requests": redis.get("requests")}
|
启动:
1 2 3
| docker compose up -d --build docker compose ps curl http://localhost:8000/
|
depends_on 只保证启动顺序时并不足够。使用 condition: service_healthy 后,API 会等待数据库和 Redis 健康检查通过。应用本身仍应实现重试,因为健康检查通过后服务也可能在运行期间短暂重启。
三、Nginx 反向代理
生产或接近生产的开发环境中,可以增加 Nginx:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| services: nginx: image: nginx:1.27-alpine ports: - "80:80" volumes: - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - api networks: [frontend, backend]
networks: frontend: backend:
|
default.conf:
1 2 3 4 5 6 7 8
| server { listen 80; location / { proxy_pass http://api:8000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
|
Nginx 连接的是 api:8000,不是宿主机的 localhost:8000。在容器网络中,localhost 始终表示当前容器。
四、实战 2:WordPress 经典组合
WordPress 需要应用、MySQL、反向代理和可选管理工具:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| services: wordpress: image: wordpress:php8.3-apache environment: WORDPRESS_DB_HOST: mysql:3306 WORDPRESS_DB_USER: wp WORDPRESS_DB_PASSWORD: change-me WORDPRESS_DB_NAME: wordpress depends_on: mysql: condition: service_healthy volumes: - wordpress:/var/www/html networks: [lamp]
mysql: image: mysql:8.4 environment: MYSQL_DATABASE: wordpress MYSQL_USER: wp MYSQL_PASSWORD: change-me MYSQL_ROOT_PASSWORD: root-change-me volumes: - mysql:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 5s timeout: 5s retries: 20 networks: [lamp]
phpmyadmin: image: phpmyadmin:5 environment: PMA_HOST: mysql ports: - "8081:80" depends_on: - mysql networks: [lamp]
volumes: wordpress: mysql:
networks: lamp:
|
数据库密码应通过 .env 或密钥管理系统提供,不要把生产密码提交到仓库。
五、实战 3:ELK 日志收集
Elasticsearch、Logstash 和 Kibana 资源消耗较大。开发机应限制 JVM 堆:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3 environment: discovery.type: single-node xpack.security.enabled: "false" ES_JAVA_OPTS: "-Xms512m -Xmx512m" ports: - "9200:9200" volumes: - esdata:/usr/share/elasticsearch/data
kibana: image: docker.elastic.co/kibana/kibana:8.15.3 environment: ELASTICSEARCH_HOSTS: http://elasticsearch:9200 ports: - "5601:5601" depends_on: - elasticsearch
volumes: esdata:
|
日志平台不应与核心数据库共用无上限资源。Compose 可以通过 mem_limit 做开发环境保护,生产则应使用更完善的资源调度方案。
六、网络、卷与多环境配置
自定义 bridge 网络支持服务名解析和网络隔离。可以让 Nginx 同时加入 frontend、backend,数据库只加入 backend,这样外部入口无法直接访问数据库。
named volume 由 Docker 管理,适合数据库;bind mount 直接映射宿主机目录,适合源代码热加载和配置文件。数据库生产环境通常优先 named volume 或独立存储。
.env:
1 2 3
| POSTGRES_USER=app POSTGRES_PASSWORD=local-password POSTGRES_DB=app
|
compose.yaml 中可以写 ${POSTGRES_PASSWORD}。注意 .env 是变量插值来源,不等同于自动注入所有容器。需要注入时,明确写 env_file 或 environment。
开发覆盖文件:
1 2 3 4 5
| services: api: volumes: - ./app:/app command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
启动生产配置:
1
| docker compose -f compose.yaml -f compose.prod.yaml up -d --build
|
七、常用调试命令与踩坑
1 2 3 4 5 6
| docker compose logs -f api docker compose exec db psql -U app -d app docker compose top docker compose config docker compose down docker compose down -v
|
down -v 会删除 named volume 中的数据,不能在生产环境随意执行。容器内时区默认可能是 UTC,业务应统一使用 UTC 存储、在展示层转换。volume 权限问题通常来自宿主机 UID 与容器用户不同,应使用非 root 用户并调整目录属主。
八、小结
Compose 适合单机开发、测试、演示和中小规模部署。它不是 Kubernetes 的简单替代品,但能把多容器系统的依赖、网络和数据生命周期清楚地表达出来。团队应提交 Compose 文件、示例环境变量和健康检查,同时把密码、证书和生产密钥交给专门的 Secret 系统管理。