当前位置: 首页 > news >正文

Docker 部署 - 不只是写个 Dockerfile:一次 FastAPI 项目的“排错”复盘

Docker 部署 - 不只是写个 Dockerfile:一次 FastAPI 项目的“排错”复盘

最近将一个 FastAPI 项目部署到 VMware 虚拟机时,我差点体会到了什么叫“从入门到放弃”。本以为 docker compose up 是终点,没想到是噩梦的起点。但坚持就是胜利,最终还是成功通关!!!

这篇文章记录了我如何通过分析报错日志,逐一击破 5 大关卡:从 Docker 镜像拉取的“网络迷宫“,到 pip 依赖的“积木难题”,再到容器网络的“巴别塔”混乱。如果你也在部署时感到迷茫,不妨看看这份“血泪史”复盘,或许能帮你少走几小时弯路。


目录
  • Docker 部署 - 不只是写个 Dockerfile:一次 FastAPI 项目的“排错”复盘
    • 一、项目部署背景
    • 二、第一关:Docker 镜像拉取失败,是网络层问题
      • 2.1 先判断是不是网络问题
      • 2.2 方式一:配置 Docker 镜像加速器
      • 2.3 方式二:给 Docker daemon 配代理
      • 2.4 这一层的判断标准
    • 三、第二关:pip 出现 ResolutionImpossible,是依赖层问题
      • 3.1 typing_extensions 版本冲突
      • 3.2 async-timeout 版本冲突
      • 3.3 conda 导出的 requirements 不适合直接进 Docker
      • 3.4 这一关的判断汇总
    • 四、第三关:Alembic 连不上 MySQL,是容器网络问题
      • 4.1 关键理解:容器里的 127.0.0.1 是它自己
      • 4.2 环境变量名也要对得上
      • 4.3 推荐配置 DB_URI
    • 五、第四关:表建好了但查不到,是表名问题
    • 六、第五关:HBuilderX 提示网络连接失败,是前后端地址问题
      • 6.1 先看前端 BASE_URL
      • 6.2 用浏览器先验证后端地址
      • 6.3 验证码接口路径
    • 七、速查表:Docker 部署报错“翻译器”
    • 八、常用命令速查表
      • 8.1 Docker Compose 常用命令
      • 8.2 数据库相关命令
      • 8.3 Alembic 迁移命令
      • 8.4 网络验证命令
    • 九、总结
    • 参考链接


image

 开始闯关!!![████████░░] 80% 环境准备中...

一、项目部署背景

这次部署的项目结构大概是这样:

组件 作用
FastAPI 后端接口服务
MySQL 8.0 业务数据库
Redis 邮箱验证码缓存
Alembic 数据库结构迁移
Nginx 对外转发 HTTP 请求
HBuilderX / uni-app 本地运行前端页面
VMware Ubuntu Docker 部署环境

整体访问链路可以理解为:

Windows 本地浏览器 / HBuilderX 前端↓
虚拟机 IP,例如 http://192.168.x.x↓
Docker 中的 nginx↓
FastAPI web 容器↓
MySQL db 容器 / Redis 容器

也就是说,前端不在 Docker 里面,而是在 Windows 本地跑;后端、数据库、Redis 在 Ubuntu 虚拟机的 Docker 里面跑。

这就自然带来几个容易混淆的点:

Windows 的 127.0.0.1
Ubuntu 虚拟机的 127.0.0.1
web 容器里的 127.0.0.1
MySQL 容器里的 127.0.0.1

它们看起来都叫 127.0.0.1,但指向的根本不是同一台“机器”。


二、第一关:Docker 镜像拉取失败,是网络层问题

刚开始执行:

docker compose up -d --build

可能会遇到类似报错:

failed to resolve reference "docker.io/library/nginx:alpine"
failed to do request: Head "https://registry-1.docker.io/v2/..."
connect: connection refused

这个错误发生在拉取基础镜像阶段,比如:

image: nginx:alpine
image: mysql:8.0
image: redis:alpine

这类问题通常和代码没关系,属于 Docker 访问镜像仓库失败。

2.1 先判断是不是网络问题

在虚拟机里可以这样检查:

ping baidu.com
curl -I https://registry-1.docker.io/v2/
docker info | grep -i proxy

如果 Docker Hub 无法访问,可以考虑两种方式。

2.2 方式一:配置 Docker 镜像加速器

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<EOF
{"registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com","https://hub-mirror.c.163.com","https://docker.mirrors.ustc.edu.cn"]
}
EOFsudo systemctl daemon-reload
sudo systemctl restart docker

