本文目录
在新机器上跑你的 FastAPI 项目:装 Python、配虚拟环境、同步依赖、拷 .env、再敲 uvicorn——步骤一多,「在我电脑上能跑」就变成运维事故。Docker 把「环境 + 文件 + 启动命令」打成镜像,在任意装了 Docker 的主机上以容器实例运行,同一 镜像 多次 启动 行为一致。本篇只讲 容器 概念与 Dockerfile 怎么包住 FastAPI,不写 k8s、CI/CD 或某云控制台点击流。
学会 Dockerfile 与 run 命令,你就能够把本地已验证的 FastAPI 应用原样搬到测试机或同事机器上,而不必口述一长串安装步骤。
三个核心概念
| 概念 | 是什么 | 类比 |
|---|---|---|
| Dockerfile | 构建 recipe 的文本文件 | makefile / 安装脚本 |
| 镜像 | 只读层叠文件系统 + 元数据 | 类 + 模板 |
| 容器 | 镜像 的运行实例,有 PID、网络、可写层 | 对象实例 |
构建:docker build -t shop-api:1.0 . → 生成 镜像 shop-api:1.0。
运行:docker run -p 8000:8000 shop-api:1.0 → 起 容器,把主机 8000 映射到 容器 内 8000。
容器 删了不留进程;改代码要重新 build 镜像(或开发时用 volume 挂载源码,生产一般不挂)。
为什么要容器化 FastAPI
- 依赖隔离:项目 A 要 Python 3.12、项目 B 要 3.14,各跑各 容器,不污染主机。
- 可复现:Dockerfile 进 Git,构建产物与提交绑定。
- 交付物清晰:运维拿到 镜像 tag,不必在服务器上
pip install。 - 与进程模型衔接:一个 容器 常跑一个 Uvicorn 主进程(或多 worker 同 容器);扩缩容是「多 容器 实例」,不是本篇重点。
主机上直接跑 Uvicorn 时,Python 版本、系统库(如 libpq)随操作系统变化;容器 镜像 把这些钉死在 Dockerfile 里。回滚版本 = 回滚 镜像 tag,而不是在服务器上手动 pip install 某个旧版本碰运气。
多阶段 Dockerfile 示例
减小 镜像 体积:构建阶段装编译依赖,运行阶段只留 wheel 与 venv:
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN pip install --no-cache-dir uv \
&& uv sync --frozen --no-dev --no-editable
FROM python:3.12-slim AS runtime
WORKDIR /app
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
RUN useradd --create-home appuser
COPY --from=builder /app/.venv /app/.venv
COPY app ./app
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]说明:
AS builder/AS runtime:多阶段把构建工具留在 builder,runtime 镜像 更小。uv sync --frozen:锁文件决定依赖版本,与本地一致。USER appuser:避免 root 跑 容器(安全基线)。CMD:容器 启动 默认命令;可被docker run ... other覆盖。EXPOSE:文档化端口,不自动映射主机。
配置与密钥:别 bake 进 镜像
镜像 应尽量与环境无关。数据库 URL、JWT secret 用 容器 启动时注入:
docker run -p 8000:8000 \
-e DATABASE_URL=postgresql://... \
-e JWT_SECRET=... \
shop-api:1.0或 --env-file .env.production(文件勿 commit)。与 Settings 篇一致:代码读环境变量,镜像 不含 .env。
Python 侧读取方式不变,仍是 Settings + 环境变量;容器 只是把注入点从 shell export 换成 docker run -e 或编排文件的 env 块。同一 镜像 打三个 tag 分别连 dev/staging/prod 库,靠的是 启动 参数而非三个 Dockerfile。
# app/main.py — 与是否在容器内无关
from app.core.settings import get_settings
settings = get_settings()
app = FastAPI(title=settings.app_name, lifespan=lifespan)本地开发 vs 生产 镜像
| 开发 | 生产 镜像 | |
|---|---|---|
| 源码 | volume 挂载热更 | COPY 进 镜像 |
| 依赖 | 可 --dev | --no-dev |
| 进程 | --reload | 无 reload,多 worker 视情况 |
| 日志 | 彩色 stdout | JSON 到 stdout(采集侧接) |
开发可用 docker compose 起 API + Postgres,但 compose 文件属编排,原理仍是「每个 服务 一个 镜像 / 容器」。
Compose 里 API 服务 build 同一 Dockerfile,数据库 服务 用官方 Postgres 镜像,网络内用 服务 名互连。开发 volume 挂载 ./app 实现热更,生产 镜像 则 COPY 烘焙代码——两套 启动 参数用 compose profile 或 override 文件区分,仍不必写云厂商步骤。
构建上下文与 .dockerignore
docker build 把当前目录(context)发给 daemon。用 .dockerignore 排除 .git、.venv、__pycache__,加快构建、避免把垃圾打进 镜像:
.git
.venv
__pycache__
*.md
tests/
.env健康检查直觉
orchestrator(将来 k8s 或云)需要知 容器 是否存活。Docker 原生:
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')"应用应提供轻量 /health(配置篇 lifespan 后可查 DB 可选)。健康检查失败 → 容器 重启策略由运行平台决定,本篇不展开。
本地调试:docker build -t shop-api . 然后 docker run --rm -p 8000:8000 -e DATABASE_URL=... shop-api,用 curl 打 /health 与 /docs,确认 镜像 内 Uvicorn 路径与模块 import 正确。构建失败时先看 Dockerfile 里 COPY 路径是否与 monorepo 布局一致。
镜像 层缓存
Dockerfile 指令顺序影响构建速度:先 COPY 依赖清单、uv sync,再 COPY 源码。依赖不变时重建复用缓存层;只改 业务 代码时不 reinstall 全家桶。这与「把易变放后面」的通用 镜像 优化一致。
常见误解
误解 1:容器 = 虚拟机。容器 共享主机内核,更轻;没有完整 Guest OS。
误解 2:镜像 里要装 Docker。运行 FastAPI 只需 Python venv + 代码;Docker-in-Docker 是进阶场景。
误解 3:改 Dockerfile 不用改应用。WORKDIR、COPY 路径、uvicorn 模块路径必须与实际包结构一致(如 app.main:app)。
误解 4:把数据库打进同一 镜像。DB 应独立 容器 或托管 服务;API 镜像 只含应用。
最小运行命令回顾
构建与运行各一行,便于记忆 镜像 与 容器 关系:
docker build -t shop-api:1.0 .
docker run --rm -p 8000:8000 --env-file .env.production shop-api:1.0第一条产生 镜像;第二条从 镜像 创建并 启动 容器。--rm 表示 容器 退出后删除文件系统层,适合本地试跑;生产环境由平台负责重启策略,通常不用 --rm。
进入运行中 容器 排查:docker exec -it <container_id> /bin/sh(镜像需有 shell;slim 镜像可能只有 bash 或无 shell,那时靠 日志 与 /health 诊断)。容器 内不应手改代码——改了也不进 镜像,重启即丢失;应改源码 rebuild 镜像。
与 FastAPI 系列收尾
到本篇为止,应用代码侧的主线已经齐全:分层 架构、认证、事务 边界、日志 追踪、流式 接口、最后用 Docker 打包交付。容器 不是新框架,而是把 Uvicorn + FastAPI + 依赖封进可搬运单元;启动 时仍走 lifespan 与 Settings,容器 只是换了一种注入环境与 启动 进程的方式。
主机 Docker 与 镜像 registry
构建在本机完成后,镜像 可 push 到 registry(Harbor、ECR 等概念层:存 镜像 层的远程仓库),另一台主机 docker pull 同一 tag 再 docker run,无需在目标机安装 Python 或 uv。本篇不展开具体云控制台;只需建立「Dockerfile → build → 镜像 tag → pull → 容器 run」的心智模型,就与课件「认识 Docker」的范围一致。
资源限制直觉
容器 可设 CPU、内存上限(概念层:--cpus、--memory)。FastAPI 进程 OOM 会被杀;Uvicorn worker 数过多也会撑爆内存。调优 worker 与连接池属于运行参数,写在 启动 命令或编排文件里,不必写进 Dockerfile 层——镜像 保持环境可复现,运行时可调。
安全扫描与 镜像 体积
slim 基础 镜像、多阶段构建、非 root 用户 是三条低成本安全基线。镜像 越小攻击面与拉取时间越小;与 FastAPI 本身无关,但属于交付 容器 时的常识。漏洞扫描在 registry 侧做即可,本篇不展开工具名。
与 Uvicorn worker 数
单 容器 内 uvicorn --workers N 取决于 CPU 核数;每个 worker 是独立进程,各有 lifespan 启动 与连接池。水平扩展时往往「多 容器 单 worker」或「每 容器 少量 worker」,由平台负载均衡;Dockerfile 的 CMD 可写默认 worker,运行时可 override。
数据卷与有状态服务
API 容器 本身应无状态;上传文件应走对象存储而非 容器 可写层——容器 重启可丢弃本地盘。数据库、Redis 用独立 容器 或托管 服务,与 API 镜像 分离,符合十二因子。
端口与网络
容器 内 Uvicorn 监听 0.0.0.0:8000 才能被主机端口映射访问;只绑 127.0.0.1 会导致 docker run -p 连不上。多 容器 互联时用 bridge 网络与服务名 DNS,API 容器 通过 hostname db 连 Postgres 容器——仍是概念,不涉及具体云 VPC。
与系列前文的衔接
- Settings:运行时
-e注入。 - lifespan:容器 收到 SIGTERM 时 Uvicorn graceful shutdown,lifespan
yield后释放连接池。 - 日志:容器 标准实践是 stdout/stderr 采集,对应 结构化 logging 篇。
小结
Dockerfile 描述如何构建 镜像;容器 是 镜像 的运行实例。FastAPI 项目用多阶段 Dockerfile 把 venv 与代码封进 runtime 镜像,配置外置,非 root 启动 uvicorn。掌握 镜像 与 容器 分工,你就有了「到处一样跑」的交付单元;至于上一台跑几个 容器、谁来做负载均衡,留给后续部署专题。