docker build 跑了 92 分钟还是失败了

docker compose build 跑了 92 分钟,最后死在一个 short read 上。

我没重试,而是用 7 个 md5 证明了旧镜像可以直接用。

打包阶段要重建三个业务镜像。镜像就是打包好的只读文件模板,容器是它跑起来的实例。

命令很简单。

docker compose 是 Docker 官方的多容器编排工具,一份 yaml 管好几个容器。

bash 复制代码
docker compose build gin admin

然后它跑了起来,跑了一小时三十二分钟。结果一半成功,一半失败。

复制代码
#17 DONE 5542.6s                        ← 拉一个层花了 92 分钟
#25 [admin 2/6] WORKDIR /app
#25 ERROR: short read: expected 14450867 bytes but got 10679994: unexpected EOF
target admin: failed to solve: failed to compute cache key

gin 成功了。admin 挂在了 WORKDIR /app 这一步。

挂掉的不是编译,也不是写代码。它死在第 2 步,连代码都还没开始拷。

报错原文里两个数是关键:expected 14450867 bytes but got 10679994。

它说的是,这一层该收到 14450867 字节,实际只收到 10679994 字节。

差了约 3.7MB,连接就断了。

这叫 short read,也就是下载被截断了。

原因是拉基础镜像 python:3.11-slim 的时候,本地镜像源极不稳定。

所以第一件事确定了:这次失败和代码一点关系都没有。

它在下载阶段就死了,压根没轮到源码出场。

看到构建失败,我当时的肌肉记忆是再跑一遍。但先算一下这笔账。

直接重试,代价是再来一次 92 分钟,而且可能再一次被截断。

成不成功,取决于镜像源的心情。

换成证明「这个镜像不需要重建」,代价只有几条命令、几分钟。

成不成功,取决于镜像里装的到底是不是当前代码。

第一条路的坏处不只是慢。更要命的是,92 分钟之后的那个结果,我没法提前知道。

它可能成功,也可能在第 80 分钟又断一次。

真正该想的问题不是「怎么让它成功」。

我一开始想的也是这个。

该想的是:这个镜像,真的需要重建吗?

如果镜像里装的代码就是当前代码,这次失败根本不用修,直接用现成的镜像就行。

那就先取证,再定结论。取证的顺序是先筛时间线,再逐个算 md5。

镜像的构建时间和源码的最后修改时间,都是可以查的。三个镜像挨个对一遍。

app-admin 的镜像构建时间是 2026-08-23 12:06。

源码最后修改是 2026-08-23 11:15,镜像比源码晚了 51 分钟。

app-gin 的镜像构建时间是 2026-09-21 20:05。源码最后修改是 2026-08-26 07:43,它已经重建过了。

app-h5 的镜像构建时间是 2026-08-23 12:06。

源码最后修改是 2026-08-26 08:31,镜像比源码旧,需要重建。

判据很简单。镜像比源码新,大概率包含了最新代码;镜像比源码旧,就一定漏了东西。

app-admin 落在第一种情况。但时间线只是旁证。

它能证明上次构建发生在最后一次改代码之后,却证明不了构建进去的就是这些文件。

万一当时构建的是另一个分支,万一改完之后文件被回滚过,时间线都看不出来。

所以真正做的,是把镜像里的文件算一遍 md5,和本地逐个对。

在容器里算,用镜像自带的 python,不依赖宿主机装任何东西。

bash 复制代码
docker run --rm app-admin:latest python -c \
  "import hashlib;print(hashlib.md5(open(chr(47)+'app'+chr(47)+'config.py','rb').read()).hexdigest())"

在本地算。

bash 复制代码
python -c "import hashlib;print(hashlib.md5(open('config.py','rb').read()).hexdigest())"

两个值一比,就知道镜像里那份和本地这份是不是同一个字节序列。

七个文件跑下来。

复制代码
√ config.py            √ wsgi.py            √ app.py
√ models/admin.py      √ routes/auth.py     √ routes/admin_user.py
√ templates/base.html
         一致 7 / 不同 0

一致 7,不同 0。

结论直接落下来:app-admin:latest 里装的就是当前代码,复用,不重建。

这里顺带踩到一个环境坑。

上面那条命令里,为什么写 chr(47)+'app'+chr(47),而不直接写 /app/config.py?

