Docker: Phase 2综合实践
最后更新:2026-08-26
把 Phase 2 的知识串起来——从源码到生产级镜像,完整容器化一个 Web 应用。
1. 你将学到
- 多阶段构建的完整实战
- 环境配置管理策略
- 日志持久化方案
- 健康检查与自愈配置
- 镜像标签与版本管理
2. 一个接手遗留项目的故事
(1) 痛点:在 Docker 里跑不起来的遗留项目
Charlie 接手了一个"在 Docker 里跑不起来"的遗留 Python 项目——Dockerfile 1.2 GB、每次构建 10 分钟、没有健康检查、日志写在容器里(重启就丢)、root 用户运行、环境变量硬编码。部署一次要 30 分钟,还不稳定。
(2) 全面容器化的解法
Alice 帮助他:分析依赖树、编写多阶段 Dockerfile、配置日志持久化、添加健康检查、非 root 用户运行。最终项目在 3 分钟内完成构建部署。
DOCKERFILE
# Multi-stage production Dockerfile
FROM python:3.12-slim AS builder
# ... build optimized venv
FROM python:3.12-slim
# ... copy venv, non-root, healthcheck
(3) 收益:从 30 分钟到 3 分钟
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 镜像体积 | 1.2 GB | 150 MB |
| 构建时间 | 10 分钟 | 3 分钟 |
| 部署时间 | 30 分钟 | 3 分钟 |
| 安全评分 | 45 | 85 |
3. 项目架构设计
(1) 应用架构
本课容器化一个 Todo List Flask 应用:Flask + PostgreSQL + Nginx。
graph TB
USER["Browser"] --> NGX["Nginx<br/>Reverse Proxy + Static"]
NGX -->|"proxy_pass :5000"| APP["Flask App<br/>Todo List API + UI"]
APP -->|"psycopg2 :5432"| DB["PostgreSQL<br/>Todo Database"]
DB --> VOL["Named Volume<br/>pg-data"]
APP --> LOGS["Named Volume<br/>app-logs"]
(2) 容器化策略
| 组件 | 基镜像 | 策略 |
|---|---|---|
| Flask 应用 | python:3.12-slim |
多阶段构建 + 非 root + HEALTHCHECK |
| PostgreSQL | postgres:15-alpine |
官方镜像 + Named Volume |
| Nginx | nginx:1.25-alpine |
官方镜像 + 配置文件 Bind Mount |
4. Flask 应用 Dockerfile
(1) 项目结构
TEXT
📖 仅展示
todo-app/
├── app.py # Flask application
├── requirements.txt # Python dependencies
├── templates/ # HTML templates
│ └── index.html
├── Dockerfile # Multi-stage production build
├── .dockerignore # Exclude unnecessary files
├── nginx.conf # Nginx reverse proxy config
└── docker-run.sh # One-click deployment script
▶ 示例:编写多阶段 Dockerfile(难度⭐⭐⭐)
DOCKERFILE
# ============================================
# Multi-stage Dockerfile for Flask Todo App
# Stage 1: Build virtual environment
# Stage 2: Production runtime
# ============================================
# ---------- Stage 1: Builder ----------
FROM python:3.12-slim AS builder
WORKDIR /build
# Cache: install dependencies first
COPY requirements.txt .
RUN python -m venv /opt/venv && \
/opt/venv/bin/pip install --no-cache-dir -r requirements.txt
# ---------- Stage 2: Production ----------
FROM python:3.12-slim
# Install curl for healthcheck
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r appgroup && \
useradd -r -g appgroup -u 1000 -m -d /home/appuser appuser
WORKDIR /app
# Copy virtual environment from builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy application code
COPY --chown=appuser:appgroup . .
# Create log directory
RUN mkdir -p /app/logs && chown appuser:appgroup /app/logs
# Switch to non-root user
USER appuser
# Environment variables (defaults, override at runtime)
ENV FLASK_ENV=production \
LOG_LEVEL=info \
APP_PORT=5000
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD curl -f http://localhost:5000/health || exit 1
# Start the application
CMD ["python", "app.py"]
(2) .dockerignore
TEXT
📖 仅展示
# .dockerignore
__pycache__
*.pyc
*.pyo
.git
.env
venv
.venv
*.md
Dockerfile
docker-compose*.yml
5. 环境配置管理
(1) 环境变量层级
| 层级 | 方式 | 优先级 | 适用场景 |
|---|---|---|---|
| Dockerfile ENV | 默认值 | 最低(1) | 通用默认配置 |
| docker run -e | 运行时覆盖 | 中(2) | 部署时配置 |
| .env 文件 | --env-file |
中(3) | 多变量管理 |
| Docker Secrets | --secret |
最高 | 敏感信息 |
▶ 示例:配置环境变量(难度⭐⭐)
BASH
# Run with custom environment variables
docker run -d \
--name todo-app \
-e FLASK_ENV=production \
-e DATABASE_URL="postgresql://appuser:apppass@todo-db:5432/tododb" \
-e LOG_LEVEL=warning \
-p 5000:5000 \
todo-app:1.0
▶ 示例:使用 .env 文件(难度⭐⭐)
BASH
# Create .env file
cat > .env << 'EOF'
FLASK_ENV=production
DATABASE_URL=postgresql://appuser:apppass@todo-db:5432/tododb
LOG_LEVEL=warning
APP_PORT=5000
EOF
# Run with --env-file
docker run -d \
--name todo-app \
--env-file .env \
-p 5000:5000 \
todo-app:1.0
6. 日志持久化
▶ 示例:配置日志持久化(难度⭐⭐)
BASH
# Mount a named volume for application logs
docker run -d \
--name todo-app \
-v app-logs:/app/logs \
-e LOG_LEVEL=info \
todo-app:1.0
(1) 日志策略对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 容器内写日志 | 简单 | 容器删除后丢失 |
| Volume 持久化 | 容器删除后保留 | 需手动管理日志轮转 |
| stdout + 日志驱动 | Docker 原生管理 | 配置较复杂 |
| 集中式日志(ELK/Loki) | 全栈可搜索 | 需额外基础设施 |
7. 镜像标签与版本管理
(1) 标签策略
| 策略 | 标签格式 | 示例 | 适用场景 |
|---|---|---|---|
| 语义版本 | v主.次.修订 |
v2.1.0 |
生产发布 |
| Git SHA | sha-<commit> |
sha-a1b2c3d |
CI/CD 追踪 |
| latest | latest |
latest |
开发/快速体验 |
| 分支名 | <branch> |
main, dev |
开发环境 |
▶ 示例:多标签推送(难度⭐⭐)
BASH
# Build with multiple tags
docker build -t todo-app:v1.0.0 -t todo-app:latest .
# Push all tags
docker push localhost:5000/todo-app:v1.0.0
docker push localhost:5000/todo-app:latest
8. 部署全栈
▶ 示例:一键部署 Flask + PostgreSQL + Nginx(难度⭐⭐⭐)
BASH
# ============================================
# One-click deployment script
# ============================================
# 1. Create network and volumes
docker network create todo-net
docker volume create pg-data
docker volume create app-logs
# 2. Start PostgreSQL
docker run -d \
--name todo-db \
--network todo-net \
-e POSTGRES_USER=appuser \
-e POSTGRES_PASSWORD=apppass \
-e POSTGRES_DB=tododb \
-v pg-data:/var/lib/mysql \
--restart=unless-stopped \
postgres:15-alpine
# 3. Wait for PostgreSQL to initialize
sleep 15
# 4. Build and start Flask app
docker build -t todo-app:1.0 ./todo-app
docker run -d \
--name todo-app \
--network todo-net \
-e DATABASE_URL="postgresql://appuser:apppass@todo-db:5432/tododb" \
-v app-logs:/app/logs \
--restart=unless-stopped \
todo-app:1.0
# 5. Start Nginx reverse proxy
docker run -d \
--name todo-nginx \
--network todo-net \
-p 80:80 \
-v $(pwd)/todo-app/nginx.conf:/etc/nginx/conf.d/default.conf \
--restart=unless-stopped \
nginx:1.25-alpine
# 6. Verify
docker ps
curl http://localhost/health
9. 验证与优化效果
(1) 用 dive 验证优化
BASH
# Analyze the multi-stage image
dive todo-app:1.0
# Expected: efficiency score > 90%, no wasted space
(2) 镜像大小对比
| version | Dockerfile | 镜像大小 | 构建时间 |
|---|---|---|---|
| V1 单阶段 | FROM python:3.12 + COPY . . + RUN pip install |
1.2 GB | 5 分钟 |
| V2 优化基镜像 | FROM python:3.12-slim + 清理缓存 |
350 MB | 3 分钟 |
| V3 多阶段 | builder + slim + 非 root + HEALTHCHECK | 150 MB | 3 分钟(缓存命中时 30 秒) |
10. 完整示例:容器化 Todo List 应用
BASH
# ============================================
# Complete walkthrough: Containerize a Flask app
# Covers: Dockerfile, build, multi-container deploy
# ============================================
# 1. Build the optimized image
docker build -t todo-app:v1.0.0 -t todo-app:latest ./todo-app
# 2. Verify the image
docker images todo-app
docker history todo-app:v1.0.0
# 3. Create infrastructure
docker network create todo-net
docker volume create pg-data
docker volume create app-logs
# 4. Start PostgreSQL with persistent storage
docker run -d \
--name todo-db \
--network todo-net \
-e POSTGRES_USER=appuser \
-e POSTGRES_PASSWORD=apppass \
-e POSTGRES_DB=tododb \
-v pg-data:/var/lib/postgresql/data \
--restart=unless-stopped \
postgres:15-alpine
# 5. Start Flask application
docker run -d \
--name todo-app \
--network todo-net \
-e DATABASE_URL="postgresql://appuser:apppass@todo-db:5432/tododb" \
-v app-logs:/app/logs \
--restart=unless-stopped \
todo-app:v1.0.0
# 6. Start Nginx reverse proxy
docker run -d \
--name todo-nginx \
--network todo-net \
-p 8080:80 \
-v $(pwd)/todo-app/nginx.conf:/etc/nginx/conf.d/default.conf \
--restart=unless-stopped \
nginx:1.25-alpine
# 7. Verify full stack
echo "=== Containers ===" && docker ps
echo "=== Health Check ===" && docker inspect todo-app --format='{{.State.Health.Status}}'
echo "=== App Response ===" && curl -s http://localhost:8080/health
# 8. Push to private registry (optional)
docker tag todo-app:v1.0.0 localhost:5000/todo-app:v1.0.0
docker push localhost:5000/todo-app:v1.0.0
# 9. Clean up (optional)
# docker stop todo-nginx todo-app todo-db
# docker rm todo-nginx todo-app todo-db
❓ 常见问题
Q 一个容器跑一个进程吗?
A 最佳实践是"一个容器一个进程"——Flask 应用一个容器,Nginx 一个容器,PostgreSQL 一个容器。好处:独立伸缩、独立更新、故障隔离。反例:一个容器跑 Nginx+Flask+PostgreSQL,一个挂全挂,无法独立扩缩。
Q 日志文件应该写在容器里还是挂载卷?
A 两种方式结合:① 应用同时写 stdout(docker logs 可查看)和文件(挂载卷持久化);② 生产环境推荐 stdout + 集中式日志收集(ELK/Loki),不再写文件。本课的 Volume 方案是中间过渡方案。
Q 怎么在构建时区分开发/生产环境?
A 三种方式:① ARG + 条件 RUN(不推荐,Dockerfile 变复杂);② 多个 Dockerfile(Dockerfile.dev / Dockerfile.prod);③ 多阶段构建 + --target 选择(推荐)。本课用 ENV 设置默认值 + docker run -e 覆盖。
Q 镜像版本号怎么管理?
A 语义版本(SemVer):主版本.次版本.修订号(如 v2.1.0)。CI/CD 中同时打 Git SHA 标签(如 sha-a1b2c3d)便于追溯。latest 标签始终指向最新稳定版,但生产环境不用 latest,用精确版本号。
Q 容器时区怎么设置?
A 两种方式:①
docker run -e TZ=Asia/Shanghai(需要基镜像安装 tzdata);② -v /etc/localtime:/etc/localtime:ro(直接用主机时区)。alpine 镜像需要 RUN apk add --no-cache tzdata 才支持 TZ 变量。📖 小节
- 多阶段构建:Builder 阶段创建 venv,Runner 阶段只复制 venv,镜像从 1.2 GB 降到 150 MB
- 环境变量分层管理:Dockerfile ENV(默认值)→ docker run -e(运行时覆盖)→ .env 文件(多变量)
- 日志持久化:Named Volume 挂载 /app/logs,容器重启/重建后日志保留
- 健康检查:curl /health 端点,Docker 自动检测 + restart 策略实现自愈
- 镜像版本管理:语义版本 + Git SHA + latest 多标签推送
- 非 root 运行 + HEALTHCHECK + 日志持久化 = 生产级 Dockerfile 的三大必备
📝 作业
- 基础题(难度⭐):克隆一个开源 Flask/Express 项目,编写 Dockerfile 构建镜像并运行。
- 进阶题(难度⭐⭐):为该项目编写多阶段 Dockerfile,对比单阶段和多阶段的镜像大小差异。
- 挑战题(难度⭐⭐⭐):用 dive 分析优化后的镜像,找到还可以进一步优化的层,然后修复并验证体积缩减效果。