把 FastAPI 装进 Docker:从本地运行到可交付镜像

摘要:用一个最小 Dockerfile 固定 Python、依赖和启动命令,解释为什么容器必须监听 0.0.0.0、生产不能使用 reload,并用健康检查验证镜像。

应用与依赖被打包进容器并接受健康检查

图:解释器、依赖、应用和启动命令组成镜像,通过端口映射提供服务并接受健康检查。

虚拟环境解决了 Python 依赖隔离,却没有固定操作系统、解释器和启动命令。“在我的电脑上能运行”到了另一台机器,仍可能因为环境差异失败。

这篇文章把一个 FastAPI 服务装进可重复构建的镜像。重点不是背 Dockerfile 指令,而是看清几个直接影响交付的选择:复制顺序、构建上下文、监听地址和生产启动方式。

先看镜像构建和运行的最终命令

docker build -f examples/ch15_docker/Dockerfile -t fastapi-tutorial:ch15 .
docker run --name fastapi-ch15 -p 8000:8000 fastapi-tutorial:ch15

另开终端:

curl http://127.0.0.1:8000/health

预期返回 {"status":"ok"}

先确认本机真的有可用的 Docker Server

cd 04-fastapi-beginner
docker version

命令需要同时显示 Client 和 Server 信息。若只有 Client 或提示无法连接 daemon,请先启动 Docker Desktop 或容器运行时。

本文镜像不需要本地虚拟环境;安装发生在镜像内部。

用四段 Dockerfile 固定运行环境

固定基础镜像和环境

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

固定 Python 小版本系列比 python:latest 更可预测。slim 减少无关系统组件。

先复制依赖

COPY examples/ch15_docker/requirements.txt ./requirements.txt
RUN python -m pip install --no-cache-dir -r requirements.txt

COPY examples/ch15_docker/main.py ./main.py

代码变化但依赖不变时,Docker 可以复用安装层缓存。

使用容器启动命令

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

容器内必须监听 0.0.0.0 才能接受映射端口流量;生产启动不使用 --reload

容器镜像按基础环境、依赖和应用分层构建

图:先固定基础环境和依赖层,再复制经常变化的应用代码,镜像构建会更稳定。

构建镜像,启动容器,再检查健康状态

docker build -f examples/ch15_docker/Dockerfile -t fastapi-tutorial:ch15 .
docker run -d --name fastapi-ch15 -p 8000:8000 fastapi-tutorial:ch15
curl -i http://127.0.0.1:8000/health
docker logs fastapi-ch15
docker stop fastapi-ch15
docker rm fastapi-ch15

预期构建成功、健康检查 200、日志出现 Uvicorn 启动信息,容器能够正常停止。静态校验:

python -m pytest tests/test_ch15.py -q

镜像、容器和端口映射分别解决什么

镜像是只读交付物,容器是镜像的一次运行实例。数据库文件、上传文件等持久数据不应只写在容器可写层,应使用数据库服务或显式 volume。

Dockerfile 中的 EXPOSE 8000 是元数据,不会自动发布端口;-p 8000:8000 才把宿主端口映射到容器。

生产还需要非 root 用户、镜像扫描、只读文件系统、资源限制、健康探针和外部秘密注入。本文先建立可重复交付闭环。

主机访问通过端口映射进入容器内监听的应用

图:主机请求先到容器端口,再沿映射通道到达容器内部监听的应用。

容器启动了,接口却访问不到的常见原因

  • 服务监听 127.0.0.1:容器外无法访问,改为 0.0.0.0。
  • build context 选错:本 Dockerfile 要从项目根目录构建。
  • .venv 复制进镜像:平台可能不同且体积巨大,使用 .dockerignore
  • 容器删除后 SQLite 数据消失:使用 volume 或外部 PostgreSQL。
  • 生产使用 --reload:增加文件监控和重启行为,不适合作为生产进程管理。

从教学镜像继续补齐生产约束

  1. 给镜像增加 Docker HEALTHCHECK。
  2. 使用 docker inspect 查看端口映射和启动命令。
  3. 比较加入与移除 .dockerignore 后的构建上下文。
  4. 增加非 root 用户运行应用。
  5. APP_VERSION 作为环境变量传入并在 /health 返回。

最后,用测试固定这次改动

手动请求通过后,运行与本文对应的自动化测试:

python -m pytest tests/test_ch15.py -q
python tests/validate_course.py

写在最后

应用已经形成可交付镜像。系列最后一篇会把认证、数据库、依赖、路由、所有者权限和场景测试合并成一个完整项目,并按真实用户旅程验证。