验证:

docker info | grep "Registry Mirrors"

2.3 方式二:给 Docker daemon 配代理

如果使用 VMware NAT 模式,虚拟机访问 Windows 上的代理时,不能写 Windows 的 127.0.0.1,而要写 VMnet8 网关 IP。

Windows 上查看:

ipconfig

找到类似:

VMware Network Adapter VMnet8
IPv4 地址: 192.168.xxx.1

在虚拟机中配置 Docker 代理:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=http://192.168.xxx.1:7897"
Environment="HTTPS_PROXY=http://192.168.xxx.1:7897"
Environment="NO_PROXY=localhost,127.0.0.1"
EOFsudo systemctl daemon-reload
sudo systemctl restart docker

同时要注意,Windows 代理软件里需要开启类似:

Allow LAN
Bind Address = 0.0.0.0

否则虚拟机访问不到 Windows 上的代理端口。

2.4 这一层的判断标准

如果报错出现在:

registry-1.docker.io
nginx:alpine
mysql:8.0
python:3.10-slim

优先考虑 Docker 镜像拉取问题。

如果日志已经开始出现:

pip install -r requirements.txt
Downloading https://pypi.tuna.tsinghua.edu.cn/...

说明已经进入下一层了,不要还一直纠结 Docker Hub。


三、第二关:pip 出现 ResolutionImpossible,是依赖层问题

image

镜像拉下来之后,Dockerfile 里通常会执行:

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt \-i https://pypi.tuna.tsinghua.edu.cn/simple --prefer-binary --timeout 300

这时的报错就变成了“Python 依赖安装失败”。

3.1 typing_extensions 版本冲突

日志里出现过:

ERROR: Cannot install ... and typing_extensions==4.12.2 because these package versions have conflicting dependencies.The conflict is caused by:The user requested typing_extensions==4.12.2cryptography 47.0.0 depends on typing-extensions>=4.13.2

这说明依赖版本不兼容。翻译过来就是:“我没法同时满足这两个包的要求。”

比如,cryptography 说它要 typing_extensions 的 4.13.2 以上版本,而你的 requirements.txt 却死死锁定了 4.12.2 版本。

3.2 async-timeout 版本冲突

日志可能又出现:

ERROR: Cannot install ... and async-timeout==5.0.1 because these package versions have conflicting dependencies.The conflict is caused by:The user requested async-timeout==5.0.1langchain-classic 1.0.7 depends on async-timeout<5.0.0 and >=4.0.0; python_version < "3.11"

这个问题的关键点在:Python 3.10 下,langchain-classic 要求:

async-timeout>=4.0.0,<5.0.0

而当前写死成了:

async-timeout==5.0.1

这就好比你在搭积木

  • 你手里有一块旧积木(比如 langchain-classic),它的卡扣设计只认特定形状的连接件(async-timeout<5.0.0)。
  • 但你的工具箱里只有新积木async-timeout==5.0.1),形状对不上。
  • 这时候,pip 这个“拼装工”就罢工了,这俩完全拼不到一块去!

既然是积木冲突,解决方案无非:

  1. 换积木:升级那个“挑剔”的包(比如升级 langchain),让它兼容新版本。
  2. 削足适履:降级新包,满足旧包的依赖(修改 requirements.txt)。
  3. 引入“翻译官”:使用 pip-tools 或 poetry 这种高级依赖管理工具,它们能自动帮你找到一套“大家都兼容”的版本组合。

如果你使用的是新版 LangChain,请检查其对应版本的依赖要求,原理相同。

3.3 conda 导出的 requirements 不适合直接进 Docker

这次还发现了类似本地路径依赖:

greenlet @ file:///D:/某位置
SQLAlchemy @ file:///D:/某位置
typing_extensions @ file:///某位置...

这些路径只在原来的本机环境里存在,Docker 容器里当然找不到。

应该改成正常 PyPI 依赖:

greenlet>=3.0.0
SQLAlchemy>=2.0.0
typing_extensions>=4.13.2,<5

比如使用 pip list --format=freeze > requirements.txt 重新导出,或者使用 conda env export --no-builds 来去除平台特定信息。

3.4 这一关的判断汇总

如果日志里有:

ResolutionImpossible
conflicting dependencies
The conflict is caused by

就不要先怀疑代理。pip 已经能下载包了,只是版本解不出来。

重建命令:

docker compose build --no-cache web
docker compose up -d

