从0到1实战:FastAPI + MySQL 项目 Docker 容器化部署与避坑指南
作为一名刚接触 Python 和 FastAPI 的后端新人,当你在本地成功跑通了带有增删改查功能的"快递单号查询系统"后,下一个面临的巨大挑战就是:如何把它部署到云服务器上?
传统的手工部署(装环境、装数据库、配进程守护)不仅繁琐,而且极易遭遇"在本地明明能跑,上服务器就报错"的环境玄学。因此,我果断选择了现代后端的行业标准------Docker 容器化部署。
这篇文章将记录我从 0 到 1 将 FastAPI 项目 Docker 化的全过程,并毫无保留地分享我在这期间踩过的 4 个史诗级大坑及解决方案,希望能帮到同样在摸索的你。
🏗️ 核心概念:为什么是 Docker?
如果把开发后端应用比作"开餐厅":
- 传统部署:你需要带着菜谱(代码)去新店面,现场装修厨房(装 Python)、买冰箱(装 MySQL)。这极其容易出错。
- Docker 部署:你打造了一个**"集装箱式移动餐厅"**。你在本地把运行环境、代码、甚至数据库统统打包。到了服务器上,一行命令就能让整个集装箱落地营业,环境绝对一致。
📝 核心部署文件(可直接抄作业)
为了让 FastAPI 和 MySQL 协同工作,我们需要编写两个核心文件:Dockerfile(打包应用的图纸)和 docker-compose.yml(多集装箱调度总控)。
1. Dockerfile (构建 FastAPI 镜像)
在项目根目录创建 Dockerfile,这里我们使用了现代化的包管理工具 uv 来提升安装速度。
perl
FROM python:3.12-slim
# 设置工作目录
WORKDIR /app
# 设置环境变量,防止 Python 写入 .pyc 文件,并让输出直接打印到终端(不缓冲)
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# 复制项目依赖文件
COPY pyproject.toml uv.lock ./
# 安装 uv,并使用它直接安装系统级别的依赖
RUN pip install --no-cache-dir uv && \
uv pip install --system -r pyproject.toml
# 复制项目所有代码
COPY . .
# 暴露应用运行端口
EXPOSE 8000
# 启动 FastAPI 服务
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
💡 资深技巧: 注意看,我们是先 复制依赖清单(pyproject.toml),再 安装依赖,最后才复制整个项目代码。这样做可以完美利用 Docker 的缓存机制:只要依赖清单没变,每次修改代码后重新打包都会瞬间完成!
2. docker-compose.yml (编排服务)
这个文件定义了两个服务(容器):api(我们的应用)和 db(MySQL数据库)。
yaml
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
image: express-api:latest
container_name: express_api_server
ports:
- "8888:8000"
# 从 .env 文件读取环境变量(安全规范!)
env_file:
- .env
# 覆盖 .env 中的 DB_HOST,指向同在 docker 网络的 mysql 容器
environment:
- DB_HOST=db
depends_on:
db:
condition: service_healthy
restart: always
db:
image: mysql:8.0
container_name: express_api_mysql
environment:
MYSQL_ROOT_PASSWORD: 123
MYSQL_DATABASE: express_db
ports:
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql
# 添加健康检查,确保数据库启动完毕后 api 服务才连接
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: always
volumes:
mysql_data:
准备好这两个文件后,在服务器终端执行 docker compose up -d --build,你的项目就能上线了!
🩸 血泪避坑指南(Troubleshooting)
在部署过程中,我遇到了几个极其经典的报错,以下是排查过程与最终解法:
坑一:端口冲突 (ports are not available / WinError 10013)
报错现象 :bind: Only one usage of each socket address (protocol/network address/port) is normally permitted. 或 forbidden by its access permissions. 原因分析 :宿主机(服务器本身)上的 3306 端口或 8000 端口已经被其他程序(比如你之前手动装的旧 MySQL 或没关掉的测试服务)霸占了。 解决方案 :修改 docker-compose.yml 中的 ports 映射。只修改左边的数字 (宿主机暴露的端口),保留右边的数字(容器内部端口)。比如上面代码中,我将 MySQL 映射到了外网的 3307,FastAPI 映射到了 8888。
坑二:PyMySQL 缺少 cryptography 依赖
报错现象 :RuntimeError: 'cryptography' package is required for sha256_password or caching_sha2_password auth methods 原因分析 :这是典型的"环境差异"。本地老版本 MySQL 密码锁较弱,PyMySQL 徒手就能连。但 Docker 拉取的 MySQL 8.0 默认使用了高级的 caching_sha2_password 加密,PyMySQL 需要密码学包辅助解密。Docker 环境太纯净,缺乏这个包就会罢工。 解决方案 :在项目的 pyproject.toml 或 requirements.txt 依赖中,显式添加 cryptography 包,然后加上 --build 参数重新构建镜像。
坑三:找不到依赖清单文件 (Build failed)
报错现象 :uv pip install --system -r pyproject.toml' returned a non-zero code: 1 原因分析 :在 Dockerfile 里"没买票就上车"。如果没有用 COPY 指令先把本地的 pyproject.toml 拷进集装箱,uv 在安装时就会抓瞎。 解决方案 :确保 COPY pyproject.toml ./ 指令放在 RUN pip install 之前。
坑四:构建时网络断联 (Errno 101 Network is unreachable)
报错现象 :在 build 镜像下载依赖时一直重试,最后报错 Network is unreachable。但在宿主机 ping 百度是通的,甚至 Docker 也能拉取基础镜像。 原因分析 :云服务器的 IPv6 解析问题,或者系统防火墙隔离了 Docker 的"打包区(Build)"网络,导致 pip 在容器构建时出不了外网。 解决方案 :在 docker-compose.yml 的 api.build 层级下,添加一行 network: host,强制让 Docker 在打包阶段直接借用服务器的主物理网络去下载包。
yaml
build:
context: .
network: host # 添加此行绕过虚拟网桥断联问题
🔒 进阶总结:把密码收好
最后提一个架构上的安全细节:绝对不要把数据库密码硬编码(写死)在代码里! 正如你在我上面的 docker-compose.yml 中看到的,我使用了 env_file: - .env。在实际开发中,我们会在服务器上新建一个隐藏的 .env 文件来存放真实的密码,然后通过环境变量的方式喂给程序。这样即使你的代码上传到了 GitHub,也不会面临删库的风险。
希望这篇实战记录能帮你少走弯路,早日让自己的应用在云端平稳运行!