Docker 构建产物找不到速查:多阶段漏 COPY --from、未挂载导出与 CI 取物路径
更新时间:2026-10-24。FAQ 速查页,面向镜像构建「成功」了但产物不见了的同学——容器里跑编译一切正常,登进最终镜像却找不到二进制;或者 CI 流水线绿了,归档目录却是空的。产物找不到的本质是「构建阶段 × 镜像分层 × 宿主编译产物边界」三层各管一段:编译发生在一个阶段、产物拷贝要显式
COPY --from、落到宿主还要靠挂载或 docker cp,三层之间没有任何自动传递。病因收敛在六根轴上:多阶段构建漏写 COPY --from、COPY 源路径与编译输出路径不一致、产物只在停止的容器层里没挂载出来、构建上下文(context)不含产物目录、CI 归档路径与容器内路径错配、.dockerignore 把产物过滤掉。顺一遍基本必中。如果是构建本身报错而不是产物缺失,转 Docker 构建缓存失效速查。
本文要回答的问题
- 多阶段构建里编译明明成功了,最终镜像里为什么没有二进制?
lstat /proj/demo/dist: no such file or directory是路径写错了还是产物根本没生成?- 容器里跑出来的产物,怎么干净地拿到宿主机上?
- CI 流水线显示构建成功,产物归档却是空目录,差在哪一步?
- 本地能拿到产物、CI 拿不到,是环境问题还是路径问题?
一、报错原文与典型现象
产物缺失的「报错」往往不显式出现,更多是静默的空目录(项目路径一律以 /proj/demo 占位,registry 以 registry.example.com 占位):
# 构建期:COPY 找不到源文件,这一层会显式失败
COPY failed: file not found in build context or excluded
by .dockerignore: stat app/dist: file does not exist
# 或 BuildKit 风格
ERROR failed to compute cache key: lstat /proj/demo/dist:
no such file or directory# 运行期:构建成功但入口文件缺失
docker: Error response from daemon: failed to create task:
exec: "/server": stat /server: no such file or directory# CI 期:归档步骤静默拿到空目录
Archiving artifacts: build/output/** (0 files matched)读现象分三段:构建期 COPY failed 是显式的,直接告诉你路径不存在;运行期 stat /server 失败说明 COPY 那步「拷了空气」或根本没写;CI 期 0 files matched 说明产物从未落到归档目录。静默失败比显式失败更要多留一个心眼。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| 最终镜像无二进制但编译日志正常 | 多阶段漏写 COPY --from 编译阶段 | 通读 Dockerfile 数 FROM 与 COPY --from | 在最终阶段补 COPY --from=build 语句 |
| COPY failed 路径不存在 | COPY 源路径与编译实际输出路径不符 | 编译阶段里 ls 确认输出落点 | 对齐 COPY 源路径与编译输出目录 |
| 容器里产物在、宿主拿不到 | 产物只在容器层,未挂载卷导出 | docker inspect 看 Mounts 段 | 启动时 -v 挂宿主目录,或 docker cp 拷出 |
| CI 归档 0 files matched | 归档路径与容器内产物路径错配 | CI 日志对比产物落点与归档 glob | 统一用绝对路径约定产物目录 |
| 本地有产物、COPY 却说没有 | .dockerignore 把产物目录过滤了 | 查看 .dockerignore 规则 | 调整 ignore 规则或用命名构建目标 |
| 改了代码产物还是旧的 | 构建缓存复用了旧编译层 | 观察构建日志 CACHED 标记 | 关键层加 --no-cache 重建或优化层顺序 |
三、排查决策图
下面这张图是标准排查路线,颜色按角色区分:红是报错入口与命中的根因,蓝是排查动作,绿是通过分支,橙是修复动作,紫是防复发沉淀。
读这张图抓分水岭:现象出现在哪一段走哪支——构建期 COPY 显式失败或运行期镜像里缺文件走左支,回到 Dockerfile 数阶段、对路径;容器里产物明明在但宿主或 CI 拿不到走右支,查挂载、docker cp 与归档 glob。镜像内的事和镜像外的事,药不通用。
四、逐条排查展开
多阶段漏 COPY --from 是最经典的静默丢失。 多阶段构建的每个 FROM 都是一个全新文件系统,编译阶段的产物不会「自动」出现在最终阶段——必须显式写 COPY --from=build /proj/demo/dist /app/dist。漏写的结果极具迷惑性:构建日志里编译器欢快地跑完,镜像也构建成功,只有运行时才暴露入口文件不存在。自查方法:通读 Dockerfile,数 FROM 出现几次,再数 COPY --from 出现几次,两者对不上就是嫌疑现场。给阶段起名字(FROM golang AS build)比用序号更不易错。
COPY 源路径与编译输出路径错配排第二。 编译器把产物落在 /proj/demo/build/bin,COPY 写的却是 /proj/demo/dist——BuildKit 会直接报 no such file or directory,这是好事;更糟的是老式构建里某些 COPY 写法静默拷空目录。处置原则:编译命令的输出目录与 COPY 源路径写成同一个常量,Dockerfile 里用 ARG 参数化,杜绝两处手抄。
容器里的产物要落地宿主只有两条正规路。 一是启动时挂载宿主目录:-v /宿主/产物目录:/proj/demo/output,容器编译完直接写进挂载点,宿主立即可见;二是事后 docker cp 容器名:/proj/demo/output /宿主/产物目录。指望「容器停了文件还在镜像里」是误解——容器层随容器删除而消失,docker run --rm 跑完连层都不留。CI 里常用第三种姿势:docker create 建而不启,再 docker cp 取出产物,避免挂载权限问题。
CI 归档空档九成是路径错配。 流水线里编译发生在容器内、归档发生在 agent 宿主上,两个文件系统的路径必须靠挂载点对齐。0 files matched 的固定排查顺序:构建日志里找产物落点 → 对照挂载映射换算成宿主路径 → 对照归档步骤的 glob 表达式。强烈建议全流水线约定一个绝对路径常量(如 /proj/demo/artifacts),容器内、挂载、归档三处共用,一处改三处跟。
.dockerignore 是隐形的过滤器。 构建上下文送进 daemon 之前先过 .dockerignore,产物目录若在黑名单里,COPY 拿到的就是「不存在」。常见于沿用 .gitignore 模板把 dist/、build/ 一并忽略的项目。修法:.dockerignore 里用否定规则放行产物目录,或改用命名 target 构建(docker build --target export)配合 BuildKit 的 --output 直接把产物导出到宿主目录,绕过镜像打包这一层。
旧产物伪装成「找不到」的反例。 还有一类相反症状:产物在,但内容是旧的——构建缓存复用了上一个编译层。判断看构建日志里的 CACHED 标记。这类问题不属于本篇主线,但与 COPY --from 错配常结伴出现,排查时一并核对。
五、规范做法与预防
- 阶段命名与 COPY 成对出现:每个 FROM 命名,最终阶段的每个 COPY --from 在评审时点名核对,漏写当场打回。
- 产物路径常量参数化:编译输出目录、COPY 源路径、CI 归档 glob 共用同一 ARG/环境变量,一处定义三处引用。
- 构建后自验脚本:构建完成立刻在镜像内
ls并校验产物大小/哈希,失败即让流水线变红,不让空镜像出门。 - CI 取物用 docker cp 范式:
docker create+docker cp+docker rm三步写成可复用片段,避免挂载权限在各 agent 上的差异。 - .dockerignore 与 .gitignore 分开维护:产物目录在 dockerignore 里显式放行,注释说明为什么。
- BuildKit --output 直出宿主:纯取产物不打镜像的场景用
--output type=local,路径明确、无中间层。
这一篇在知识体系里的位置
本篇是 Docker 排错速查簇的构建产物入口篇,定位「产物去哪了」:产物在但内容旧看 Docker 构建缓存失效速查;构建期报路径解析类错误看 Docker 路径不存在速查;产物导出后容器内缺运行依赖看 Docker 依赖缺失速查。「现象定段 → 镜像内查 Dockerfile 阶段与 COPY、镜像外查挂载与归档路径」这条主线对一切产物缺失类问题通用。
一句话总结
Docker 构建产物找不到先按现象定段:构建期 COPY failed 与运行期入口缺失走镜像内排查——多阶段构建数 FROM 与 COPY --from 是否成对、COPY 源路径与编译输出是否对齐、.dockerignore 有没有把产物目录过滤掉;容器里有产物但宿主与 CI 拿不到走镜像外排查——产物落地只有挂载卷与 docker cp 两条正规路,CI 归档空档九成是容器内路径与宿主归档 glob 没经挂载点对齐。产物路径全流水线共用一个常量、构建后加镜像内自验脚本,产物静默丢失基本绝迹。