做Python后端开发,谁没踩过FastAPI打包的坑?本地跑的好好的服务,传到服务器上缺依赖、端口不对、路径乱码,甚至连uvicorn都找不到——折腾大半天,最后发现就是打包环节没做对,白瞎了一下午开发时间。
今天把FastAPI从开发到生产的打包方案全梳理一遍,从最简单的单文件打包,到容器化、生产级镜像优化,全覆盖,看完你再不会遇到“本地好好的,线上跑不起来”的问题。
先理清楚:FastAPI部署到底要打包什么?
很多新手刚入门会搞混:FastAPI本身只是一个Web框架,你的服务跑起来,其实需要三个部分:
你的业务代码:写的接口、路由、工具函数这些
Python依赖环境:FastAPI本身、uvicorn、pydantic,还有你用的各种第三方库
运行入口:启动服务的命令、绑定的IP/端口配置
打包出问题,90%都是这三个部分某一个没对齐:要么本地Python版本和服务器不一样,要么依赖漏装,要么路径写死导致找不到文件。接下来我们从最简单到最复杂,一个个说方案。
方案一:最基础的requirements.txt打包——小项目够用,新手入门首选
这是最传统、最简单的方案,适合个人小项目、原型验证,不需要学复杂工具,两步就能搞定。
第一步:本地导出依赖,别直接pip freeze
很多人习惯直接pip freeze > requirements.txt,但这个方法会把你本地所有的包都导进去,包括开发调试用的工具、你根本没用到的依赖,传到服务器安装慢还容易冲突。
正确做法是用pipreqs只导出你项目实际用到的依赖:
bash
# 安装pipreqs
pip install pipreqs
# 在项目根目录生成requirements.txt,会自动扫描你import的包
pipreqs . --force
生成之后打开requirements.txt检查一下,FastAPI和uvicorn肯定要有,一般长这样:
txt
fastapi>=0.100.0
uvicorn[standard]>=0.27.0
pydantic>=2.0.0
第二步:服务器部署,对齐Python版本
打包流程:
把你的项目代码(除了本地虚拟环境、__pycache__这些)打包成zip传到服务器
服务器上先装和你本地大版本一致的Python(比如本地用3.11,服务器就装3.11,别差大版本)
新建虚拟环境隔离依赖,避免和系统Python冲突:
bash
python3 -m venv venv
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
启动服务,注意IP一定要写0.0.0.0,不然只能本地访问:
bash
uvicorn main:app --host 0.0.0.0 --port 8000
这个方案的坑&解决方法:
❌ 坑1:启动提示No module named 'main'——大概率是你没在项目根目录启动,或者你的入口文件名不是main.py,改成你实际的文件名就行:uvicorn your_filename:app
❌ 坑2:本地正常,服务器报依赖版本冲突——就是因为直接pip freeze导了太多无用依赖,用pipreqs重新生成就好
✅ 优点:简单不用学额外工具,适合小项目
❌ 缺点:环境对齐麻烦,多项目部署容易依赖冲突,要自己维护进程守护
方案二:单文件可执行打包——不用装Python,一个文件就能跑
如果不想在服务器装Python环境,想打一个直接运行的二进制包,可以用PyInstaller打包,适合给客户交付独立服务、轻量工具类FastAPI项目。
打包步骤:
安装PyInstaller:
bash
pip install pyinstaller
在项目根目录新建build.spec配置文件,核心要把静态文件、隐式依赖加进去,不然打包完找不到:
python
import sys
from pathlib import Path
# 你的入口文件
entry_file = "main.py"
# 项目根目录
root = Path(".")
a = Analysis(
[entry_file],
pathex=[str(root)],
# 如果你有静态文件、模板文件夹,要加到这里
datas=[
("static", "static"),
("templates", "templates")
],
hiddenimports=[
"uvicorn.logging",
"uvicorn.lifespan",
"pydantic.deprecated",
],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=None)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='fastapi-service',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True, # 控制台输出日志,调试方便
disable_windowed_traceback=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
执行打包:
bash
pyinstaller build.spec
打包完成后,在dist文件夹里会得到一个fastapi-service二进制文件,直接传到服务器运行就行:
bash
./fastapi-service
这个方案的坑&优化:
❌ 坑1:打包后的文件特别大,几十M很正常——因为把整个Python环境都打进去了,接受不了就用UPX压缩,能小一半
❌ 坑2:动态导入的模块会找不到——一定要在hiddenimports里加进去,比如你用了sqlalchemy,就要把相关模块加进去
✅ 优点:不用装Python,一个文件就能跑,部署简单
❌ 缺点:文件大,跨平台打包麻烦(本地Mac打包的不能在Linux跑),不适合中大型项目
方案三:Docker容器化打包——生产环境首选,一次打包到处运行
这是现在生产环境FastAPI的标准打包方案,彻底解决“环境不一致”的问题,不管你在哪开发,打包完镜像在哪都能跑起来。
标准Dockerfile写法(带优化):
别用网上那种几行的基础写法,那种打包出来镜像几个G,我们用多阶段构建,最后镜像能控制在100M以内:
dockerfile
# 第一阶段:构建依赖,只装需要的包,缓存分层
FROM python:3.11-slim AS builder
# 设置pip镜像源,加速下载
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 新建工作目录
WORKDIR /app
# 先复制requirements,利用Docker缓存,没改依赖就不用重新装
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# 第二阶段:运行阶段,只留运行需要的内容,最小化镜像
FROM python:3.11-slim AS final
WORKDIR /app
# 从builder阶段复制装好的依赖过来
COPY --from=builder /root/.local /root/.local
# 复制你的项目代码
COPY . .
# 暴露端口
EXPOSE 8000
# 启动命令,一定要加--host 0.0.0.0
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
.dockerignore文件(一定要加,不然镜像会很大):
text
__pycache__
venv
.git
.gitignore
.env
*.pyc
*.pyo
*.pyd
.DS_Store
打包&运行命令:
bash
# 打包镜像,tag命名方便管理
docker build -t fastapi-service:v1 .
# 运行容器,映射端口,后台运行
docker run -d -p 8000:8000 --name fastapi-service fastapi-service:v1
生产级优化:
用非root用户运行:避免容器权限风险,在Dockerfile最后加上:
dockerfile
RUN useradd -m appuser
USER appuser
配合docker-compose做多服务编排:如果你需要同时连MySQL、Redis,直接写docker-compose.yml一键启动,不用单独一个个装。
开启uvicorn多进程模式:利用多核CPU提升性能,启动命令改成:
bash
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
workers数量一般设成CPU核心数的2倍+1就行。
✅ 优点:环境一致,部署方便,可扩展,适合生产
❌ 缺点:需要学Docker基础,对新手有一点门槛
三个通用坑,90%的人都踩过
不管用哪个打包方案,这几个坑几乎每个人都遇到过,提前避开省时间:
IP绑定错:永远别写--host 127.0.0.1,服务器上这么写,外部根本访问不到,一定要写0.0.0.0
硬编码绝对路径:比如你本地把配置文件放在/Users/xxx/project/config.py,传到服务器肯定找不到,永远用相对路径,或者通过环境变量配置路径:
python
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
CONFIG_PATH = BASE_DIR / "config.yaml"
端口被占用:服务器上启动提示端口已被占用,要么换端口,要么杀掉占用端口的进程:lsof -i:8000找到PID,然后kill -9 PID就行
总结:不同场景选不同方案
表格
场景 推荐方案 优势
新手入门、个人小项目 requirements.txt打包 简单,不用额外工具
独立工具、交付单服务 PyInstaller单文件打包 不用装Python,部署极简
生产环境、中大型项目 Docker容器化 环境一致,易扩展,稳定
只要把打包环节的这几件事做对,FastAPI基本不会再出现“本地好好的,线上跑不起来”的问题,选对适合自己项目的方案,比瞎折腾半天有用多了。