从0到1实战:FastAPI + MySQL 项目 Docker 容器化部署与避坑指南

12 阅读5分钟

从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 来提升安装速度。

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数据库)。

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.tomlrequirements.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.ymlapi.build 层级下,添加一行 network: host,强制让 Docker 在打包阶段直接借用服务器的主物理网络去下载包。

    build: 
      context: .
      network: host  # 添加此行绕过虚拟网桥断联问题

🔒 进阶总结:把密码收好

最后提一个架构上的安全细节:绝对不要把数据库密码硬编码(写死)在代码里! 正如你在我上面的 docker-compose.yml 中看到的,我使用了 env_file: - .env。在实际开发中,我们会在服务器上新建一个隐藏的 .env 文件来存放真实的密码,然后通过环境变量的方式喂给程序。这样即使你的代码上传到了 GitHub,也不会面临删库的风险。

希望这篇实战记录能帮你少走弯路,早日让自己的应用在云端平稳运行!