第 15 章 · 最佳实践:项目结构、生产规范与 CI/CD 集成
第 15 章 · 最佳实践
15.1 项目结构规范
推荐目录结构
myproject/
├── compose.yaml # 基础配置
├── compose.override.yaml # 开发覆盖(自动加载)
├── compose.prod.yaml # 生产覆盖
├── compose.test.yaml # 测试覆盖
├── compose.monitoring.yaml # 监控组件
├── .env # 默认环境变量
├── .env.example # 变量模板(提交到 Git)
├── .env.production # 生产变量(不提交)
├── .gitignore
├── Makefile # 常用命令封装
├── README.md
│
├── app/ # 应用代码
│ ├── Dockerfile
│ ├── Dockerfile.prod # 生产专用 Dockerfile
│ ├── .dockerignore
│ ├── src/
│ └── tests/
│
├── nginx/ # Nginx 配置
│ ├── nginx.conf
│ └── conf.d/
│
├── monitoring/ # 监控配置
│ ├── prometheus.yml
│ ├── alertmanager.yml
│ ├── grafana/
│ │ └── provisioning/
│ └── rules/
│
├── secrets/ # 密钥文件(不提交)
│ ├── .gitkeep
│ ├── db_password.txt
│ └── tls/
│
├── scripts/ # 辅助脚本
│ ├── backup.sh
│ ├── deploy.sh
│ └── health-check.sh
│
└── docs/ # 文档
├── architecture.md
└── runbook.md
.gitignore
# 环境变量(敏感)
.env
.env.*
!.env.example
# 密钥
secrets/
# Docker 构建产物
.docker/
# 数据卷挂载目录(如果在项目内)
data/
logs/
# IDE
.vscode/
.idea/
# 系统文件
.DS_Store
Thumbs.db
Makefile 封装
# Makefile
.PHONY: dev up down build test prod logs clean backup
# ===== 开发环境 =====
dev:
docker compose up
dev-d:
docker compose up -d
down:
docker compose down
# ===== 构建 =====
build:
docker compose build
build-nc:
docker compose build --no-cache
# ===== 生产部署 =====
prod:
docker compose -f compose.yaml -f compose.prod.yaml up -d --build
prod-down:
docker compose -f compose.yaml -f compose.prod.yaml down
# ===== 测试 =====
test:
docker compose -f compose.yaml -f compose.test.yaml up \
--abort-on-container-exit --exit-code-from test
docker compose -f compose.yaml -f compose.test.yaml down -v
# ===== 日志 =====
logs:
docker compose logs -f
logs-web:
docker compose logs -f web
# ===== 清理 =====
clean:
docker compose down -v --rmi local --remove-orphans
clean-all:
docker compose down -v --rmi all --remove-orphans
docker system prune -f
# ===== 备份 =====
backup-db:
docker compose exec db pg_dump -U postgres mydb > \
backups/db_$$(date +%Y%m%d_%H%M%S).sql
# ===== 数据库 =====
db-shell:
docker compose exec db psql -U postgres mydb
db-migrate:
docker compose run --rm app python manage.py migrate
# ===== 健康检查 =====
health:
@echo "=== Service Health Status ==="
@docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}"
# ===== 帮助 =====
help:
@echo "Available commands:"
@echo " make dev - Start development environment"
@echo " make dev-d - Start development environment (detached)"
@echo " make down - Stop all services"
@echo " make build - Build images"
@echo " make prod - Deploy production"
@echo " make test - Run tests"
@echo " make logs - View logs"
@echo " make clean - Clean up everything"
@echo " make health - Check service health"
15.2 compose.yaml 编写规范
规范一:文件命名
# ✅ 推荐:使用 compose.yaml(官方推荐)
# ✅ 可接受:docker-compose.yml
# ❌ 不推荐:docker-compose.yaml(较长)
# ❌ 不推荐:compose.yml(较少见但合法)
规范二:服务排序
# 按照依赖关系从底到上排列
services:
# 1. 反向代理(最上层)
nginx:
# 2. 应用服务
web:
api:
# 3. 后台任务
worker:
scheduler:
# 4. 数据存储
db:
redis:
# 5. 辅助工具
adminer:
规范三:镜像标签
services:
# ❌ 不要用 latest
web:
image: myapp:latest
# ✅ 使用精确版本
web:
image: myapp:1.2.3
# ✅ 使用 digest(最安全)
web:
image: myapp:1.2.3@sha256:abc123...
# ✅ 官方镜像使用精确版本
db:
image: postgres:16.2-alpine
规范四:健康检查
# 所有长期运行的服务都应配置健康检查
services:
web:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
规范五:重启策略
# 生产环境所有服务设置 restart
services:
web:
restart: unless-stopped
db:
restart: unless-stopped
redis:
restart: unless-stopped
# 一次性任务不重启
migration:
restart: "no"
规范六:资源限制
services:
web:
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 256M
规范七:日志配置
services:
web:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
规范八:网络安全
services:
db:
# ❌ 不要暴露数据库端口到公网
ports:
- "0.0.0.0:5432:5432"
# ✅ 仅本地访问
ports:
- "127.0.0.1:5432:5432"
# ✅ 最佳:完全不暴露,仅通过 Docker 网络访问
# 不配置 ports
15.3 Dockerfile 最佳实践
通用规范
# 1. 使用精确的基础镜像版本
FROM python:3.12.3-slim-bookworm
# 2. 设置元数据
LABEL maintainer="[email protected]"
LABEL version="1.0.0"
LABEL description="My Application"
# 3. 设置工作目录
WORKDIR /app
# 4. 系统依赖安装(合并 RUN 减少层数)
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libpq-dev \
curl \
&& rm -rf /var/lib/apt/lists/*
# 5. 先复制依赖文件(利用缓存层)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 6. 再复制应用代码
COPY src/ ./src/
# 7. 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser
# 8. 暴露端口(文档性质)
EXPOSE 8000
# 9. 使用 exec 格式的 CMD
CMD ["gunicorn", "src.main:app"]
多阶段构建
# 阶段一:构建
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 阶段二:运行
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package.json .
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]
安全规范
# 不要安装不需要的工具
# ❌ apt-get install vim wget telnet
# 使用非 root 用户
USER 1000:1000
# 不要在镜像中存储密钥
# ❌ ENV DB_PASSWORD=secret
# 使用 Docker Secrets
# RUN --mount=type=secret,id=db_pass cat /run/secrets/db_pass
15.4 安全最佳实践
镜像安全
| 实践 | 说明 |
|---|
| 使用官方镜像 | 比第三方镜像更可信、更安全 |
| 使用精简镜像 | alpine、slim、distroless 比 full 更小更安全 |
| 固定版本标签 | 避免 latest,使用精确版本或 digest |
| 定期更新 | 修补已知漏洞 |
| 扫描镜像 | 使用 docker scout 或 Trivy 扫描漏洞 |
# 使用 Docker Scout 扫描漏洞
docker scout cves myapp:latest
# 使用 Trivy
trivy image myapp:latest
运行时安全
services:
app:
# 只读文件系统
read_only: true
tmpfs:
- /tmp
- /var/run
# 禁用特权
privileged: false
# 能力限制
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # 仅在需要时添加
# 安全选项
security_opt:
- no-new-privileges:true
网络安全
services:
db:
# 不暴露端口
networks:
- backend
nginx:
# 仅 nginx 暴露到外部
ports:
- "80:80"
- "443:443"
networks:
- frontend
- backend
networks:
frontend:
backend:
internal: true # 内部网络,不允许外部访问
15.5 CI/CD 集成
GitHub Actions
# .github/workflows/ci.yml
name: CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests with Compose
run: |
docker compose -f compose.yaml -f compose.test.yaml up \
--build --abort-on-container-exit --exit-code-from test
- name: Cleanup
if: always
run: |
docker compose -f compose.yaml -f compose.test.yaml down -v
build:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: ./app
push: true
tags: |
ghcr.io/${{ github.repository }}/app:latest
ghcr.io/${{ github.repository }}/app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to server
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_KEY }}
script: |
cd /opt/myapp
docker compose -f compose.yaml -f compose.prod.yaml pull
docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose -f compose.yaml -f compose.prod.yaml exec -T app python manage.py migrate
GitLab CI
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
DOCKER_TLS_CERTDIR: "/certs"
test:
stage: test
services:
- docker:dind
script:
- docker compose -f compose.yaml -f compose.test.yaml up --build --abort-on-container-exit --exit-code-from test
after_script:
- docker compose -f compose.yaml -f compose.test.yaml down -v
build:
stage: build
only:
- main
script:
- docker build -t $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA ./app
- docker push $CI_REGISTRY_IMAGE/app:$CI_COMMIT_SHA
deploy:
stage: deploy
only:
- main
environment: production
script:
- ssh deploy@server "cd /opt/myapp && docker compose pull && docker compose up -d"
部署脚本
#!/bin/bash
# scripts/deploy.sh
set -euo pipefail
PROJECT="myapp"
COMPOSE_FILES="-f compose.yaml -f compose.prod.yaml"
ENV_FILE="--env-file .env.production"
echo "=== Deploying $PROJECT ==="
# 1. 备份
echo "→ Backing up database..."
docker compose $COMPOSE_FILES exec -T db pg_dump -U postgres mydb > \
"backups/pre_deploy_$(date +%Y%m%d_%H%M%S).sql" || true
# 2. 拉取新镜像
echo "→ Pulling images..."
docker compose $COMPOSE_FILES $ENV_FILE pull
# 3. 执行迁移
echo "→ Running migrations..."
docker compose $COMPOSE_FILES $ENV_FILE run --rm app python manage.py migrate
# 4. 滚动更新
echo "→ Starting services..."
docker compose $COMPOSE_FILES $ENV_FILE up -d --remove-orphans
# 5. 健康检查
echo "→ Waiting for services to be healthy..."
sleep 10
docker compose $COMPOSE_FILES ps
# 6. 清理旧镜像
echo "→ Cleaning up..."
docker image prune -f
echo "=== Deploy complete ==="
15.6 性能优化
镜像优化
| 技巧 | 效果 |
|---|
| 使用 Alpine 基础镜像 | 体积减少 50-80% |
| 多阶段构建 | 构建工具不进入运行镜像 |
| 合并 RUN 指令 | 减少镜像层数 |
| 清理缓存 | rm -rf /var/lib/apt/lists/* |
.dockerignore | 减小构建上下文 |
| BuildKit 缓存 | 加速增量构建 |
Compose 性能
# 并行构建
docker compose build --parallel
# 并行拉取
docker compose pull --parallel
# 只重建变化的服务
docker compose up -d --build web # 只重建 web
# 使用 BuildKit(更快)
DOCKER_BUILDKIT=1 docker compose build
容器运行时性能
services:
app:
# 网络性能(仅 Linux)
network_mode: host # 去掉 NAT 层,提升网络性能
# 存储性能
volumes:
- type: volume # 命名卷比 bind mount 性能好
source: app-cache
target: /app/cache
15.7 备份与恢复策略
自动备份脚本
#!/bin/bash
# scripts/backup.sh
set -euo pipefail
BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
echo "=== Starting backup ==="
# 数据库备份
echo "→ Backing up PostgreSQL..."
docker compose exec -T db pg_dump -U postgres -Fc mydb > "$BACKUP_DIR/db.dump"
# 卷备份
echo "→ Backing up volumes..."
for VOL in $(docker volume ls --filter "label=com.docker.compose.project=$(basename $(pwd))" -q); do
echo " → $VOL"
docker run --rm -v "$VOL":/source:ro -v "$(pwd)/$BACKUP_DIR":/backup \
alpine tar czf "/backup/${VOL}.tar.gz" -C /source .
done
# 配置备份
echo "→ Backing up configuration..."
cp compose.yaml "$BACKUP_DIR/"
cp .env.production "$BACKUP_DIR/" 2>/dev/null || true
# 压缩
echo "→ Compressing..."
tar czf "backups/backup_$(date +%Y%m%d_%H%M%S).tar.gz" -C "$BACKUP_DIR" .
# 清理旧备份(保留最近 7 天)
find backups/ -name "backup_*.tar.gz" -mtime +7 -delete
echo "=== Backup complete: $BACKUP_DIR ==="
定期备份(cron)
# crontab -e
# 每天凌晨 3 点备份
0 3 * * * cd /opt/myapp && bash scripts/backup.sh >> logs/backup.log 2>&1
15.8 团队协作规范
文档要求
| 文档 | 内容 |
|---|
README.md | 项目简介、快速开始、常用命令 |
.env.example | 所有环境变量的模板和说明 |
docs/architecture.md | 架构设计、服务关系图 |
docs/runbook.md | 运维手册、常见问题处理 |
代码审查清单
## Docker Compose 审查清单
### 镜像
- [ ] 使用精确版本标签,非 `latest`
- [ ] 基础镜像来自可信来源
- [ ] 已扫描已知漏洞
### 安全
- [ ] 敏感信息使用 Secrets,非 environment
- [ ] 数据库不暴露公共端口
- [ ] 非 root 用户运行
- [ ] 最小权限原则
### 可靠性
- [ ] 配置了健康检查
- [ ] 配置了重启策略
- [ ] 配置了资源限制
- [ ] 依赖关系正确使用 `depends_on.condition`
### 可维护性
- [ ] 代码有 `.dockerignore`
- [ ] 日志配置了轮转
- [ ] 数据使用命名卷
- [ ] Makefile 包含常用命令
### CI/CD
- [ ] 测试使用独立的 compose 文件
- [ ] 构建使用多阶段构建
- [ ] 部署脚本包含回滚机制
15.9 从 Compose 迁移到 Kubernetes
迁移时机
| 信号 | 说明 |
|---|
| 需要多节点部署 | 单机无法满足容量需求 |
| 需要自动扩缩容 | 流量波动大,需要弹性 |
| 需要蓝绿/金丝雀部署 | 发布策略更复杂 |
| 团队规模增长 | 需要更完善的 RBAC 和命名空间隔离 |
迁移工具
# Kompose — 将 Compose 文件转换为 K8s 资源
kompose convert -f compose.yaml
# 生成的文件:
# web-deployment.yaml
# web-service.yaml
# db-deployment.yaml
# db-persistentvolumeclaim.yaml
Compose vs K8s 概念映射
| Compose | Kubernetes |
|---|
services.web | Deployment + Service |
image | container image |
ports | Service + Ingress |
volumes | PersistentVolumeClaim |
environment | ConfigMap / Secret |
depends_on | Init Container |
healthcheck | livenessProbe / readinessProbe |
restart | restartPolicy |
networks | NetworkPolicy |
deploy.replicas | replicas |
deploy.resources | resources (requests/limits) |
15.10 总结回顾
全书知识图谱
第 1 章 概念 ─── 什么是 Compose,为什么用
│
第 2 章 安装 ─── 怎么装,V2 vs V1
│
第 3 章 基础 ─── services / image / ports / volumes
│
├── 第 4 章 网络 ─── 默认网络 / 自定义 / 外部
├── 第 5 章 卷 ─── 命名卷 / 绑定 / tmpfs
├── 第 6 章 环境 ─── .env / environment / env_file
│
第 7 章 依赖 ─── depends_on / healthcheck / restart
第 8 章 构建 ─── Dockerfile / 多阶段 / 缓存
│
├── 第 9 章 多环境 ─── override / profiles / include
├── 第 10 章 安全 ─── secrets / configs
│
第 11 章 Swarm ─── deploy / replicas / 滚动更新
第 12 章 日志 ─── logging driver / Loki
第 13 章 监控 ─── cAdvisor / Prometheus / Grafana
│
第 14 章 排障 ─── 常见问题 / 调试技巧
第 15 章 实践 ─── 项目规范 / CI/CD / 生产化
关键经验总结
| # | 经验 |
|---|
| 1 | 永远不用 latest 标签 — 版本不可控 |
| 2 | 所有服务都要健康检查 — 否则 depends_on 毫无意义 |
| 3 | 日志必须配轮转 — 否则磁盘会满 |
| 4 | 敏感信息用 Secrets — 永远不要硬编码密码 |
| 5 | 数据库不要暴露端口 — 通过 Docker 网络内部访问 |
| 6 | 使用多阶段构建 — 镜像体积减少 80%+ |
| 7 | .env.example 提交到 Git — 方便团队了解配置项 |
| 8 | 使用 Makefile — 封装常用命令,降低团队门槛 |
| 9 | 开发用绑定挂载,数据用命名卷 — 各取所长 |
| 10 | 先 Compose,需要时再 K8s — 不要过度工程化 |
扩展阅读
官方资源
社区资源
相关工具
结语
Docker Compose 看似简单,但用好它需要理解网络、存储、安全、监控等多个维度的知识。希望这 15 章的系统讲解能帮助你在实际项目中游刃有余。
记住:最好的基础设施是看不见的基础设施。
祝你构建顺利!🚀
上一章:第 14 章 · 故障排查 ← | 回到 目录