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/...

相关推荐
工业一体机老司机5 小时前
Docker部署工控机采集服务 Compose编排与开机自启
运维·docker·容器
miaobinfei11 小时前
docker拉取dify镜像,执行docker compose up -d 报错
运维·docker·容器
kobe_OKOK_12 小时前
docker compose命名卷 映射到宿主机的路径
运维·docker·容器
晨陌y17 小时前
Flare自托管导航页教程:Docker部署、多用户、API与公网访问
运维·docker·容器
梅孔立19 小时前
Docker 容器网络不通:Tomcat 无法解析 MySQL、Nginx 无法反代 Tomcat 的生产级排查与治理
网络·docker·tomcat
螺蛳粉 螺蛳粉1 天前
第 05 篇:etcd 深入:Raft 共识、watch 机制与快照备份恢复实战(底座认知 05)
docker·云原生·容器·kubernetes·etcd·分布式存储·kube-apiserver
PellyKoo1 天前
【docker】cifs卷改smb密码后容器起不来的解决方法
运维·docker·容器
时早玖沐1 天前
放弃 QQ 官方机器人:用 SnowLuma + AstrBot 自建 QQ 机器人
docker·开源·agent
阿标在干嘛1 天前
容器编排的“场景适配”:我们为什么在部分服务中回归了Docker Compose?
运维·docker·容器
IT19952 天前
Docker 快速部署 DM8 达梦数据库|完整踩坑笔记
笔记·docker·容器