FastAPI: Docker 部署 — 容器化与多阶段构建
最后更新:2026-08-26
Docker 就像集装箱——把应用和所有依赖打包成标准容器,不管在哪个码头(服务器)都能直接卸货运行,不再有"在我机器上能跑"的问题。
1. 你将学到
- 多阶段 Dockerfile:构建阶段(UV 安装依赖)vs 运行阶段(精简镜像)
- Docker Compose 编排:FastAPI + PostgreSQL + Redis + Celery Worker 的完整栈
- 环境变量与 Secrets 管理:
.env文件与 Docker secrets - 健康检查:
HEALTHCHECK指令与 FastAPI/health端点 - Alice 场景:Charlie 一键
docker compose up启动 PriceTracker 全栈服务
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) 构建流程
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) 完整栈架构
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 聚合日志。📖 小节
- 多阶段 Dockerfile:Builder 阶段安装依赖,Runtime 阶段只拷贝结果,镜像从 1.2GB 降到 200MB
- Docker Compose 编排完整栈:API + PostgreSQL + Redis + Celery Worker + Flower
depends_on+healthcheck保证启动顺序:数据库先就绪,API 再启动.env文件管理环境变量,生产环境用 Docker secrets- 健康检查端点验证数据库和 Redis 连接状态,
HEALTHCHECK指令自动检测
📝 作业
- 基础题(难度⭐):编写 PriceTracker 的单阶段 Dockerfile,构建镜像并运行容器,验证
/health端点可访问。提示:FROM python:3.12-slim+CMD ["uvicorn", ...] - 进阶题(难度⭐⭐):将 Dockerfile 改为多阶段构建,编写 docker-compose.yml 包含 API + PostgreSQL + Redis 三个服务,
docker compose up一键启动。提示:AS builder+COPY --from=builder - 挑战题(难度⭐⭐⭐):完整 Docker Compose 编排——添加 Celery Worker 和 Flower 服务,添加 healthcheck,添加
.env文件管理密码,添加entrypoint.sh在启动前运行alembic upgrade head。提示:depends_on+condition: service_healthy+ entrypoint 脚本
---|