先记住这个答案
COPY --from=<name> 的 name 可以是镜像名或构建阶段名。引用外部镜像时,构建器会拉取该镜像并从中复制指定路径,该镜像与当前构建无依赖。引用构建阶段时,该阶段必须在本 Dockerfile 中通过 FROM 定义,其生成的文件来自前面指令的执行结果。区别:外部镜像不参与当前构建的层缓存,其内容变动只取决于镜像 tag;构建阶段则受源码和指令影响,且可利用多阶段构建的缓存。判断依据:需要的是别人的现成文件,还是自己构建后的产物。
- 外部镜像可作 COPY 来源
- 构建阶段受当前 Dockerfile 控制
- 可重复性首选阶段或固定 digest
解析路径与缓存归属不同
当指定 COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/ 时,BuildKit 先解析镜像引用,若本地或缓存不存在则从 registry 拉取,展开为只读层后过滤源路径。复制出的文件成为新层,但不引入源镜像的元数据。而 COPY --from=build 要求 build 是在同一 Dockerfile 中 FROM ... AS build 声明的阶段,构建器先执行或复用该阶段产物,再提取文件。
缓存边界上,外部镜像的复制操作依赖镜像 digest:若 tag 指向的摘要未变,复制层可命中,但每次仍需向 registry 检查;构建阶段则与前面指令的缓存紧密关联,只要阶段内 RUN、COPY 未变,阶段产物层即可复用,不会重复执行构建命令。路径变化通常使两者缓存失效,但外部镜像的拉取不受本地构建缓存控制。
生产 Go 二进制的两种拷贝方式
项目要求最终镜像只含编译好的 app 和证书。方案 A:先写 FROM golang:1.22 AS build,再 RUN go build,然后 COPY --from=build /app/app /usr/local/bin/app。方案 B:直接 COPY --from=registry.example.com/app:ci-123 /app/app /usr/local/bin/app。A 完全受源码控制,任何代码变化会使 go build 层失效并重编;B 把构建责任交给 CI,本地构建依赖镜像已推送,若无推送则失败,且无法从二进制回溯源码。
决策实际:若普通用户需用 docker build 从源码产出镜像,只能用构建阶段;若团队已有独立发布管道,且 Dockerfile 仅负责打包,可考虑外部镜像。但引用外部镜像建议用 digest 形如 COPY --from=myrepo/app@sha256:abcd,否则 tag 被覆盖会破坏可重复性。构建阶段天然可重复,只需固定基础镜像 digest。
失效条件与代价
外部镜像使用浮动 tag(如 latest)时,每次构建可能拉到不同内容,即使 COPY 指令未变,缓存也会因摘要变化失效,且构建器须向 registry 确认 tag 导致延迟。构建阶段的失效源于内部指令:若 RUN 使用 apt-get 而未锁版本,deb 仓库更新可能改变层内容,Docker 不会检测仍用旧缓存,除非强制 --no-cache。此外,阶段必须完整执行,若依赖网络可能临时失败,而这对外部镜像是现成的拉取。
处理方式:外部镜像固定摘要,且避免用 --pull 强制每次检查(否则失去缓存)。构建阶段应将基础镜像固定为 digest,并显式 COPY 源码和依赖清单,确保缓存命中时内容正确。通常优先构建阶段,因为过程透明、易调试;只有完全静态且由外部团队维护的文件,才把镜像当文件源。
容易答错的地方
- 认为只能引用构建阶段
- 官方文档明确 COPY --from 支持构建阶段、命名上下文或镜像。只要名称可解析为有效镜像或已定义阶段,都能作为来源。但注意阶段名优先于镜像名,若同名则按阶段处理。
- 混淆镜像层与复制内容
- 引用外部镜像时,源镜像的 ENV、CMD、历史层不会进入最终镜像,只有 COPY 指令选中的文件被写入新层。但若使用构建阶段,阶段中的 ENV 等也不继承,因为 COPY 只复制文件。
面试官还会怎么问?
COPY --from=alpine:latest /bin/sh /bin/sh 与构建阶段相比,构建时是否需要重新下载?
若本地或缓存没有 alpine:latest 的层,需要从 registry 拉取;而引用构建阶段时,若该阶段之前已构建过且缓存有效,则不会额外下载(但其基础镜像缺失时仍需拉取)。且 alpine tag 变化会触发每次检查,阶段不会自发变化。
如何强制构建阶段不被缓存?
可用 docker build --no-cache 全局禁用,或在该阶段前插入一个 ARG 并改变值。但更精确是用 BuildKit 的 RUN --mount=type=cache 控制局部缓存。若依赖远端仓库,也可用 --pull 强制拉基础镜像。
如果外部镜像和构建阶段同名,Dockerfile 中如何区分?
若定义了 AS build,则 COPY --from=build 优先解析为当前构建中的阶段,不会去 registry 查。若没有该阶段名,则当作镜像引用处理。建议避免同名以消除歧义。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。