FastAPI: Docker 部署 — 容器化与多阶段构建

最后更新:2026-08-26

Docker 就像集装箱——把应用和所有依赖打包成标准容器,不管在哪个码头(服务器)都能直接卸货运行,不再有"在我机器上能跑"的问题。

1. 你将学到


2. Alice 的真实故事

(1) 痛点:环境差异导致部署失败

Charlie 在本地配置好的 PriceTracker 部署到生产服务器时,Python 版本不一致、系统缺少 libpq 库、UV 未安装、PostgreSQL 连接配置不同——每次部署都要手动排查 2 小时。更糟的是,FastAPI、PostgreSQL、Redis、Celery 四个服务要分别启动和管理,启动顺序依赖复杂。

(2) Docker Compose 的解法

Dockerfile 定义应用容器,Docker Compose 编排所有服务及其依赖关系。一条 docker compose up 启动完整服务栈,环境一致性由镜像保证。

(3) 收益

部署从手动 2 小时变成一键 30 秒,本地开发环境与生产完全一致,Charlie 再也不用说"在我的服务器上能跑"。


3. 多阶段 Dockerfile

(1) 构建流程

100%
flowchart LR
    A[Builder Stage] -->|Copy installed deps| B[Runtime Stage]
    
    subgraph Builder
        A1[UV install deps] --> A2[Compile wheels]
    end
    
    subgraph Runtime
        B1[Python slim image] --> B2[Copy app code]
        B2 --> B3[Copy deps from builder]
        B3 --> B4[Run uvicorn]
    end

▶ 示例:多阶段 Dockerfile

DOCKERFILE
# === Stage 1: Builder ===
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder

WORKDIR /app

# Copy dependency files first (cache layer)
COPY pyproject.toml uv.lock ./

# Install dependencies into virtual environment
RUN uv sync --frozen --no-dev --no-install-project

# Copy application code
COPY app/ app/

# === Stage 2: Runtime ===
FROM python:3.12-slim-bookworm AS runtime

WORKDIR /app

