docker cp 从标准输入写文件时报 must be a directory
docker cp 的标准输入接收 tar 归档,目标必须是解包目录。区分普通文件复制与归档流传输,避免把原始文本或归档输出误当作文件内容。
把文本通过管道送给 docker cp,例如 printf 'ready\n' | docker cp - demo:/tmp/status.txt,收到 destination ... must be a directory。这里不能只把文件名改成目录就结束排查:docker cp 的 - 输入要求的是 tar 归档流,普通文本不是它接收的文件内容格式。
本文针对 Docker CLI 的 docker cp,使用 Linux 容器中的普通文件路径。- 同时改变了输入格式和目标规则:归档里的条目提供文件名,容器端参数提供解包目录。它不等同于在容器中执行 cat > 文件。
复制一个文件时,直接给本地文件路径
若内容已经保存在本地普通文件 ./status.txt 中,使用普通文件复制即可:
docker cp ./status.txt demo:/tmp/status.txt
demo 是示例容器名,需换成实际目标。容器内 /tmp 必须存在;目标路径若已有需要保留的文件,应先备份。这种形式把一个具体文件复制进去,不需要你手动打包,也不要求容器中装有 shell 或 tar。
与之相比,docker cp - demo:/tmp/status.txt 中的源参数是 -,没有本地源文件名。Docker 按 tar 输入处理,并要求目标是目录,不能用目标参数为原始文本指定一个新文件名。
确实需要流式传输时,先生成 tar
假设本地 ./staging/status.txt 是准备复制的文件,容器内 /tmp 已存在,且已确认不会覆盖需要保留的同名内容。下面在 Bash 中演示归档流的传输:
set -o pipefail
tar -C ./staging -cf - status.txt | docker cp - demo:/tmp/
tar 从 ./staging 取出 status.txt,将归档输出到标准输出;Docker 将它解包到 /tmp/,因此目标文件是 /tmp/status.txt。这里的文件名来自归档条目,而不是 docker cp 的目标文件名参数。
pipefail 让上游打包失败也能使整个管道失败,调用脚本仍需检查并处理这个状态。它不会把解包变成事务:如果传输中断,不能假定目标完全没有变化,重试前应检查实际文件。
不要将来源不明的归档直接解包进应用目录。核对归档中将写入的路径和文件类型,只打包这次需要传入的内容;复制多个条目时尤其要留意同名覆盖。也不要把整个配置目录打包,只为取其中一个小文件。
输出到 - 时得到的也是归档
反向复制也遵循相同约定。docker cp demo:/tmp/status.txt - 输出的是包含该文件的 tar 流,不是文件中的纯文本。如果把它直接重定向保存成 .txt,看到归档头或其他非预期内容,并不能说明容器里的文本损坏。
只想在本地取得文件时,选择一个未占用的本地输出路径:
docker cp demo:/tmp/status.txt ./status-from-container.txt
这也可用于复制后的内容核对。确认回读文件与准备传入的文件一致,再检查应用读取所需的权限。不要仅凭 docker cp 返回成功就认为服务已经加载新配置。
文件传入后仍需核对用途
docker cp 可操作运行中或停止的容器,普通文件复制本身不需要先启动应用。它不会替目标创建缺失的父目录;文件的归属、读取权限和挂载限制也需要按实际目标确认,不能用放宽整目录权限来修复输入格式错误。
若本次覆盖了原文件,使用事先保存的副本恢复,并按应用正常流程检查和加载。只删除本次明确新增的临时文件。长期配置应纳入受维护的镜像或挂载配置,避免把一次复制误当成容器重建后仍会保留的部署变更。


