K 的一隅

Python FastAPI 后端实战

镜像与容器:用 Docker 跑 FastAPI

镜像、容器、Dockerfile 多阶段构建概念;把 FastAPI 与依赖打进可复现的运行环境;不涉及 k8s 与云厂商部署手册。

7 分钟阅读 更新于 2026-07-28
本文目录

在新机器上跑你的 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

  1. 依赖隔离:项目 A 要 Python 3.12、项目 B 要 3.14,各跑各 容器,不污染主机。
  2. 可复现Dockerfile 进 Git,构建产物与提交绑定。
  3. 交付物清晰:运维拿到 镜像 tag,不必在服务器上 pip install
  4. 与进程模型衔接:一个 容器 常跑一个 Uvicorn 主进程(或多 worker 同 容器);扩缩容是「多 容器 实例」,不是本篇重点。

主机上直接跑 Uvicorn 时,Python 版本、系统库(如 libpq)随操作系统变化;容器 镜像 把这些钉死在 Dockerfile 里。回滚版本 = 回滚 镜像 tag,而不是在服务器上手动 pip install 某个旧版本碰运气。

多阶段 Dockerfile 示例

减小 镜像 体积:构建阶段装编译依赖,运行阶段只留 wheel 与 venv:

dockerfile
# 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 用 容器 启动时注入:

bash
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

python
# 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 视情况
日志彩色 stdoutJSON 到 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 原生:

dockerfile
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 不用改应用。WORKDIRCOPY 路径、uvicorn 模块路径必须与实际包结构一致(如 app.main:app)。

误解 4:把数据库打进同一 镜像。DB 应独立 容器 或托管 服务;API 镜像 只含应用。

最小运行命令回顾

构建与运行各一行,便于记忆 镜像容器 关系:

bash
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。掌握 镜像容器 分工,你就有了「到处一样跑」的交付单元;至于上一台跑几个 容器、谁来做负载均衡,留给后续部署专题。