因为这是在 Windows 的 MSYS 环境里跑的。

MSYS 会自作主张,把命令行里的 /app/config.py 改写成 Windows 路径。

实测变成了 F:/app/install/Git/app/config.py。

容器里自然找不到这个文件,于是每一行都返回「取不到哈希」。

看起来就像镜像里的文件全都不对。

这个假象排查了两轮才定位到。绕法有两个。

bash 复制代码
# 绕法一:让命令行里不出现 /app 这个字面量
python -c "import hashlib;print(hashlib.md5(open(chr(47)+'app'+chr(47)+'config.py','rb').read()).hexdigest())"

# 绕法二:用双斜杠,MSYS 就不转换了
docker run --rm app-admin:latest md5sum //app/config.py

这类坑的共同特征是,它不会报错,只会给你一个看起来很有意义的错误答案。

「取不到哈希」和「哈希不一致」,是两种完全不同的结论,但输出长得差不多。

回头看那三行报错,信息量比想象中大。

复制代码
#25 [admin 2/6] WORKDIR /app
#25 ERROR: short read: expected 14450867 bytes but got 10679994: unexpected EOF
target admin: failed to solve: failed to compute cache key

admin 2/6 说明整个 Dockerfile 一共 6 步,死在第 2 步。

short read 加 unexpected EOF 是网络层的症状。

failed to compute cache key 说的是前面没做成功、后面算不出缓存键。

它是结果,不是原因。

把网络失败和代码失败分开,是这一步唯一重要的事。

分开了,网络失败就去查源、查代理,或者干脆绕过去。

分不开,就会对着自己没问题的代码改上两个小时。

而「绕过去」的前提,是我手上有别的证据证明产物可用。

这就是那七个 md5 的价值。

三个镜像的处置落下来是这样。

app-gin 重建,镜像 ID 从 5090ee632c85 变成 f6f16e877dc6。

依据是源码 8-26 晚于镜像 8-23。

app-h5 也重建,从 f2ed0db556cd 到 c9f55183cb3f,耗时 1.1 秒。

依据是源码更新,加上静态产物今日重建。纯 COPY,所以极快。

app-admin 复用,镜像 ID 是 cab9af444506,依据是 md5 逐文件比对一致。

app-h5 那条 1.1 秒值得单独看一眼。

它的 Dockerfile 只有一句 COPY,把本地构建好的静态产物拷进 nginx 镜像。

没有编译,就没有编译时间。

以后再遇到这个情况,就按照今天这种方式来排查。

报错里带 short read、unexpected EOF,先把失败定位到网络层,先看它死在第几步。

x/y 里的 y 很小,就说明大概率还没碰到你的代码。

想判断旧镜像能不能复用,先比时间线,也就是镜像构建时间对源码最后修改时间。

镜像晚于源码,再进入下一步验证。

用镜像自带的运行时算 md5,和本地逐文件比对,全部一致就直接复用。

记下镜像 ID,跳过这次 build,后续打包直接 docker save 它。

在 Windows 或者 MSYS 下跑容器命令,避免命令行里出现 /app 这个字面量。

用 chr(47) 拼路径,或者写成 //app/...

相关推荐
她说可以呀2 小时前
Docker 镜像的两种创建方式
java·分布式·docker
虎头金猫3 小时前
从上传PPT到观众远程打开互动页面:Claper在CentOS 7上的Docker部署、互动组件设置与固定访问地址实战
linux·运维·网络·人工智能·docker·centos·powerpoint
%KT%5 小时前
ubuntu系统部署docker
运维·docker·容器
十二同学啊5 小时前
Docker 容器化技术详解:镜像、容器、网络与应用部署设计
网络·docker·eureka
I'mChloe6 小时前
不再逐台装Docker:我用Ansible Playbook批量统一服务器环境
服务器·docker·ansible
江湖有缘6 小时前
Docker实战 | 使用Docker部署Readeck书签管理工具
java·docker·eureka
十二同学啊7 小时前
Python FastAPI 项目使用 Docker 部署并映射端口(完整教程)
python·docker·fastapi
flytalei7 小时前
Kubernetes健康检查探针
云原生·容器·kubernetes
java_logo7 小时前
Docker 部署 Halo:轻松搭建开源个人博客
docker·容器·开源·博客·开源博客·halo·轩辕镜像