四、第三关:Alembic 连不上 MySQL,是容器网络问题

构建成功后,执行数据库迁移:

docker compose exec web alembic upgrade head

结果报错:

ConnectionRefusedError: [Errno 111] Connect call failed ('127.0.0.1', 3306)pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '127.0.0.1'")

但是进入 MySQL 容器却正常:

docker compose exec db mysql -uroot -p你的密码

说明 MySQL 本身没坏。

4.1 关键理解:容器里的 127.0.0.1 是它自己

在 Docker Compose 中,每个容器都有自己的网络命名空间。

web 容器里的 127.0.0.1 = web 容器自己
db 容器里的 127.0.0.1 = db 容器自己

在 Compose 网络中,容器之间应该用服务名访问:

web -> db:3306
web -> redis:6379

web 容器死活连不上 db 容器,报错 ConnectionRefusedError。但是,在 db 容器里检查过,MySQL 明明跑得好好的啊?

这里的核心误区在于对 127.0.0.1 的理解。

  • 在容器的世界里,127.0.0.1 意思是“本国领土”。
  • 当 web 容器找 12idot0.0.1:3306 时,它其实是在问自己:“我家里有 MySQL 吗?” 没有它就懵了。

这就好比两个不同国家的人(容器),说着不同的母语(网络命名空间)。

这就需要你建立一条“跨国专线”**。

  1. 起个外号(服务名):在 docker-compose.yml 里,我们给数据库服务起名叫 db
  2. 指名道姓:告诉 web 容器,你要找的不是“本国的 127.0.0.1”,而是隔壁“叫 db 的那个国家”。
  3. 修改连接串:把数据库地址从 127.0.0.1 改为 db

这样,web 容器就能通过 Docker 内置的 DNS 翻译官,顺利找到 db 容器了。

4.2 环境变量名也要对得上

本项目后端读取的是:

DB_HOST
DB_USER
DB_PASSWORD
DB_NAME
DB_URI

但 Compose 中如果写成:

environment:- MYSQL_HOST=db- MYSQL_USER=root- MYSQL_PASSWORD=你的密码- MYSQL_DB=你的数据库名称

后端并不会读取这些变量,于是会退回默认配置:

127.0.0.1

这就是为什么 MySQL 明明在运行,Alembic 却连不上。

4.3 推荐配置 DB_URI

最直接的方式是给 web 服务配置完整连接串:

services:web:environment:- REDIS_HOST=redis- DB_URI=mysql+aiomysql://root:你的密码@db:3306/你的数据库名称?charset=utf8mb4

或者拆开写:

services:web:environment:- REDIS_HOST=redis- DB_HOST=db- DB_PORT=3306- DB_USER=root- DB_PASSWORD=你的密码- DB_NAME=你的数据库名称

修改后重启 web 容器:

docker compose up -d --force-recreate web

再次执行迁移:

docker compose exec web alembic upgrade head

成功时会看到:

INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> d71e18fcb326, add user email_code model

这时数据库迁移就完成了。


五、第四关:表建好了但查不到,是表名问题

进入 MySQL 后:

docker compose exec db mysql -uroot -p你的密码

查看数据库:

show databases;
use 你的数据库名称;
show tables;

结果:

+------------------------+
| Tables_in_你的数据库名称 |
+------------------------+
| alembic_version        |
| email_code             |
| user                   |
+------------------------+

但是执行:

select * from users;

报错:

ERROR 1146 (42S02): Table '你的数据库名称.users' doesn't exist

原因很简单:实际表名是单数:

user

不是:

users

正确查询方式:

select * from `user`;
desc `user`;
select * from email_code;
select * from alembic_version;

这里建议给 user 加反引号,因为 user 在 MySQL 中容易和系统概念混淆。

如果 user 表为空,也不一定是错的。还没有注册用户时,表就是空的。

更推荐通过网页注册用户,而不是直接 SQL 插入。因为注册流程通常会做这些事:

邮箱格式校验
验证码校验
密码哈希
重复用户校验
写入 user 表
删除 Redis 中的验证码

如果手动插入,很容易把明文密码写进去,后面登录会失败。


六、第五关:HBuilderX 提示网络连接失败,是前后端地址问题

后端和数据库都跑起来后,用 HBuilderX 打开前端,在注册页点击“获取验证码”,出现提示:

网络连接失败,请检查服务地址

这个提示来自前端 uni.requestfail 回调。它说明:请求没有成功到达后端。

6.1 先看前端 BASE_URL

前端请求配置一般在:

