旧 Dockerfile 前端的 ONBUILD COPY --from 找不到阶段产物
继承的 ONBUILD COPY 跨阶段复制需要 Dockerfile 1.11 起的语法支持。核对实际前端、下游阶段定义和产物内容,旧环境可把复制改为普通下游指令。
最终阶段继承的基础镜像带有 ONBUILD COPY --from=builder ...,而 builder 阶段需要先生成文件。旧构建环境可能在复制时直接报:
failed to compute cache key
"/build/dist": not found
如果源目录预先存在,构建也可能结束了,却没有复制到生成步骤新增的文件。这个场景需要核对 Dockerfile 前端对跨阶段 ONBUILD 的支持,不能只根据报错里的 cache key 就判断缓存损坏。
本文限定继承的触发器本身包含 COPY --from,并引用下游 Dockerfile 中的阶段。普通 COPY --from、阶段名拼错和生成命令本身失败,应分别排查;不能据此认为所有带 ONBUILD 的多阶段构建都会漏执行。
看实际使用的 Dockerfile 前端
先记录当前构建环境,并查看出错的下游 Dockerfile 第一行:
docker buildx version
docker buildx ls
Dockerfile 语法从 1.11 开始支持在 ONBUILD 指令中从其他阶段、镜像或构建上下文复制或挂载文件。BuildKit 0.17.0 内置的 Dockerfile 前端是 1.11.0,但 Buildx 客户端版本、Docker Engine 版本和实际前端版本是不同信息。只升级客户端,不足以证明构建已经使用新语法。
使用外部前端时,在下游 Dockerfile 的最前面明确写:
# syntax=docker/dockerfile:1.11
也可以选用团队已经验证的更新稳定版本。该指令应位于文件最前面,不能放在 FROM 后。构建日志应显示所选前端的解析或加载过程;网络、镜像源策略或构建器能力不允许加载时,应处理对应错误,不把它当成语法已经切换成功。这里的 1.11 是该特性的起始语法版本,不是要求长期停在旧版本。
没有指定外部前端时,需要确认构建器内置的前端具备这项能力。选择新前端与升级整个构建器不是同一操作,不能把“BuildKit 0.17 内置 1.11”反过来解释成所有外部前端都必须先升级到同一服务端版本。
阶段名必须定义在触发 ONBUILD 的 Dockerfile 中
例如,可复用基础镜像的构建文件如下:
# syntax=docker/dockerfile:1.11
FROM alpine:3.22
ONBUILD COPY --from=builder /build/dist/result.txt /app/result.txt
构建这个基础镜像时,ONBUILD 只登记触发器,不立即复制文件。假设它已经以 demo-runtime-onbuild:checked 的名称提供给当前构建器,下游文件应包含对应阶段:
# syntax=docker/dockerfile:1.11
FROM alpine:3.22 AS builder
RUN mkdir -p /build/dist \
&& printf '%s\n' 'artifact-ready' > /build/dist/result.txt
FROM demo-runtime-onbuild:checked
RUN test "$(cat /app/result.txt)" = 'artifact-ready'
demo-runtime-onbuild:checked 是需要先构建或提供的基础镜像占位,不是公共镜像名。两个构建若使用不同构建器或镜像存储,必须先保证下游能取得正确的基础镜像;“找不到基础镜像”与跨阶段触发器支持是两个问题。
这里的 builder 定义在下游文件里。不能只在制作基础镜像时定义一个同名阶段,就期待下游自动继承那个阶段。若 --from 引用的是命名构建上下文,则应把该上下文传给下游构建,不能只传给基础镜像的构建过程。
示例把生成动作直接写成普通 RUN,便于检查依赖关系。实际项目也可能从另一个带 ONBUILD 生成步骤的镜像开始构建 builder;判断时仍应确认所需产物确实由这个阶段生成,再由支持该语法的前端解析跨阶段触发器。
暂时不能换前端时,把跨阶段复制写回下游
如果基础镜像由自己维护,可以发布一个不带该跨阶段 ONBUILD COPY 的版本,把复制操作写进下游的普通 Dockerfile。下面用普通 Alpine 基础镜像展示完整结构:
FROM alpine:3.22 AS builder
RUN mkdir -p /build/dist \
&& printf '%s\n' 'artifact-ready' > /build/dist/result.txt
FROM alpine:3.22
COPY --from=builder /build/dist/result.txt /app/result.txt
RUN test "$(cat /app/result.txt)" = 'artifact-ready'
实际应用的运行库、用户和启动设置仍须保留,不能为了这个示例直接替换生产基础镜像。若继续使用带有失败触发器的旧基础镜像,只在后面追加一条普通 COPY 没有用:触发器在处理 FROM 时就会运行,失败后到不了新增的指令。需要配套修改由自己维护的基础镜像,并为新版本使用独立标签或固定摘要。
普通 COPY 能表达阶段依赖,不需要通过 --no-cache 来补偿缺失的语法能力。也不要预先创建空目录以消除报错;那可能把“构建失败”变成“成功生成缺少文件的镜像”。
验收应检查产物内容
在隔离的测试构建中,保留生成步骤的成功状态,并在最终阶段验证必需文件的内容、版本或已知校验值。上面的 test 只是针对固定演示内容;真实应用应换成自己的产物检查。只检查目录存在,无法发现空目录或旧文件被复制进去。
保存修改前的 Dockerfile、基础镜像引用及构建配置。若切换前端后出现其他兼容问题,可以恢复这些配置并使用已验证的旧镜像制品;不要把原先缺失产物的构建结果当作回退版本。构建前端的调整不需要清空所有 Docker 缓存,也不需要删除现有应用数据。


