FastAPI Docker容器化部署完全指南

📂 所属阶段:第五阶段 — 工程化与部署(实战篇)
🔗 相关章节:Nginx与Gunicorn生产部署 · Pydantic Settings多环境配置

目录

为什么选择Docker?

在现代软件开发中,Docker已成为标准化部署方式,完美解决了传统"在我电脑上能跑"的问题:

传统部署困境:
┌─────────────────────────────────────────────────────┐
│  开发:Python 3.11, PostgreSQL 13 → 测试:3.10, 14 → 生产:3.11, 15  │
│                        ↓ 环境不一致 ↓                          │
└─────────────────────────────────────────────────────┘

Docker解决方案:
┌─────────────────────────────────────────────────────┐
│  项目代码 + Dockerfile → Image → Container → 随处一致运行  │
└─────────────────────────────────────────────────────┘

核心优势

  1. 环境一致性:开发/测试/生产环境完全统一
  2. 快速部署:秒级启动停止
  3. 资源隔离:避免依赖冲突
  4. 可移植性:一次构建,到处运行
  5. 弹性伸缩:根据负载自动扩缩容

Dockerfile最佳实践

生产级基础结构

# 使用官方轻量级Python镜像
FROM python:3.11-slim as base

# 设置优化环境变量
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    g++ \
    && rm -rf /var/lib/apt/lists/*

# 安装Python依赖
COPY requirements.txt .
RUN pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt

# 最终运行镜像
FROM python:3.11-slim

# 复用环境变量
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

WORKDIR /app

# 从构建阶段复制依赖
COPY --from=base /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=base /usr/local/bin /usr/local/bin

# 创建非root用户(安全最佳实践)
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

# 复制应用代码并设置权限
COPY --chown=appuser:appgroup . .

USER appuser

EXPOSE 8000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# 启动命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

requirements.txt优化

fastapi>=0.109.0
uvicorn[standard]>=0.27.0
gunicorn>=21.2.0
uvloop>=0.19.0
httptools>=0.6.1
sqlalchemy[asyncio]>=2.0.0
asyncpg>=0.29.0
redis[hiredis]>=5.0.0
python-jose[cryptography]>=3.3.0
passlib[bcrypt]>=1.7.4
pydantic-settings>=2.0.0
python-multipart>=0.0.6

多阶段构建优化

使用Poetry的多阶段构建示例,显著减小最终镜像大小:

# 构建阶段
FROM python:3.11-slim as builder

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc g++ \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir poetry

COPY pyproject.toml poetry.lock* ./
RUN poetry config virtualenvs.create false && \
    poetry install --no-dev --no-interaction --no-ansi

# 运行阶段
FROM python:3.11-slim as runtime

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --chown=appuser:appgroup . .
USER appuser

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

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

Docker Compose编排

本地开发环境

# docker-compose.yml
version: "3.9"

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile.dev
    container_name: daoman_fastapi_dev
    ports:
      - "8000:8000"
    environment:
      - ENV=development
      - DEBUG=true
      - DATABASE_URL=postgresql+asyncpg://postgres:postgres@db:5432/daoman_dev
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - .:/app  # 热重载
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload

  db:
    image: postgres:16-alpine
    container_name: daoman_postgres_dev
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: daoman_dev
    ports:
      - "5432:5432"
    volumes:
      - postgres_dev_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

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

volumes:
  postgres_dev_data:
  redis_dev_data:

生产环境编排

# docker-compose.prod.yml
version: "3.9"

services:
  nginx:
    image: nginx:alpine
    container_name: daoman_nginx_prod
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - api
    networks:
      - app-network

  api:
    build:
      context: .
      dockerfile: Dockerfile.prod
    image: daoman_fastapi:latest
    container_name: daoman_fastapi_prod
    restart: always
    expose:
      - "8000"
    environment:
      - ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    networks:
      - app-network

  db:
    image: postgres:16-alpine
    container_name: daoman_postgres_prod
    restart: always
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_prod_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    container_name: daoman_redis_prod
    restart: always
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_prod_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 3
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  postgres_prod_data:
  redis_prod_data:

生产环境配置

Nginx反向代理配置

# nginx/conf.d/fastapi.conf
upstream fastapi_app {
    server api:8000;
    keepalive 32;
}

server {
    listen 80;
    server_name your-domain.com;

    access_log /var/log/nginx/fastapi.access.log;
    error_log /var/log/nginx/fastapi.error.log;

    # 安全头部
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    client_max_body_size 100M;

    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types application/javascript application/json text/css text/plain;

    location / {
        proxy_pass http://fastapi_app;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
        proxy_buffering off;
    }

    location /health {
        access_log off;
        proxy_pass http://fastapi_app/health;
    }
}

环境变量配置

# .env.production
ENV=production
DEBUG=false

DATABASE_URL=postgresql+asyncpg://user:password@host:5432/dbname
POSTGRES_DB=daoman_prod
POSTGRES_USER=daoman_user
POSTGRES_PASSWORD=secure_password_here

REDIS_URL=redis://redis:6379/0
JWT_SECRET=your_super_secret_jwt_key_here

安全最佳实践

容器安全加固

FROM python:3.11-slim as base
# ... 省略与前面相同的构建步骤 ...

FROM python:3.11-slim
# ... 省略依赖复制步骤 ...

# 创建固定UID/GID的用户
RUN groupadd -r appgroup --gid 1001 && \
    useradd -r -g appgroup --uid 1001 appuser

COPY --chown=appuser:appgroup . .
USER appuser

EXPOSE 8000
# ... 健康检查和启动命令 ...

Docker Compose安全配置

# 在生产服务中添加以下配置
services:
  api:
    # ... 其他配置 ...
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    read_only: true
    tmpfs:
      - /tmp
      - /var/tmp
    volumes:
      - ./logs:/app/logs:rw
      - ./uploads:/app/uploads:rw
    sysctls:
      - net.core.somaxconn=1024
    ulimits:
      nproc: 65535
      nofile:
        soft: 20000
        hard: 40000

健康检查与监控

应用健康检查端点

# health_check.py
from fastapi import APIRouter
from pydantic import BaseModel
from datetime import datetime
import asyncio

router = APIRouter()

class HealthStatus(BaseModel):
    status: str
    timestamp: str
    services: dict

@router.get("/health", response_model=HealthStatus)
async def health_check():
    services_status = {
        "database": await check_database(),
        "redis": await check_redis(),
    }
    
    overall_status = "healthy" if all(services_status.values()) else "degraded"
    
    return HealthStatus(
        status=overall_status,
        timestamp=datetime.now().isoformat(),
        services=services_status
    )

async def check_database():
    try:
        await asyncio.sleep(0.1)  # 替换为实际数据库检查
        return True
    except Exception:
        return False

async def check_redis():
    try:
        await asyncio.sleep(0.05)  # 替换为实际Redis检查
        return True
    except Exception:
        return False

CI/CD集成

GitHub Actions核心流程

# .github/workflows/docker.yml
name: Docker Build and Push

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt pytest
    - name: Run tests
      run: pytest tests/ -v

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      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:
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}
    - name: Build and push
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: your-registry/daoman-fastapi:latest
        cache-from: type=gha
        cache-to: type=gha,mode=max

常见问题与总结

常见问题(FAQ)

Q: 如何优化Docker镜像大小?
A: 使用多阶段构建、选择轻量级基础镜像(如alpine/slim)、删除不必要的依赖、使用.dockerignore排除无关文件。

Q: 为什么要使用非root用户运行容器?
A: 提升安全性,即使容器被攻破,攻击者也只能获得有限权限,无法影响宿主机。

Q: Docker Compose在生产环境中如何管理敏感信息?
A: 使用环境变量文件(.env),不要将其提交到版本控制,或者使用Docker Secrets管理敏感数据。

总结

Docker容器化部署是现代FastAPI应用的标准实践,核心要点包括:

  • 🚀 使用多阶段构建优化镜像大小
  • 🔒 非root用户运行提升安全性
  • 🩺 健康检查确保服务可用性
  • 🔄 CI/CD自动化部署流程
  • 📊 Docker Compose编排生产环境

🔗 相关教程推荐

🏷️ 标签云: FastAPI部署 Docker容器化 Dockerfile Docker Compose 多阶段构建 生产部署 容器安全