【问题标题】:Docker - how can I copy a file from an image to a host?Docker - 如何将文件从图像复制到主机?
【发布时间】:2014-10-07 04:03:38
【问题描述】:

我的问题与this question 将文件从容器复制到主机有关;我有一个 Dockerfile,它获取依赖项、从源代码编译构建工件并运行可执行文件。我还想复制构建工件(在我的情况下,它是由sbt dist 在'../target/` 中生成的.zip,但我认为这个问题也适用于罐子、二进制文件等。

docker cp 适用于容器,而不是图像;我是否需要启动一个容器才能从中获取文件?在脚本中,我尝试在后台以交互模式运行/bin/bash,将文件复制出来,然后终止容器,但这似乎很麻烦。有没有更好的办法?

另一方面,我想避免在运行docker save $IMAGENAME 后解压.tar 文件,只是为了取出一个文件(但这似乎是目前最简单,但最慢的选项)。

我会使用 docker 卷,例如:

docker run -v hostdir:out $IMAGENAME /bin/cp/../blah.zip /out

但我在 OSX 中运行 boot2docker,我不知道如何直接写入我的 mac 主机文件系统(读写卷安装在我的 boot2docker 虚拟机中,这意味着我无法轻松共享脚本从与其他人的图像中提取blah.zip。想法?

【问题讨论】:

    标签: docker boot2docker


    【解决方案1】:

    要从图像中复制文件,请创建一个临时容器,从中复制文件,然后将其删除:

    id=$(docker create image-name)
    docker cp $id:path - > local-tar-file
    docker rm -v $id
    

    【讨论】:

    • @ThorSummoner docker create 在 docker 1.3 中引入,blog.docker.com/2014/10/…
    • @ChrisCleeland 当您将 --entrypoint / 参数添加到 docker create 命令时,它是否适用于您的情况?
    • 不知道为什么这个没有被选为正确答案。
    • 绝对正确的答案!!!不依赖容器内的任何东西...对于 Golang 临时图像,这是唯一可能的方法!
    • 有什么理由复制到标准输出然后将其定向到本地文件?当我这样做时,它在文件内容之前和之后转储了一堆控制字符。直接以docker cp $id:path > local-tar-file 运行它效果很好。
    【解决方案2】:

    我在 MacOS 上使用 boot2docker。我可以向您保证,基于“docker cp”的脚本是可移植的。因为任何命令都在 boot2docker 内中继,但随后二进制流会中继回在您的 mac 上运行的 docker 命令行客户端。所以来自 docker 客户端的写操作在服务器内部执行并写回执行的客户端实例!

    我正在与我提供的任何 docker 容器共享 docker 卷的备份脚本,并且我的备份脚本在 linux 和 MacOS 上都使用 boot2docker 进行了测试。备份可以在平台之间轻松交换。基本上我在我的脚本中执行以下命令:

    docker run --name=bckp_for_volume --rm --volumes-from jenkins_jenkins_1 -v /Users/github/jenkins/backups:/backup busybox tar cf /backup/JenkinsBackup-2015-07-09-14-26-15.tar /jenkins
    

    运行一个新的 busybox 容器,并使用名称 jenkins_jenkins_1 挂载我的 jenkins 容器的卷。整个卷被写入文件备份/JenkinsBackup-2015-07-09-14-26-15.tar

    我已经在 linux 容器和我的 mac 容器之间移动了存档,没有对备份或恢复脚本进行任何调整。如果这是你想要的,你可以在这里找到整个脚本的教程:blacklabelops/jenkins

    【讨论】:

      【解决方案3】:

      不幸的是,似乎没有办法直接从 Docker 映像复制文件。您需要先创建一个容器,然后从容器中复制文件。

      但是,如果您的图像包含 cat 命令(并且在很多情况下都会这样做),您可以使用单个命令来完成:

      docker run --rm --entrypoint cat yourimage  /path/to/file > path/to/destination
      

      如果您的图像不包含 cat,只需创建一个容器并按照 Igor 的回答中的建议使用 docker cp 命令。

      【讨论】:

      • 很棒的解决方案。无法访问我的容器,因为它在启动后一秒钟就崩溃了,但需要在其中抓取一个文件。这非常有效。
      【解决方案4】:

      家长评论已经展示了如何使用cat。您也可以以类似的方式使用 tar

      docker run yourimage tar -c -C /my/directory subfolder | tar x
      

      【讨论】:

      • 这个答案是按照原始问题的要求复制目录而不是文件。但是,+1 因为它也适用于文件并带有一个额外的功能:权限和所有者保留。太好了!
      • 其实我用的是docker run --rm --entrypoint tar _image_ cC _img_directory_ . | tar xvC _host_directory_
      【解决方案5】:

      更快的选择是将文件从正在运行的容器复制到已安装的卷:

      docker run -v $PWD:/opt/mount --rm --entrypoint cp image:version /data/libraries.tgz /opt/mount/libraries.tgz
      

      真正的 0m0.446s

      ** VS **

      docker run --rm --entrypoint cat image:version /data/libraries.tgz > libraries.tgz
      

      真正的 0m9.014s

      【讨论】:

      • 这可能与在第一个示例中执行文件的延迟/浅拷贝(想想写时复制)的底层文件系统有关,而不是实际将文件的字节复制到第二个例子。一个有用的测试是查看cat a >bcp a b 是否有类似的时序,如此处所示。此外,如果源路径和目标路径位于不同的文件系统上,那么这两个示例都将导致完整的逐字节复制。
      • 只有解决方案对我有用,可以从图像中复制文件。其他解决方案给出了愚蠢的 docker 错误。我的 docker 版本:Docker version 20.10.6, build 370c289
      【解决方案6】:

      您可以将主机上的本地路径bind 指向容器上的路径,然后在脚本末尾将所需文件cp 指向该路径。

      $ docker run -d \
        -it \
        --name devtest \
        --mount type=bind,source="$(pwd)"/target,target=/app \
        nginx:latest
      

      那以后就不用复制了。

      【讨论】:

        【解决方案7】:
        docker cp $(docker create --rm registry.example.com/ansible-base:latest):/home/ansible/.ssh/id_rsa ./hacked_ssh_key
        

        想要提供基于纯 docker 功能的单线解决方案(不需要 bash)

        编辑:容器甚至不必在此解决方案中运行

        edit2:感谢@Jonathan Dumaine for --rm 所以容器将在之后被删除,我只是从未尝试过,因为从某个已经被上一个命令删除的地方复制一些东西听起来不合逻辑,但我试过了它有效

        【讨论】:

        • 被低估的答案!如果您将 --rm 添加到 docker create 调用以不留下临时容器的痕迹,它也可以工作。
        • 谢谢我编辑了这个,这听起来很奇怪,先 --rm 它然后从中复制文件,我认为它不起作用所以我从未尝试过,但现在我尝试并且它有效哈哈
        • 我认为--rm 在这种情况下不会删除任何内容,因为容器永远不会运行
        • 不,因为正如罗马所说,--rm 不会删除任何东西,因为容器永远不会运行
        • 这必须通过docker rm 调用来移除容器。
        【解决方案8】:

        这个问题的另一个(简短)答案:

        docker run -v $PWD:/opt/mount --rm -ti image:version bash -c "cp /source/file /opt/mount/"
        

        更新 - 正如@Elytscha Smith 所指出的那样这仅在您的图像内置了 bash 时才有效

        【讨论】:

        • *另一个包含 bash 可执行文件 alpine 的图像的简短答案不是默认情况下,从头开始构建的图像也没有
        • 如果您的图片没有bash,您可以使用sh
        • 如果你改用 docker cp,不管容器是否安装了 bash、sh、zsh 或没有 shell,它都可以工作
        【解决方案9】:

        您基本上已经有了最好的解决方案。让容器为您复制文件,然后在完成后自行删除。

        这会将文件从/inside/container/ 复制到您的计算机/path/to/hostdir/

        docker run --rm -v /path/to/hostdir:/mnt/out "$IMAGENAME" /bin/cp -r /inside/container/ /mnt/out/
        

        【讨论】:

        • 我很喜欢。人们给出-1并且不解释为什么。我想知道为什么会这样。
        • 这个解决方案唯一烦人的事情是主机上的文件由 root 拥有,而不是我用来运行 docker 命令的用户。有什么办法可以解决吗?
        【解决方案10】:

        不是对问题详细信息的直接回答,但一般来说,一旦您提取了图像,图像就会存储在您的系统中,其所有文件也会存储在系统中。根据本地 Docker 安装的存储驱动程序,这些文件通常可以在 /var/lib/docker/overlay2 中找到(需要 root 访问权限)。 overlay2 应该是现在最常见的存储驱动了,但是路径可能不同。

        可以使用$ docker inspect image IMAGE_NAME:TAG 找到与图像关联的图层,查找GraphDriver 属性。
        至少在我的本地环境中,以下方法也可以快速查看与图像关联的所有层:
        docker inspect image IMAGE_NAME:TAG | jq ".[0].GraphDriver.Data"

        在这些diff 目录之一中,可以找到想要的文件。
        所以理论上,没有必要创建一个临时容器。 Ofc 这个解决方案很不方便。

        【讨论】:

        • 不错的解决方案,它可能不方便,但肯定有你不想启动容器的用例。请注意,这仅在您使用 overlay2 作为存储驱动程序时有效。 (但与其他路径相同的技术可用于其他存储驱动程序)
        • 实际上我在此期间学到了更多关于容器的知识,并相应地编辑了答案。
        猜你喜欢
        • 1970-01-01
        • 2018-09-13
        • 1970-01-01
        • 2014-05-19
        • 1970-01-01
        • 2014-03-29
        • 1970-01-01
        相关资源
        最近更新 更多