你的项目名称/http/http.js

类似:

const BASE_URL = "http://192.168.x.x:8080";

这里最容易混淆:8080 可能只是 HBuilderX 本地前端预览端口,不一定是后端端口。(通常前端开发服务器是 8080,但生产环境通常不是)。

请确保这里的端口与 docker compose ps 显示的对外暴露端口一致。

如果 Docker Compose 中 nginx 对外暴露的是:

nginx:ports:- "80:80"

那么前端应该访问:

const BASE_URL = "http://192.168.x.x";

如果直接暴露 FastAPI 的 8000 端口,才写:

const BASE_URL = "http://192.168.x.x:8000";

具体用哪个端口,以 docker compose ps 的端口映射为准。

6.2 用浏览器先验证后端地址

在虚拟机中查看 IP:

hostname -I

在 Windows 浏览器中访问:

http://虚拟机IP/

如果后端正常,可能看到:

{"message":"Hello World"}

只有浏览器能访问这个地址,HBuilderX 前端才可能访问成功。

6.3 验证码接口路径

后端验证码接口:

GET /auth/code?email=xxx@xxx.com

前端封装:

getEmailCode: (email) => request("/auth/code?email=" + encodeURIComponent(email), { method: "GET" })

如果前端提示“网络连接失败”,优先查:

BASE_URL 是否正确
虚拟机 IP 是否正确
Docker 服务是否启动
Nginx 或 web 端口是否暴露
Windows 是否能访问虚拟机 IP

如果请求已经到达后端,但邮件发送失败,才继续查 SMTP 配置,比如:

MAIL_USERNAME
MAIL_PASSWORD
MAIL_FROM
MAIL_PORT
MAIL_SERVER
MAIL_STARTTLS
MAIL_SSL_TLS

七、速查表:Docker 部署报错“翻译器”

为了方便大家排查问题,我把这次复盘的“线索”整理成了一张表。下次遇到报错,直接对号入座:

报错关键词 / 现象 可能层级 通俗解释 (Lyn_Li版) 解决方向
registry-1.docker.io / 超时 网络层 “海关”堵车了 配置镜像加速器 / Docker 代理
ResolutionImpossible / 冲突 依赖层 “乐高积木”形状不匹配 修改 requirements.txt 版本范围
Can't connect to MySQL / 127.0.0.1 网络层 “认错亲爹” (容器内 127.0.0.1 指的是自己) 改用服务名访问 (如 db:3306)
Table doesn't exist 数据层 “眼花看错名” 执行 show tables; 确认真实表名 (如 user vs users)
网络连接失败 (前端) 联调层 “地址写错收不到货” 检查 BASE_URL 是否指向虚拟机 IP 和正确端口

这张图比单独背命令更有用。部署失败时,就不再会是盲目把所有问题都归因成“网络不好”或者“Docker 程序问题”。


八、常用命令速查表

8.1 Docker Compose 常用命令

操作 命令
启动服务 docker compose up -d
构建并启动 docker compose up -d --build
只重建 web 镜像 docker compose build --no-cache web
强制重建 web 容器 docker compose up -d --force-recreate web
查看容器状态 docker compose ps
查看 web 日志 docker compose logs -f web
停止容器但保留数据 docker compose down
停止并删除数据卷 docker compose down -v

⚠️ docker compose down -v 会删除数据卷,MySQL 数据可能一起没了,谨慎使用。

8.2 数据库相关命令

# 进入 MySQL 容器
docker compose exec db mysql -uroot -p你的密码
show databases;
use 你的数据库名称;
show tables;
select * from `user`; # 特殊表名
select * from alembic_version; # 普通表名

8.3 Alembic 迁移命令

# 执行迁移到最新版本
docker compose exec web alembic upgrade head# 查看当前迁移版本
docker compose exec web alembic current

8.4 网络验证命令

# 虚拟机中查看 IP
hostname -I# 查看 Docker 端口映射
docker compose ps# 查看 Docker 代理配置
docker info | grep -i proxy

Windows 浏览器中访问:

http://虚拟机IP/

九、总结

这次部署让我对 Docker 的理解更具体了一点:Docker 不是把项目“打包一下”这么简单,它其实把任务拆成了几层。

镜像层:基础镜像能不能拉下来
依赖层:requirements 能不能解出来
容器层:服务之间能不能互相访问
数据库层:迁移有没有真正执行
前端层:浏览器能不能访问到后端入口

每一层都有自己的错误信号。

