说明 Docker 拉取镜像时出现 lease does not exist、failed commit on ref 或内容摘要丢失,如何确认 image prune 并发、先止损、串行化并验证回退。
如果 docker pull 偶发出现 lease does not exist、failed commit on ref、content digest ... not found 或解压层时提示文件不存在,先不要删除 /var/lib/docker,也不要立即认定镜像仓库损坏。先检查报错时段是否有另一项任务执行 docker image prune。Moby 上游已记录一种仍在处理的竞态:使用 containerd 镜像存储时,拉取和镜像清理重叠,清理可能移除正在保护拉取内容的 lease,随后拉取在提交或解压阶段失败。
默认止损方案是暂停自动清理,让所有拉取结束,再把拉取、部署和清理改成同一把锁下串行执行。这个改动不会删除业务数据,但可能暂时增加镜像占用;清理本身会删除未使用镜像,恢复前必须确认回滚需要的镜像是否仍有容器引用或已在仓库保存。
先判断是不是同一个并发现场
本文只处理“同一台 Docker 主机上,拉取镜像与镜像清理重叠”的故障。如果错误稳定发生在所有机器、手工重试仍立即失败,或者仓库明确返回 401、403、manifest unknown,应分别检查登录权限、镜像名称和标签,可参考Docker pull access denied 排查。如果磁盘或 inode 已满,应先按Docker no space left on device 排查处理容量。
并发清理竞态通常同时满足三点:同一个标签手工重拉经常成功;失败时间与定时清理、部署脚本收尾或测试框架清理接近;daemon 日志中能看到 lease、提交引用、内容摘要或解压层丢失。过滤条件并不能证明安全,上游复现显示,即使 prune 的标签过滤最终匹配不到镜像,也可能影响同时进行的拉取。
第一步:只读确认镜像存储和磁盘状态
先记录 Docker 版本、存储驱动和当前空间。以下命令只读取状态,不会清理镜像。需要在 Docker 主机执行;没有权限时使用具备 Docker 读取权限的运维账号。
docker version
docker info --format 'driver={{.Driver}} driver_status={{json .DriverStatus}} docker_root={{.DockerRootDir}}'
docker system df -v
df -h /var/lib/docker
df -i /var/lib/docker
若 DriverStatus 或完整 docker info 显示 containerd snapshotter,且环境是 Docker Engine 28/29 系列,应继续核对并发清理。若磁盘 100% 或 inode 用尽,容量问题本身就足以解释写入失败,不能仅凭相似报错归因于竞态。docker system df -v 只是估算 Docker 对象占用,不会自动删除任何内容。
第二步:把拉取失败和清理时间对齐
先查 daemon 日志,不要把整份日志公开粘贴;其中可能包含私有仓库地址、镜像名和节点信息。把时间范围替换为实际故障窗口。
sudo journalctl -u docker \
--since '2026-10-15 08:50:00' \
--until '2026-10-15 09:10:00' \
--no-pager | grep -Ei 'lease does not exist|failed commit on ref|content digest|failed to extract|prune|deleting lease'
如果同一分钟先出现 prune 或删除 lease,随后出现 failed commit on ref、rename ... no such file or directory、lease does not exist,并且无并发清理时手工拉取成功,这条证据链支持“清理与拉取竞态”。只有拉取错误而没有任何清理时间证据,还不能下结论。
再检查常见调度入口。以下查询不修改计划:
systemctl list-timers --all | grep -Ei 'docker|prune|cleanup'
sudo grep -RniE 'docker (image |system )?prune' /etc/cron.d /etc/cron.daily /etc/systemd/system 2>/dev/null
ps -ef | grep -E '[d]ocker (image |system )?prune|[d]ocker pull'
如果使用 CI runner、部署平台或测试框架,还要检查平台任务日志。清理不一定来自管理员写的 cron;测试会话结束、部署脚本的 finally 阶段也可能调用镜像清理。
第三步:先停止并发清理,不要动 Docker 数据目录
找到定时器后,先停止并禁用对应的清理 timer;把示例单元名替换为真实名称。停止 timer 不会删除镜像,也不会停止业务容器。不要直接停用陌生系统单元,先用 systemctl cat 核对内容。
systemctl cat docker-image-prune.timer
sudo systemctl disable --now docker-image-prune.timer
若清理写在 CI 或部署脚本中,先移除收尾阶段的 docker image prune -f,或暂停对应任务。不要通过反复 prune“修复”内容存储,也不要手工删除 containerd 的 lease、blob、ingest 目录;这些动作可能把可恢复的拉取失败扩大成现有镜像不可用。
第四步:让拉取、部署和清理串行执行
长期止损可以用主机文件锁保护所有会拉取或清理镜像的任务。锁文件路径只负责互斥,不存放业务数据。下面示例先等待锁,再拉取和部署;清理任务必须使用同一把锁。
flock -w 1800 /run/lock/docker-image-maintenance.lock \
sh -c 'docker compose pull && docker compose up -d'
清理任务改为:
flock -w 1800 /run/lock/docker-image-maintenance.lock \
docker image prune -f --filter 'until=168h'
-w 1800 表示最多等待 30 分钟,不代表清理超时。生产环境应按最长一次拉取和部署时间设置。Docker 官方说明 docker image prune 默认删除悬空镜像,增加 -a 会扩大到所有未被容器引用的镜像;没有明确回收目标时不要使用 -a。使用时间或标签过滤前,先用 docker image ls 检查候选,并保留最近可回滚版本。
第五步:验证止损是否有效
先确认没有清理进程,再对原来失败的镜像按完整标签或 digest 拉取。不要用 latest 代替故障时的实际版本。
pgrep -af 'docker (image |system )?prune' || true
docker pull REGISTRY/PROJECT/IMAGE:TAG
docker image inspect REGISTRY/PROJECT/IMAGE:TAG \
--format 'id={{.Id}} digests={{json .RepoDigests}}'
把 REGISTRY/PROJECT/IMAGE:TAG 替换为真实地址。拉取成功后,还要启动一次非关键容器或让编排平台完成健康检查,确认镜像不只是登记成功。连续多个原故障窗口不再出现 lease/commit/extract 错误,且串行锁日志能证明 prune 没有与 pull 重叠,才能把止损记为有效。
不同结果走哪条分支
| 检查结果 | 判断 | 下一步 |
|---|---|---|
| 停用并发 prune 后拉取稳定成功 | 高度支持并发竞态 | 保留串行锁,关注 Moby Issue 与正式版本说明 |
| 磁盘或 inode 已满 | 容量问题优先 | 先定位大目录和日志,再在维护窗口做受控清理 |
| 仓库返回 401/403 | 认证或授权问题 | 核对登录域名、凭据作用域和仓库权限 |
| 同一 digest 在所有主机都失败 | 仓库对象或发布链更可疑 | 核对 push 结果、清单摘要与仓库日志 |
| 没有任何 prune 时间证据 | 不能归因于本文竞态 | 继续检查网络、磁盘、daemon 重启与仓库状态 |
费用、停机和数据风险
读取日志、查看空间和加文件锁本身通常不产生云资源费用。暂停清理会增加系统盘占用,可能需要临时扩容;费用以所用云平台账单为准。单纯停用 prune 不会停止容器,串行等待会延后部署。删除镜像通常不删除 volume 中的业务数据,但可能失去本地快速回滚镜像;docker system prune --volumes 风险更高,本文不建议用它处理本故障。
如何回退
若串行化后确认故障与 prune 无关,可以恢复原 timer,但不要恢复高频并发清理。先查看单元和下次触发时间,再启用:
systemctl cat docker-image-prune.timer
sudo systemctl enable --now docker-image-prune.timer
systemctl list-timers docker-image-prune.timer
如果文件锁导致部署等待过长,先停止新的清理任务,等待当前持锁任务自然结束,再调整等待时间或拆分维护窗口,不要删除正在使用的锁文件来强行并发。
与其他 Docker 故障怎么区分
镜像 push 阶段超时或摘要问题应参考Docker push timeout 排查;磁盘持续增长但未满,可结合Docker 日志占用排查。本文的独立判断点是:拉取与 prune 在同一 daemon 上重叠,并出现 lease 或内容提交链错误。
官方与上游来源
- Moby Issue #53321:并发 image prune 与 pull 的 lease 竞态(核验日期:2026-10-15)
- Docker Docs:docker image prune(核验日期:2026-10-15)
- Docker Docs:containerd image store(核验日期:2026-10-15)
- Docker Docs:docker system df(核验日期:2026-10-15)
截至核验日,上游 Issue 和修复 PR 仍为开放状态。不要把社区补丁直接编译进生产,也不要声称某个正式 Docker 版本已经包含修复;发布前应再次核对 Issue、PR 和 Docker Engine release notes。

