BuildKit 修改本地目录权限后 COPY 仍显示 CACHED 时,本文说明目录元数据取证、无缓存对照、镜像验收、隔离 builder 与回退边界。
BuildKit 修改目录权限后 COPY 仍命中旧缓存怎么办
先确认变化的是目录元数据,再用可核对的镜像结果证明陈旧缓存,最后定向绕过并隔离受影响 builder
直接答案:如果源码文件内容没有变化,只修改了本地构建上下文中目录的权限,随后 BuildKit 的 COPY 仍显示 CACHED,不要先删除整台主机的 Docker 数据。先记录 BuildKit 版本和当前 builder,用 stat 对比源目录权限,再以全新标签执行一次 --no-cache 构建,并从镜像内读取目录权限。若正常构建保留旧权限、无缓存构建得到新权限,就符合“缓存没有因目录元数据变化而失效”的判断路径。
截至 2026-10-06,BuildKit 上游 Issue #7176 已给出可复现现场,影响本地目录构建上下文;问题仍开放,没有可写成正式修复的发布版本。本文的命令不会修改源文件,但 --no-cache 会重新执行全部构建步骤,可能增加构建时间、镜像仓库流量和依赖下载费用。生产镜像不要直接覆盖现用标签,先用诊断标签验证。
先判断是不是同一种缓存异常
该问题有三个必要特征:构建使用本地目录作为 context;变化对象是已存在目录的权限等元数据;异常发生在 COPY 或 ADD 的缓存判断。普通文件内容改变后仍未进入镜像、远程 Git context 没更新、或者 RUN apt-get update 继续命中缓存,都不是本文同一个意图。
先在构建发起端运行只读检查。把 <SOURCE_DIR> 换成 Dockerfile 会复制的真实目录。
docker buildx ls
docker buildx inspect --bootstrap
stat -c '%A %a %U:%G %n' <SOURCE_DIR>
git status --short
buildx inspect 用来记录 driver、节点和 BuildKit 版本;stat 的第二列是八进制权限,例如 755 或 2755;git status 只帮助确认工作区还有没有其他变化。Git 默认不跟踪目录本身,因此“Git 没变化”不能证明目录权限没有变化。
| 现场 | 判断 | 下一步 |
|---|---|---|
| 文件内容发生变化 | 先按普通构建上下文问题处理 | 核对 `.dockerignore` 与实际 context |
| 只有文件 mtime 变化 | Docker 官方说明 mtime 不参与缓存校验 | 不要把正常命中误判为缺陷 |
| 已存在目录从 755 改成 2755,COPY 仍为 CACHED | 可能命中 #7176 的目录元数据通知缺失 | 做正常构建与无缓存构建对照 |
| 使用远程 Git 或 tar context | 与上游复现条件不同 | 先验证远端提交或归档内容 |
用两个诊断标签证明结果,不覆盖生产镜像
下面先执行普通构建,再执行一次完全不使用缓存的对照构建。<IMAGE> 换成内部诊断仓库或本机镜像名,标签不要使用生产正在部署的 latest。
docker buildx build --progress=plain --load \
-t <IMAGE>:cache-check .
docker buildx build --progress=plain --no-cache --load \
-t <IMAGE>:no-cache-check .
普通构建日志若在目标 COPY 步骤显示 CACHED,只说明 builder 选择了缓存,不证明结果正确。第二次构建会重新执行步骤,耗时和下载量通常更高。若项目包含数据库迁移、外部写操作或需要私有依赖,先在隔离 CI 节点完成,不要把诊断构建当作生产部署。
从镜像内核对目录权限
如果最终镜像包含 shell 和 stat,分别读取两个诊断镜像中的目标目录。把 <TARGET_DIR> 换成 Dockerfile 中 COPY 的实际目标路径。
docker run --rm --entrypoint sh <IMAGE>:cache-check \
-c "stat -c '%A %a %U:%G %n' <TARGET_DIR>"
docker run --rm --entrypoint sh <IMAGE>:no-cache-check \
-c "stat -c '%A %a %U:%G %n' <TARGET_DIR>"
正常结果应与源目录和 Dockerfile 的预期一致。若普通构建仍是旧权限,而无缓存构建是新权限,已经得到可以保留的差异证据。若两个结果相同,应停止套用本文缺陷,转查 COPY --chmod、基础镜像、构建阶段选择或部署是否仍拉取旧标签。
极简 scratch 镜像没有 shell,不能用上面的 docker run。这时用一个临时检查阶段复制同一目录并输出 stat,或把结果导出到独立目录检查;不要为了诊断永久改变正式镜像结构。
恢复构建时怎样选择范围
如果只需要立刻产出一次正确镜像,优先保留源码、Dockerfile 和 builder 状态,使用 --no-cache 构建新标签并完成验收。不要第一步运行 docker system prune -a --volumes;它会扩大到镜像、停止容器、网络和匿名卷,却不能说明根因。
如果多个流水线反复遇到相同现象,把受影响 builder 从生产调度池摘除,创建一个干净的隔离 builder 做对照。新 builder 会失去本地热缓存,可能增加构建时间和流量,但比删除共享主机全部 Docker 数据更容易回退。确认新 builder 连续构建结果稳定后,再决定迁移或等待上游修复。
docker buildx create --name copy-metadata-check \
--driver docker-container --use --bootstrap
docker buildx inspect --bootstrap
这会创建新的 builder 容器并消耗额外磁盘。验证结束后先切回原 builder,再删除临时 builder;不要在尚未导出需要的缓存或镜像时直接删除。
失败分支与回退
- 无缓存构建也得到旧权限:检查 Dockerfile 是否使用
COPY --chmod,以及目标目录是否在后续步骤被重新创建或修改。此时不是陈旧缓存已被证明。 - 新 builder 仍复现:保存最小 Dockerfile、目录权限、BuildKit 版本与纯文本构建日志,说明问题并非旧 builder 状态;不要循环创建 builder。
- 构建正确但部署仍是旧权限:核对镜像 digest 和部署清单,确认运行环境拉取的是新标签对应的新 digest,而不是节点上的旧镜像。
- 无缓存构建成本过高:先在最小复现或单个目标阶段验证,不要直接让所有分支永久禁用缓存。长期全量禁用会损失 BuildKit 的主要效率收益。
修复后怎样验收
验收至少包含四项:构建日志中目标步骤没有错误复用旧缓存;新镜像内目录权限符合预期;应用以新 digest 启动并通过健康检查;下一次没有源变化的构建能够稳定复用缓存。只有“第一次无缓存构建成功”还不能证明缓存行为已经恢复。
相关排障可继续阅读 Buildx prune 后缓存失效、取消构建后缓存持续 in-use、Docker exec format error 和 Docker Compose 生产部署。它们分别处理缓存丢失、缓存无法回收、架构错误和部署验收,不与本文的目录元数据缓存意图重复。
官方与上游依据
- BuildKit Issue #7176:目录元数据变化导致陈旧 COPY 缓存(复现现场,2026-10-06 核验)
- Docker Docs:Build cache invalidation(COPY/ADD 元数据校验与 mtime 边界)
- Docker Docs:Build context(本地目录、Git 与 tar context 区别)
- Docker Docs:docker buildx build(`--no-cache`、`--load` 与构建输出)
常见问题
只改目录时间,缓存没有失效是 bug 吗?
不是。Docker 官方明确说明文件修改时间不参与缓存校验。本文关注的是目录权限等应参与判断的元数据发生变化后,结果仍错误命中旧缓存。
可以永久给所有构建加 --no-cache 吗?
不建议。它可以临时绕过错误结果,但会重复执行依赖下载和编译,增加时间与费用。应把它用于诊断和受控恢复,并保留上游问题复核。
本稿需要黑鲨云确认业务事实吗?
本稿未发现需要补充确认的黑鲨云业务事实。文章只说明 Docker/BuildKit 的排障边界,不把黑鲨云写成 Docker 官方,也不承诺固定修复版本或恢复时效。

