Dockerfile 写作精讲 / 17 - Docker Compose 集成
17 - Docker Compose 集成:Compose Build、多服务构建与环境变量
17.1 Compose 中的 Build
Docker Compose 允许在 docker-compose.yml(或 compose.yml)中定义构建配置,实现一键构建和启动多服务应用。
基本语法
services:
web:
build: .
ports:
- "8080:80"
api:
build: ./api
ports:
- "3000:3000"
高级 Build 配置
services:
web:
build:
context: . # 构建上下文
dockerfile: Dockerfile # Dockerfile 路径
target: production # 构建目标阶段
args: # 构建参数
NODE_ENV: production
APP_VERSION: ${APP_VERSION:-1.0.0}
cache_from: # 缓存来源
- type=gha
cache_to: # 缓存导出
- type=gha,mode=max
platforms: # 目标平台
- linux/amd64
- linux/arm64
tags: # 镜像标签
- myapp:latest
- registry.example.com/myapp:latest
labels: # 元数据标签
com.example.version: "1.0"
ssh: # SSH 转发
- default
secrets: # Secret 挂载
- npmrc
no_cache: false # 是否禁用缓存
pull: true # 是否拉取最新基础镜像
Build 与 Image 的关系
services:
# 使用 build:从 Dockerfile 构建
web:
build: .
image: myapp:latest # 同时指定标签(构建后打标签)
# 使用 image:直接使用已有镜像
database:
image: postgres:16
17.2 多服务构建
典型的多服务架构
services:
# 前端
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
target: production
ports:
- "3000:3000"
depends_on:
api:
condition: service_healthy
# 后端 API
api:
build:
context: ./api
dockerfile: Dockerfile
target: production
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
# 数据库
db:
image: postgres:16
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# Redis
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
volumes:
pgdata:
共享 Dockerfile,不同 Target
services:
app:
build:
context: .
target: production
ports:
- "8080:8080"
worker:
build:
context: .
target: worker
command: ["python", "worker.py"]
depends_on:
- redis
scheduler:
build:
context: .
target: scheduler
command: ["python", "scheduler.py"]
test:
build:
context: .
target: tester
command: ["pytest", "tests/"]
profiles:
- testing
# 共享 Dockerfile
FROM python:3.12-slim AS base
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM base AS production
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8080"]
FROM base AS worker
CMD ["python", "worker.py"]
FROM base AS scheduler
CMD ["python", "scheduler.py"]
FROM base AS tester
COPY requirements-dev.txt .
RUN pip install --no-cache-dir -r requirements-dev.txt
CMD ["pytest"]
17.3 环境变量管理
Compose 中的环境变量
services:
app:
build: .
# 方式一:直接定义
environment:
NODE_ENV: production
LOG_LEVEL: info
DATABASE_URL: postgresql://user:pass@db:5432/mydb
# 方式二:从 .env 文件加载
env_file:
- .env
- .env.production
# 方式三:使用变量引用
environment:
- API_KEY=${API_KEY}
- DB_HOST=${DB_HOST:-localhost} # 带默认值
.env 文件层级
项目目录/
├── .env # Compose 自动读取(设置 COMPOSE_PROJECT_NAME 等)
├── .env.local # 本地覆盖(不应提交到 Git)
├── .env.development # 开发环境
├── .env.production # 生产环境
├── docker-compose.yml
└── ...
# .env
COMPOSE_PROJECT_NAME=myapp
APP_VERSION=1.0.0
# .env.production
DB_HOST=prod-db.example.com
DB_PASSWORD=supersecret
LOG_LEVEL=warning
Build Args 与环境变量的传递
services:
app:
build:
context: .
args:
APP_VERSION: ${APP_VERSION:-1.0.0}
NODE_ENV: ${NODE_ENV:-production}
API_URL: ${API_URL:-http://localhost:3000}
environment:
# 运行时环境变量
NODE_ENV: ${NODE_ENV:-production}
LOG_LEVEL: ${LOG_LEVEL:-info}
# Dockerfile
FROM node:20-alpine
# 接收构建参数
ARG APP_VERSION
ARG NODE_ENV
ARG API_URL
# 转为运行时环境变量
ENV APP_VERSION=${APP_VERSION}
ENV NODE_ENV=${NODE_ENV}
WORKDIR /app
COPY . .
# 构建时使用 API_URL
RUN VITE_API_URL=${API_URL} npm run build
CMD ["node", "server.js"]
17.4 Secrets 管理
Compose Secrets
services:
app:
build: .
secrets:
- db_password
- api_key
environment:
DB_PASSWORD_FILE: /run/secrets/db_password
API_KEY_FILE: /run/secrets/api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY . .
# 应用代码读取 secret 文件
# db_password = open('/run/secrets/db_password').read().strip()
CMD ["python", "app.py"]
使用外部 Secret Provider
services:
app:
build: .
secrets:
- db_password
secrets:
db_password:
environment: "DB_PASSWORD" # 从环境变量获取
17.5 网络配置
services:
frontend:
build: ./frontend
networks:
- frontend-net
ports:
- "3000:3000"
api:
build: ./api
networks:
- frontend-net
- backend-net
# 不暴露端口到主机
db:
image: postgres:16
networks:
- backend-net
# 不暴露端口到主机
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
internal: true # 不允许外部访问
17.6 Profiles
Profiles 允许按需启动服务:
services:
# 默认服务(无 profile 限制)
app:
build: .
ports:
- "8080:8080"
db:
image: postgres:16
redis:
image: redis:7-alpine
# 仅开发环境
adminer:
image: adminer
ports:
- "8081:8080"
profiles:
- dev
# 仅测试环境
test-runner:
build:
context: .
target: tester
profiles:
- test
# 监控服务
prometheus:
image: prom/prometheus
profiles:
- monitoring
# 启动默认服务
docker compose up -d
# 启动开发环境(包含 adminer)
docker compose --profile dev up -d
# 启动测试
docker compose --profile test run --rm test-runner
# 启动所有服务
docker compose --profile dev --profile monitoring up -d
17.7 开发与生产分离
docker-compose.override.yml
# docker-compose.yml(基础配置)
services:
app:
build: .
environment:
NODE_ENV: production
ports:
- "8080:8080"
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
# docker-compose.override.yml(开发覆盖,自动加载)
services:
app:
build:
target: development
environment:
NODE_ENV: development
DEBUG: "true"
volumes:
- ./src:/app/src # 源码挂载(热重载)
ports:
- "8080:8080"
- "9229:9229" # Node.js 调试端口
db:
ports:
- "5432:5432" # 开发时暴露数据库端口
# docker-compose.prod.yml(生产配置)
services:
app:
build:
target: production
restart: always
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# 开发(自动使用 override)
docker compose up
# 生产
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
17.8 依赖管理
services:
app:
build: .
depends_on:
db:
condition: service_healthy # 等待健康检查通过
redis:
condition: service_started # 仅等待启动
migration:
condition: service_completed_successfully # 等待完成
migration:
build: .
command: ["python", "manage.py", "migrate"]
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 10
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
依赖条件表
| 条件 | 说明 |
|---|---|
service_started | 服务已启动(默认) |
service_healthy | 健康检查通过 |
service_completed_successfully | 服务成功退出(exit code 0) |
17.9 常见错误与排查
| 错误 | 原因 | 解决方案 |
|---|---|---|
build context not found | context 路径错误 | 检查路径(相对于 compose 文件) |
env variable not set | .env 文件缺失或变量未定义 | 添加默认值 ${VAR:-default} |
| 服务启动顺序错误 | 未配置 healthcheck 和 depends_on | 使用 condition: service_healthy |
| 端口冲突 | 主机端口被占用 | 更换端口或使用随机端口 |
| Volume 权限问题 | 主机目录权限不匹配 | entrypoint 脚本修复权限 |
17.10 扩展阅读
上一章:16 - 测试与验证 下一章:18 - 生产最佳实践 — CI/CD 集成、安全基线与维护策略。