看到 Docker 部署报错,不要急着重装,也不要所有问题都归因给代理。先看错误发生在哪一层:拉镜像、装依赖、连数据库、跑迁移,还是前端访问。层次分清了,问题通常就小了一半。

问题 核心判断
拉镜像失败 查 Docker Hub、镜像源、daemon 代理
ResolutionImpossible 查 requirements 版本冲突
连不上 127.0.0.1:3306 容器内 localhost 指向当前容器,改用 db:3306
所查询的表不存在 show tables;,查看实际表名
HBuilderX 网络连接失败 先检查 BASE_URL 和虚拟机后端地址

部署跑通后,再回头看这些报错,会发现它们其实都在提示同一件事:不要只看命令,要看命令运行在哪个环境里。


参考链接

  • Docker Compose Networking: https://docs.docker.com/compose/how-tos/networking/
  • Docker daemon proxy: https://docs.docker.com/engine/daemon/proxy/
  • pip dependency resolution: https://pip.pypa.io/en/latest/topics/dependency-resolution/
  • FastAPI Docker Deployment: https://fastapi.tiangolo.com/deployment/docker/

声明:本文借助 AI 辅助工具进行资料整理与初稿生成,所有内容均经过作者本人的核对、修改与编排,文责自负。

http://www.jsqmd.com/news/1056932/

相关文章:

  • 17_家政服务_GEO营销案例实践总结 - 技术瞭望台
  • 从i.MX RT1020迁移到RT1024:硬件设计、软件适配与调试避坑指南
  • E-Ink Launcher:为墨水屏设备打造的终极Android启动器解决方案
  • 2026年6月国内热门的三角型排烟窗公司哪家强,侧墙电动消防排烟窗/电动消防排烟天窗,三角型排烟窗公司哪家权威 - 品牌推荐师
  • 5GHz WiFi射频前端设计:NXP BGU7258 LNA芯片选型、实测与PCB布局实战
  • 2026年高效节能与精密成型技术:中空成型设备实力厂家解析 - 品牌发掘
  • Lerna实战指南:构建高可用前端Monorepo工程体系
  • 安徽省2026年秋季入学想读幼儿教育专业可以选择的10所中职中专学校 - 辛云教育资讯
  • 2026年成都哪个学校可以自考畜牧兽医证书?女生初中毕业可以自考吗? - 知名不具123
  • Ethereum 与 Solana 生态对比:DeFi 协议的架构差异与设计哲学
  • 基于MPC5643L的无感BLDC电机控制:状态机与零交检测实战解析
  • W1502FA高速精密滚珠丝杠技术手册
  • 网络空间测绘实战:Shodan与Cencys自动化资产发现与渗透测试集成
  • 2026年 无菌灌装技术领先与智能产线高性价比的BFS制造商:佛山市工正包装设备科技股份有限公司 - 品牌发掘
  • 安徽省2026年想学计算机网络应用专业好升学好就业的排名前十的中职中专学校盘点汇总 - 辛云教育资讯
  • 技术实现深度解析:R3nzSkin内存注入与钩子技术实现LOL皮肤实时替换
  • Ubuntu 20.04 + Apache + Let‘s Encrypt 一键启用 HTTPS 实战指南
  • COM3D2.MaidFiddler终极指南:5分钟掌握实时女仆编辑技巧
  • Copilot Pro移除Claude Opus原因与Sonnet替代方案实战指南
  • i.MX31 WinCE BSP LCD屏幕适配:从时序计算到驱动调试全解析
  • 常州买猫买狗去哪?全城 5 家正规猫犬舍实地横向测评,皇克莱综合实力断层第一 - 同城宠物优选基地
  • CSRF攻击全流程解析:从原理到防御的实战指南
  • Ubuntu 16.04下Apache Basic认证实战配置与排错
  • TPFanCtrl2:如何让ThinkPad风扇从“被动响应“变为“主动预测“的智能管家?
  • 青岛买猫买狗靠谱吗?全城5家正规猫犬舍实地测评,皇克莱综合实力断层第一 - 同城宠物优选基地
  • 2026年10款论文降AI率工具横评:从90%降至10%的靠谱之选 - 降AI小能手
  • IPXWrapper完整指南:在Windows 10/11上让经典游戏重获联机能力
  • 全域关键词布局,全覆盖番禺所有街道 - 花生花生1
  • 基于NXP K60的IEEE 1588 V2从时钟实现与纳秒级同步精度实测
  • PowerQUICC III平台SRIO启动配置实战:从内存映射到DMA传输