进阶3-4 小时·全栈部署

Python API → Docker 容器化

将一个 FastAPI 应用从开发到容器化的完整流程,包含 Dockerfile 编写、Compose 编排、数据持久化

PythonDockerfastapidockercomposedeployment

Python API → Docker 容器化

将一个 FastAPI 应用从开发到容器化的完整流程,包含 Dockerfile 编写、Compose 编排、数据持久化

你将学到

  • 开发一个 FastAPI 应用
  • 编写 Dockerfile 容器化应用
  • 使用 Docker Compose 编排多服务
  • 实现数据持久化

前置知识

架构设计

┌─────────────────────────────────────┐
│           Docker Compose            │
├─────────────────────────────────────┤
│  ┌─────────┐      ┌─────────────┐  │
│  │  FastAPI │      │  PostgreSQL │  │
│  │  :8000   │──────│  :5432      │  │
│  └─────────┘      └─────────────┘  │
│                         │           │
│                    ┌────┴────┐      │
│                    │ Volume  │      │
│                    └─────────┘      │
└─────────────────────────────────────┘

实现步骤

第一步:FastAPI 应用

# app/main.py
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import List
from datetime import datetime

from .database import get_db, engine, Base
from .models import Todo

Base.metadata.create_all(bind=engine)

app = FastAPI(title="Todo API")

class TodoCreate(BaseModel):
    title: str
    description: str = None

class TodoResponse(BaseModel):
    id: int
    title: str
    description: str
    completed: bool
    created_at: datetime

    class Config:
        from_attributes = True

@app.get("/")
def root():
    return {"message": "Todo API v1.0"}

@app.get("/health")
def health():
    return {"status": "healthy"}

@app.post("/todos", response_model=TodoResponse)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
    db_todo = Todo(title=todo.title, description=todo.description)
    db.add(db_todo)
    db.commit()
    db.refresh(db_todo)
    return db_todo

@app.get("/todos", response_model=List[TodoResponse])
def list_todos(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
    return db.query(Todo).offset(skip).limit(limit).all()

@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(Todo).filter(Todo.id == todo_id).first()
    if not todo:
        raise HTTPException(status_code=404, detail="Todo not found")
    todo.completed = True
    db.commit()
    return todo
# app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os

DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./app.db")

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
# app/models.py
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from .database import Base

class Todo(Base):
    __tablename__ = "todos"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, nullable=False)
    description = Column(String)
    completed = Column(Boolean, default=False)
    created_at = Column(DateTime(timezone=True), server_default=func.now())

第二步:依赖文件

# requirements.txt
fastapi==0.109.0
uvicorn==0.27.0
sqlalchemy==2.0.25
psycopg2-binary==2.9.9

第三步:Dockerfile

# Dockerfile
FROM python:3.11-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.11-slim

RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app

COPY --from=builder /root/.local /home/appuser/.local
COPY --chown=appuser:appuser . .

USER appuser
ENV PATH=/home/appuser/.local/bin:$PATH

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

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

第四步:Docker Compose

# docker-compose.yml
version: "3.8"

services:
  api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/todoapp
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=todoapp
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d todoapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres-data:

第五步:启动和测试

# 构建并启动
docker-compose up -d

# 查看状态
docker-compose ps

# 测试 API
curl http://localhost:8000/todos

# 创建 Todo
curl -X POST http://localhost:8000/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "学习 Docker", "description": "完成 Docker 课程"}'

# 查看日志
docker-compose logs -f api

# 停止服务
docker-compose down

完整项目结构

todo-api/
├── app/
│   ├── __init__.py
│   ├── main.py         # FastAPI 应用
│   ├── database.py     # 数据库连接
│   └── models.py       # SQLAlchemy 模型
├── Dockerfile
├── docker-compose.yml
└── requirements.txt

最佳实践

  1. 健康检查:确保应用真正可用
  2. 依赖等待:使用 depends_on + healthcheck
  3. 数据持久化:使用 volumes 保存数据
  4. 环境变量:通过环境变量配置数据库连接
  5. 自动重启:设置 restart: unless-stopped

常见问题

Q: 如何查看数据库数据? A: docker-compose exec db psql -U user -d todoapp

Q: 如何重置数据库? A: docker-compose down -v 删除数据卷。

Q: 如何更新应用? A: 修改代码后 docker-compose up -d --build

扩展挑战

  1. 添加 Redis 缓存
  2. 实现数据库迁移(Alembic)
  3. 添加 Nginx 反向代理

相关课程

课程相关章节
PythonWeb开发入门:Flask 框架简介
DockerDockerfile 基础
DockerDocker Compose 基础
Docker命名卷