Docker COPY 多个文件时报 destination must be a directory
COPY 显式多源或通配符匹配多个文件,并报目标不是目录时,可将目标明确写成以斜杠结尾的路径。区分构建上下文与 WORKDIR,核对锁文件、单文件重命名和最终镜像路径。
Docker 构建停在 COPY package*.json .,并提示目标必须是目录:
When using COPY with more than one source file, the destination must be a directory and end with a /
如果构建上下文中同时有 package.json 和 package-lock.json,package*.json 会匹配多个源文件。此时目标应明确写成目录,末尾带 /:
COPY package*.json ./
这里的关键是明确写出目录目标。遇到这条报错时,不要只因为工作目录已经存在就保留 .,应改成 ./。不同构建器可能在路径处理上存在差异,本文不以这段写法必然报错来判断版本;如果目标本来已有斜杠,应继续检查变量展开和实际触发的指令,不能只凭同一句报错认定原因。
确认这一条 COPY 实际复制了什么
先定位构建日志中失败的 Dockerfile 指令,再检查它的源文件数量。多个源不一定显式写在同一行,通配符也可能从一个匹配变成两个。例如,项目新增锁文件后,原来的 package*.json 就可能同时匹配两个文件。
源路径按构建上下文解析,不按 WORKDIR 解析。若构建命令末尾是项目根目录,检查该目录里的文件;不要仅根据 Dockerfile 所在目录判断。还需查看 .dockerignore 是否排除了其中的文件,因为被排除的内容不会进入通常的构建上下文。
对下面这个片段,/app 影响的是相对目标 ./:
WORKDIR /app
COPY package*.json ./
复制完成后,匹配到的文件应位于 /app/ 内。把源写成 /app/package*.json 并不能表示“读取镜像里已有的文件”;普通本地 COPY 的源仍来自构建上下文。镜像内生成文件、跨阶段复制和上下文缺失,是另外的问题。
按项目需要保留通配符,或明确列出源文件
如果项目确实需要复制所有匹配的 JSON 文件,保留源模式,只把目标改为目录即可:
WORKDIR /app
COPY package*.json ./
如果依赖流程明确要求两个文件,也可以直接列出文件名,使新增其他匹配文件时不会悄悄进入镜像:
WORKDIR /app
COPY package.json package-lock.json ./
第二种写法要求两份文件都存在于构建上下文中。没有锁文件的项目不能直接套用,也不要为了绕过当前报错删除锁文件;那会改变依赖安装的可重复性,却没有修正目标路径规则。
绝对目标同样需要尾部斜杠:
COPY package.json package-lock.json /app/
这些都是对现有 Dockerfile 的局部修改。保留项目原有的基础镜像、依赖安装命令和后续复制步骤,不需要把所有内容改为 COPY . .。扩大复制范围可能把无关文件带入构建,也会让原本清楚的依赖文件边界消失。
单文件复制不要机械补斜杠
目标末尾的斜杠会改变语义。复制单个文件且目标 /config.json 尚不存在时,下面的写法可以把它命名为 /config.json:
COPY settings.json /config.json
改成 /config.json/,含义就变成在该目录下放置文件。因而应只修正这次多源复制的目标,不要批量给 Dockerfile 中所有目标路径加斜杠。源路径尾部的斜杠也不是这里的修复点;要求针对的是最后一个目标参数。
重建时检查结果,失败时保留原配置
保存修改前的 Dockerfile 差异,在本地或隔离构建环境使用与原来相同的上下文、Dockerfile 路径和构建参数重新构建。先确认原先失败的 COPY 已通过,再确认后续依赖安装确实从预期目录读取到了所需文件。构建成功后,按项目已有检查确认最终镜像包含这些文件;多阶段构建还要检查最终阶段,不能仅凭中间阶段成功判断最终产物。
如果错误变成文件不存在或校验和计算失败,说明应转向源文件、上下文和忽略规则,而不是继续给目标追加斜杠。如果仍是同一条目录错误,核对失败行是否来自另一份 Dockerfile、基础镜像的 ONBUILD 指令,或使用了展开后不符合预期的目标变量。
这个修复不涉及删除数据卷、清理 Docker 缓存或修改生产容器。需要回退时恢复 Dockerfile 的对应差异,继续使用原有已验证镜像;不要在构建和产物检查完成前替换运行中的服务。


