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。

100%
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 变量。

📖 小节


📝 作业

  1. 基础题(难度⭐):克隆一个开源 Flask/Express 项目,编写 Dockerfile 构建镜像并运行。
  2. 进阶题(难度⭐⭐):为该项目编写多阶段 Dockerfile,对比单阶段和多阶段的镜像大小差异。
  3. 挑战题(难度⭐⭐⭐):用 dive 分析优化后的镜像,找到还可以进一步优化的层,然后修复并验证体积缩减效果。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