# Install runtime system dependencies
RUN apt-get update && \
    apt-get install -y --no-install-recommends libpq5 && \
    rm -rf /var/lib/apt/lists/*

# Copy virtual environment from builder
COPY --from=builder /app/.venv /app/.venv

# Copy application code
COPY app/ app/

# Set environment variables
ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

# Run application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

输出:

TEXT 📖 仅展示
// 执行成功

(2) 镜像大小对比

方式 镜像大小 说明
单阶段(python:3.12) ~1.2GB 含构建工具、缓存
多阶段(python:3.12-slim) ~200MB 仅运行时依赖
Alpine(python:3.12-alpine) ~80MB 更小但有兼容性问题

4. Docker Compose 编排

(1) 完整栈架构

100%
flowchart TD
    Nginx[Nginx Reverse Proxy] --> API1[FastAPI Worker 1]
    Nginx --> API2[FastAPI Worker 2]
    API1 --> PG[(PostgreSQL)]
    API2 --> PG
    API1 --> Redis[(Redis)]
    API2 --> Redis
    API1 --> Broker[Redis Broker]
    API2 --> Broker
    Broker --> CW1[Celery Worker 1]
    Broker --> CW2[Celery Worker 2]
    CW1 --> PG
    CW2 --> PG
    CW1 --> Redis
    CW2 --> Redis
    Flower[Flower Monitor] --> Broker

▶ 示例:docker-compose.yml

YAML
version: "3.8"

services:
  api:
    build:
      context: .
      dockerfile: docker/Dockerfile
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql+asyncpg://pricetracker:${DB_PASSWORD}@postgres:5432/pricetracker
      - REDIS_URL=redis://redis:6379/0
      - CELERY_BROKER_URL=redis://redis:6379/1
      - SECRET_KEY=${SECRET_KEY}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
      interval: 30s
      timeout: 10s
      retries: 3

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: pricetracker
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: pricetracker
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U pricetracker"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  celery-worker:
    build:
      context: .
      dockerfile: docker/Dockerfile
    command: celery -A app.core.celery_app worker --loglevel=info --concurrency=4
    environment:
      - DATABASE_URL=postgresql+asyncpg://pricetracker:${DB_PASSWORD}@postgres:5432/pricetracker
      - REDIS_URL=redis://redis:6379/0
      - CELERY_BROKER_URL=redis://redis:6379/1
    depends_on:
      - redis
      - postgres

  flower:
    build:
      context: .
      dockerfile: docker/Dockerfile
    command: celery -A app.core.celery_app flower --port=5555
    ports:
      - "5555:5555"
    depends_on:
      - redis

volumes:
  postgres_data:
  redis_data:

输出:

TEXT 📖 仅展示
CONTAINER ID   IMAGE          STATUS         PORTS
abc123         nginx:latest   Up 2 hours     0.0.0.0:80->80/tcp

▶ 示例:.env 文件

INI
# .env - Docker Compose reads this automatically
DB_PASSWORD=change-me-in-production
SECRET_KEY=your-very-long-random-secret-key-at-least-32-chars
CELERY_BROKER_URL=redis://redis:6379/1

输出:

TEXT 📖 仅展示
// 执行成功

5. 健康检查与启动顺序

(1) depends_on + healthcheck

Docker Compose 的 depends_on + condition: service_healthy 确保服务启动顺序:PostgreSQL 和 Redis 先就绪,FastAPI 再启动。

服务 启动顺序 健康检查
PostgreSQL 1(最先) pg_isready -U pricetracker
Redis 1(最先) redis-cli ping
FastAPI 2(PG+Redis 就绪后) GET /health
Celery Worker 3(Redis+PG 就绪后) 内部心跳

▶ 示例:FastAPI 健康检查端点

PYTHON
@app.get("/health")
async def health_check(db: AsyncSession = Depends(get_db), redis: Redis = Depends(get_redis)):
    # Check database connectivity
    try:
        await db.execute(select(1))
        db_status = "healthy"
    except Exception:
        db_status = "unhealthy"

    # Check Redis connectivity
    try:
        await redis.ping()
        redis_status = "healthy"
    except Exception:
        redis_status = "unhealthy"

    overall = "healthy" if db_status == "healthy" and redis_status == "healthy" else "unhealthy"
    return {
        "status": overall,
        "database": db_status,
        "redis": redis_status,
    }

输出:

TEXT 📖 仅展示
# 函数定义成功

❓ 常见问题

Q 为什么用多阶段构建?
A 构建阶段用完整 Python 镜像(含编译器),运行阶段用 slim 镜像。最终镜像不含构建工具,体积小 5-10 倍,攻击面更小。
Q Docker Compose 适合生产吗?
A 适合中小项目。大型生产环境用 Kubernetes 或 Docker Swarm。Compose 适合开发、测试、小型部署。
Q 如何处理数据库迁移?
A 在 API 容器启动前运行迁移。添加 init 容器或在 entrypoint.sh 中先运行 alembic upgrade head 再启动 uvicorn。
Q Secrets 如何安全传递?
A 开发用 .env 文件(加入 .gitignore)。生产用 Docker secrets 或 K8s Secrets,绝不硬编码或提交到 Git。
Q Workers 数量怎么设?
A 公式:(2 × CPU cores) + 1。4 核服务器设 9 个 workers。但也要考虑内存限制,每个 worker 占 50-100MB。
Q 如何查看容器日志?
A docker compose logs api 查看 FastAPI 日志,docker compose logs -f 实时跟踪。生产环境用 ELK/Loki 聚合日志。

📖 小节


📝 作业

  1. 基础题(难度⭐):编写 PriceTracker 的单阶段 Dockerfile,构建镜像并运行容器,验证 /health 端点可访问。提示:FROM python:3.12-slim + CMD ["uvicorn", ...]
  2. 进阶题(难度⭐⭐):将 Dockerfile 改为多阶段构建,编写 docker-compose.yml 包含 API + PostgreSQL + Redis 三个服务,docker compose up 一键启动。提示:AS builder + COPY --from=builder
  3. 挑战题(难度⭐⭐⭐):完整 Docker Compose 编排——添加 Celery Worker 和 Flower 服务,添加 healthcheck,添加 .env 文件管理密码,添加 entrypoint.sh 在启动前运行 alembic upgrade head。提示:depends_on + condition: service_healthy + entrypoint 脚本

---|

Web-Tutorial.com

Web-Tutorial 技术团队

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

100%

🙏 帮我们做得更好

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

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