Docker 部署 FastAPI 从零到生产:踩了一堆坑后的完整记录

张开发
2026/4/21 10:44:27 15 分钟阅读

分享文章

Docker 部署 FastAPI 从零到生产:踩了一堆坑后的完整记录
上周末帮朋友把一个 FastAPI 项目部署到服务器上本以为 Docker 部署嘛写个 Dockerfile 构建一下就完事了。结果从镜像体积爆炸、热重载失效、到健康检查不通过整整折腾了一个下午。事后把所有坑和最终方案整理了一遍写成这篇教程省得下次再踩。说实话 FastAPI 开发体验确实丝滑但到了部署这一步很多人包括之前的我都是直接pip install然后uvicorn main:app就完事了。放到 Docker 里门道还挺多的。项目结构先看最终的项目结构后面所有操作都基于这个my-fastapi-app/ ├── app/ │ ├── __init__.py │ ├── main.py │ ├── routers/ │ │ └── users.py │ └── models.py ├── requirements.txt ├── Dockerfile ├── docker-compose.yml ├── .dockerignore └── .env先写一个能跑的 FastAPI 应用app/main.pyfromfastapiimportFastAPIfromapp.routersimportusers appFastAPI(titleMy API,version1.0.0)app.include_router(users.router,prefix/api/v1)app.get(/health)asyncdefhealth_check():return{status:ok,service:my-fastapi-app}app/routers/users.pyfromfastapiimportAPIRouter routerAPIRouter(tags[users])router.get(/users)asyncdefget_users():return[{id:1,name:张三},{id:2,name:李四},]router.get(/users/{user_id})asyncdefget_user(user_id:int):return{id:user_id,name:f用户{user_id}}requirements.txtfastapi0.115.12 uvicorn[standard]0.34.2 pydantic2.11.3Dockerfile从踩坑版到生产版踩坑版别用这个我一开始写的 Dockerfile 长这样FROM python:3.12 WORKDIR /app COPY . . RUN pip install -r requirements.txt CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000]能跑但问题一堆镜像 1.2GB推送慢得要死每次改一行代码依赖全部重新安装用 root 用户运行安全隐患没有多阶段构建把构建工具也打包进去了生产版用这个# 阶段一安装依赖 FROM python:3.12-slim AS builder WORKDIR /app # 先复制依赖文件利用 Docker 层缓存 COPY requirements.txt . RUN pip install --no-cache-dir --prefix/install -r requirements.txt # 阶段二运行环境 FROM python:3.12-slim AS runtime # 创建非 root 用户 RUN groupadd -r appuser useradd -r -g appuser -d /app -s /sbin/nologin appuser WORKDIR /app # 从 builder 阶段复制已安装的依赖 COPY --frombuilder /install /usr/local # 复制应用代码 COPY --chownappuser:appuser ./app ./app # 切换到非 root 用户 USER appuser EXPOSE 8000 # 健康检查 HEALTHCHECK --interval30s --timeout10s --start-period5s --retries3 \ 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, --workers, 4]两个版本对比对比项踩坑版生产版基础镜像python:3.12 (1GB)python:3.12-slim (150MB)最终镜像大小~1.2GB~210MB层缓存利用每次全量重建依赖层独立缓存运行用户root非 root (appuser)健康检查无有多阶段构建否是改代码后重建耗时2-3 分钟10 秒内镜像体积从 1.2GB 降到 210MB这个差距在 CI/CD 推镜像的时候非常明显。.dockerignore 别忘了__pycache__ *.pyc *.pyo .git .gitignore .env .venv venv *.md .mypy_cache .pytest_cache docker-compose*.yml Dockerfile很多人忘了写这个文件结果把.git目录和虚拟环境都 COPY 进去了镜像大小直接翻倍。docker-compose.yml一键启动单独docker builddocker run当然可以但参数多了记不住。我习惯用 docker-compose就算只有一个服务也用services:api:build:context:.dockerfile:Dockerfilecontainer_name:fastapi-appports:-8000:8000environment:-APP_ENVproduction-LOG_LEVELinfoenv_file:-.envrestart:unless-stoppeddeploy:resources:limits:memory:512Mcpus:1.0logging:driver:json-fileoptions:max-size:10mmax-file:3启动dockercompose up-d--build查看日志dockercompose logs-fapi完整部署流程是否编写代码 requirements.txt编写 Dockerfile 多阶段构建编写 .dockerignore编写 docker-compose.ymldocker compose up -d --build健康检查通过?部署完成 ✅查看日志排查问题docker compose logs -f修复问题踩坑记录坑 1uvicorn workers 数量设错一开始设了--workers 1压测的时候 QPS 惨不忍睹。查了下workers 数量的经验公式是workers CPU 核心数 × 2 12 核机器设 4-5 个 workers 比较合理。但有一点要注意如果用了 WebSocketworkers 必须设为 1否则连接会被分配到不同 worker 导致断开。这个坑我找了好久才定位到。坑 2容器里 reload 不生效开发时习惯加--reload但放到 Docker 里改了代码容器不会自动重启。原因很简单你改的是宿主机的文件容器里是 COPY 进去的副本。开发环境用 volume 挂载就行我单独写了个docker-compose.dev.ymlservices:api:build:context:.dockerfile:Dockerfileports:-8000:8000volumes:-./app:/app/app# 挂载源码目录command:[uvicorn,app.main:app,--host,0.0.0.0,--port,8000,--reload]开发时用dockercompose-fdocker-compose.dev.yml up坑 3时区问题容器默认 UTC日志里的时间看着总差 8 小时。在 docker-compose.yml 里加个环境变量environment:-TZAsia/Shanghai或者在 Dockerfile 里ENV TZAsia/Shanghai坑 4健康检查用 curl 但镜像里没有slim 镜像里没有 curl装一个又增大体积。改用 Python 自带的 urllib就是上面 Dockerfile 里那种写法。也有人用wgetslim 镜像里倒是有但 Python 方案更稳。坑 5日志看不到uvicorn 默认日志输出到 stdout但 Docker 的日志驱动如果配了none就啥也看不到。确保 docker-compose.yml 里日志驱动是json-file同时限制日志文件大小不然磁盘迟早被撑爆。生产环境补充Nginx 反代真正上生产不会直接暴露 uvicorn前面再挡一层 Nginx。docker-compose.yml 加个 nginx 服务services:api:build:.container_name:fastapi-appexpose:-8000restart:unless-stoppednginx:image:nginx:1.27-alpinecontainer_name:nginx-proxyports:-80:80volumes:-./nginx.conf:/etc/nginx/conf.d/default.conf:rodepends_on:-apirestart:unless-stoppednginx.confupstream fastapi { server api:8000; } server { listen 80; server_name _; location / { proxy_pass http://fastapi; 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; } }FastAPI Container客户端Nginx :80uvicorn worker 1uvicorn worker 2uvicorn worker 3uvicorn worker 4常用运维命令速查# 构建并启动后台运行dockercompose up-d--build# 查看运行状态dockercomposeps# 实时日志dockercompose logs-fapi# 进入容器排查问题dockercomposeexecapi /bin/bash# 重启单个服务dockercompose restart api# 停止并删除容器保留镜像dockercompose down# 停止并删除容器镜像volume慎用dockercompose down--rmiall-v小结Docker 部署 FastAPI 说难不难细节确实多。几个核心要点用 slim 镜像 多阶段构建镜像体积能小 5-6 倍先 COPY requirements.txt 再 COPY 源码利用层缓存非 root 用户运行安全基本操作开发和生产用不同的 compose 文件开发挂载 volume 开 reload健康检查和日志限制一定要加出了问题不至于两眼一黑这套配置现在基本上每个新项目都复制一份改改就用算是自己的脚手架了。希望能帮你少踩几个坑。

更多文章