【问题标题】:How to mount host volumes into docker containers in Dockerfile during build如何在构建期间将主机卷挂载到 Dockerfile 中的 docker 容器中
【发布时间】:2014-11-20 22:53:13
【问题描述】:

自从 2014 年提出这个问题以来,发生了许多情况,许多事情发生了变化。我今天再次重新讨论这个话题,我正在第 12 次编辑这个问题以反映最新的变化。这个问题可能看起来很长,但它是按时间倒序排列的,所以最新的变化在顶部,随时停止阅读。

我想解决的问题是——如何在构建期间将主机卷挂载到 Dockerfile 中的 docker 容器中,即在docker build 期间具有docker run -v /export:/export 功能。

对我来说,背后的一个原因是在 Docker 中构建东西时,我不希望那些 (apt-get install) 缓存锁定在单个 Docker 中,而是共享/重用它们。

这是我问这个问题的主要原因。我今天面临的另一个原因是试图利用来自主机的巨大私人回购,否则我必须使用我的私人 ssh 密钥从 docker 内的私人回购中执行git clone,我不知道如何和还没看。

最新更新:

@BMitch 回答中的 Buildkit

使用 RUN --mount 语法,您还可以从构建上下文绑定挂载只读目录...

它现在已经内置在 docker 中(我认为它是一个第三方工具),只要你的超过 18.09。我的现在是 20.10.7 -- https://docs.docker.com/develop/develop-images/build_enhancements/

启用 BuildKit 构建

全新安装 docker 最简单的方法是在调用 docker build 命令时设置 DOCKER_BUILDKIT=1 环境变量,例如:

$ DOCKER_BUILDKIT=1 docker build .

否则,你会得到:

the --mount option requires BuildKit. Refer to https://docs.docker.com/go/buildkit/ to learn how to build images with BuildKit enabled

因此,如上所述,这将是我的第二个用例的完美解决方案。

2019 年 5 月 7 日更新:

在docker v18.09之前,正确答案应该是以下开头的那个:

有一种方法可以在构建期间挂载卷,但不涉及 Dockerfile。

但是,这是一个表述不佳、组织有序且支持不充分的答案。当我重新安装我的 docker contains 时,我偶然发现了以下文章:

Docker 化一个 apt-cacher-ng 服务
https://docs.docker.com/engine/examples/apt-cacher-ng/

这是码头工人对这个/我的问题的解决方案,不是直接而是间接。这是 docker 建议我们做的正统方式。而且我承认这比我在这里要问的要好。

另一种方法是,新接受的答案,例如 v18.09 中的 Buildkit。

选择适合你的。


曾经:曾经有一个解决方案——rocker,它不是来自 Docker,但现在该 rocker 已停产,我将答案回复为 “不可能” 再次。


旧更新:所以答案是“不可能”。我可以接受它作为答案,因为我知道该问题已在 https://github.com/docker/docker/issues/3156 上进行了广泛讨论。我可以理解,可移植性对于 docker 开发人员来说是一个最重要的问题;但作为 docker 用户,我不得不说我对这个缺失的功能感到非常失望。让我引用上述讨论中的一句话来结束我的论点:“我想使用 Gentoo 作为基础镜像,但绝对不希望在镜像生成后的任何层中有 > 1GB 的 Portage 树数据。构建。如果不是在安装过程中必须出现在映像中的巨大 portage 树,您可以拥有一些不错的紧凑容器。“是的,我可以使用 wget 或 curl 下载我需要的任何内容,但是仅出于可移植性考虑,现在我每次构建 Gentoo 基础映像时都必须下载超过 1GB 的 Portage 树,这既不高效也不友好。此外,软件包存储库总是在 /usr/portage 下,因此在 Gentoo 下总是可移植的。再次,我尊重这个决定,但同时请允许我表达我的失望。谢谢。


原问题详细说明:

来自

通过卷共享目录
http://docker.readthedocs.org/en/v0.7.3/use/working_with_volumes/

它说数据卷功能“从 Docker 远程 API 版本 1 开始可用”。我的 docker 版本是 1.2.0,但我发现上面文章中给出的示例不起作用:

# BUILD-USING:        docker build -t data .
# RUN-USING:          docker run -name DATA data
FROM          busybox
VOLUME        ["/var/volume1", "/var/volume2"]
CMD           ["/usr/bin/true"]

在 Dockerfile 中,通过 VOLUME 命令将主机挂载的卷挂载到 docker 容器中的正确方法是什么?

$ apt-cache policy lxc-docker
lxc-docker:
  Installed: 1.2.0
  Candidate: 1.2.0
  Version table:
 *** 1.2.0 0
        500 https://get.docker.io/ubuntu/ docker/main amd64 Packages
        100 /var/lib/dpkg/status

$ cat Dockerfile 
FROM          debian:sid

VOLUME        ["/export"]
RUN ls -l /export
CMD ls -l /export

$ docker build -t data .
Sending build context to Docker daemon  2.56 kB
Sending build context to Docker daemon 
Step 0 : FROM          debian:sid
 ---> 77e97a48ce6a
Step 1 : VOLUME        ["/export"]
 ---> Using cache
 ---> 59b69b65a074
Step 2 : RUN ls -l /export
 ---> Running in df43c78d74be
total 0
 ---> 9d29a6eb263f
Removing intermediate container df43c78d74be
Step 3 : CMD ls -l /export
 ---> Running in 8e4916d3e390
 ---> d6e7e1c52551
Removing intermediate container 8e4916d3e390
Successfully built d6e7e1c52551

$ docker run data
total 0

$ ls -l /export | wc 
     20     162    1131

$ docker -v
Docker version 1.2.0, build fa7b24f

【问题讨论】:

  • 显然更当前的功能请求(不是我希望它实现,但以防万一):docker/docker #14080
  • 确实有一个广泛的讨论,即不应允许在构建期间链接主机目录和容器目录,例如VOLUME ~/host_dir ~/container_dir。讨论相当广泛,有没有简单的方法来总结原因是什么?
  • 很遗憾,链接docker.readthedocs.org/en/v0.7.3/use/working_with_volumes 已损坏。不过,感谢彻底的问答。
  • 感谢@ramhiser 的通知,docker.readthedocs.org/en/v0.7.3/use/working_with_volumes 的下划线链接现已更新,可点击。

标签: share docker host mount


【解决方案1】:

不可能使用VOLUME 指令来告诉docker 什么要挂载。这将严重破坏便携性。该指令告诉 docker,这些目录中的内容不会进入图像,并且可以使用 --volumes-from 命令行参数从其他容器访问。您必须使用 -v /path/on/host:/path/in/container 运行容器才能从主机访问目录。

无法在构建期间装载主机卷。没有特权构建和安装主机也会严重降低便携性。您可能想尝试使用 wget 或 curl 下载构建所需的任何内容并将其放置到位。

【讨论】:

  • 谢谢。问题已修改。我要解决的实际问题是——如何在构建过程中将主机卷挂载到 Dockerfile 中的 docker 容器中。谢谢。
  • 不可能。请参阅修改后的答案。
  • 我可以理解对可移植性的“潜在”不良副作用,但也有一个有效的使用案例可以使用此选项。就我而言,我希望能够告诉用户“移动到目录并运行 'docker run' 命令”,并将 $(PWD) 安装到某个容器目录。 $(PWD) 确保保持可移植性。虽然这可能是一个极端情况,但在我为用户提供的脚本分发运行时环境时,它将极大地帮助我。
  • “严重破坏可移植性”...确定它可以做到这一点。但就目前而言,我无法避免将密钥放入 docker 映像中,这只是构建步骤所必需的。这不会破坏便携性。
【解决方案2】:

首先,回答“为什么VOLUME 不起作用?”在 Dockerfile 中定义VOLUME 时,只能定义目标,不能定义卷的源。在构建期间,您只会从中获得一个匿名卷。该匿名卷将在每个RUN 命令中安装,预填充映像的内容,然后在RUN 命令结束时丢弃。只保存对容器的更改,而不是对卷的更改。


自从提出这个问题以来,已经发布了一些可能会有所帮助的功能。首先是多阶段构建,允许您构建磁盘空间低效的第一阶段,并将所需的输出复制到您交付的最后阶段。第二个功能是 Buildkit,它极大地改变了图像的构建方式,并在构建中添加了新功能。

对于多阶段构建,您将有多个 FROM 行,每行都开始创建单独的图像。默认情况下仅标记最后一个图像,但您可以从以前的阶段复制文件。标准用途是拥有一个编译器环境来构建二进制或其他应用程序工件,以及一个运行时环境作为复制该工件的第二阶段。你可以:

FROM debian:sid as builder
COPY export /export
RUN compile command here >/result.bin

FROM debian:sid
COPY --from=builder /result.bin /result.bin
CMD ["/result.bin"]

这将导致构建只包含生成的二进制文件,而不是完整的 /export 目录。


Buildkit 将于 18.09 结束实验。这是对构建过程的完全重新设计,包括更改前端解析器的能力。其中一项解析器更改已实现 RUN --mount 选项,它允许您为运行命令安装缓存目录。例如。这是一个挂载一些 debian 目录的目录(通过重新配置 debian 映像,这可以加快软件包的重新安装速度):

# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/var/lib/apt/lists,type=cache \
    --mount=target=/var/cache/apt,type=cache \
    apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
      git

您可以为您拥有的任何应用程序缓存调整缓存目录,例如$HOME/.m2 用于 maven,或 /root/.cache 用于 golang。


TL;DR:答案在这里: 使用 RUN --mount 语法,您还可以从构建上下文绑定挂载只读目录。该文件夹必须存在于构建上下文中,并且不会映射回主机或构建客户端:

# syntax = docker/dockerfile:experimental
FROM debian:latest
RUN --mount=target=/export,type=bind,source=export \
    process export directory here...

请注意,由于目录是从上下文挂载的,因此它也是只读挂载的,您无法将更改推送回主机或客户端。构建时,您需要安装 18.09 或更新版本并使用 export DOCKER_BUILDKIT=1 启用 buildkit。

如果您收到不支持挂载标志的错误,则表明您没有使用上述变量启用 buildkit,或者您没有使用顶部的语法行启用实验语法Dockerfile 在任何其他行之前,包括 cmets。请注意,仅当您的 docker install 内置了 buildkit 支持时,切换 buildkit 的变量才会起作用,这需要 Docker 在客户端和服务器上提供 18.09 或更高版本。

【讨论】:

【解决方案3】:

更新:有些人不会把不作为答案,我非常喜欢它,尤其是对于这个特定的问题。

好消息,现在有办法了——

解决方案是 Rocker:https://github.com/grammarly/rocker

约翰·雅尼said, “IMO,它解决了 Dockerfile 的所有弱点,使其适合开发。”

摇杆

https://github.com/grammarly/rocker

通过引入新命令,Rocker 旨在解决以下用例,这些用例对于普通 Docker 来说是痛苦的:

  1. 在构建阶段装载可重复使用的卷,因此依赖项管理工具可以在构建之间使用缓存。
  2. 与 build 共享 ssh 密钥(用于拉取私有存储库等),而不是将它们留在生成的映像中。
  3. 在不同的镜像中构建和运行应用程序,能够轻松地将工件从一个镜像传递到另一个镜像,最好在单个 Dockerfile 中包含此逻辑。
  4. 直接从 Dockerfile 标记/推送图像。
  5. 从 shell 构建命令传递变量,以便将它们替换为 Dockerfile。

还有更多。这些是阻碍我们在 Grammarly 采用 Docker 的最关键问题。

更新:根据 Github 上的官方项目 repo,Rocker 已停产

截至 2018 年初,容器生态系统比三年前启动该项目时成熟得多。现在,rocker 的一些关键和突出的特性可以很容易地被 docker build 或其他支持良好的工具覆盖,尽管一些特性确实是 rocker 独有的。详情请见https://github.com/grammarly/rocker/issues/199

【讨论】:

  • 我正在尝试使用 Rocker 解决第 1 个问题,但是 mount 命令不起作用,并且创建的映像不包含主机文件夹。我的 Dockerfile 挂载命令看起来像这样 - MOUNT ~/code/docker-app-dev/new-editor/:/src/,而我的 Rocker 构建命令是这样的 - rocker build -f Dockerfile .。我做错了什么?
  • 也许尝试使用真实的主机路径? ~ 是一个 Bourne shell 元字符。
  • Rocker build 不允许使用docker run 命令行选项,因此目前不允许使用--privileged 之类的东西。
  • 嗨@xpt,既然摇杆现已停产,我们能否获得另一个更新
  • 现在摇杆已停产,我再次将答案恢复为“不可能”。请参阅 OP 和选定的答案。
【解决方案4】:

有一种方法可以在构建期间挂载卷,但不涉及 Dockerfile。

该技术是从您想要使用的任何基础到create a container(使用-v 选项将您的卷安装在容器中),运行一个shell 脚本来完成您的映像构建工作,然后@987654322 @ 完成后作为图像。

这不仅会删除您不想要的多余文件(这也适用于安全文件,例如 SSH 文件),而且还会创建一个图像。它有缺点:commit 命令不支持所有 Dockerfile 指令,如果您需要编辑构建脚本,它不会让您在离开时继续。

更新:

例如,

CONTAINER_ID=$(docker run -dit ubuntu:16.04)
docker cp build.sh $CONTAINER_ID:/build.sh
docker exec -t $CONTAINER_ID /bin/sh -c '/bin/sh /build.sh'
docker commit $CONTAINER_ID $REPO:$TAG
docker stop $CONTAINER_ID

【讨论】:

  • +1 您能否详细说明第二段中的说明。例如,如果 base 是 debian:wheezy,shell 脚本是 build.sh,那么具体的指令是什么?
【解决方案5】:

当您运行容器时,会在您的主机上创建一个目录并将其挂载到容器中。你可以找出这是什么目录

$ docker inspect --format "{{ .Volumes }}" <ID>
map[/export:/var/lib/docker/vfs/dir/<VOLUME ID...>]

如果你想从你的主机挂载一个目录到你的容器中,你必须使用-v参数并指定目录。在您的情况下,这将是:

docker run -v /export:/export data

因此,您将使用容器内的 hosts 文件夹。

【讨论】:

  • 谢谢。问题已修改。我要解决的实际问题是——如何在构建过程中将主机卷挂载到 Dockerfile 中的 docker 容器中。谢谢。
  • 请不要修改such a drastic way中的问题。这使我的问题无效,尽管它在您的编辑之前完全有效。考虑提出一个新问题。
  • 原问题如何在 Dockerfile 中使用 VOLUME 指令? 直到今天,它仍然处于问题的最开始。你的答案是关于run time,而我的问题一直是关于build time,这就是Dockerfile 的用途。
【解决方案6】:

这很丑,但我实现了这样的表象:

Dockerfile:

FROM foo
COPY ./m2/ /root/.m2
RUN stuff

imageBuild.sh:

docker build . -t barImage
container="$(docker run -d barImage)"
rm -rf ./m2
docker cp "$container:/root/.m2" ./m2
docker rm -f "$container"

我有一个 Java 版本,可以将 Universe 下载到 /root/.m2,并且每次都这样做imageBuild.sh 在构建后将该文件夹的内容复制到主机上,Dockerfile 将它们复制回映像中以供下一次构建。

这类似于卷的工作方式(即它在构建之间持续存在)。

【讨论】:

  • 这是基于 Docker 的持续集成(即 CI)的可行解决方案。设置库和编译器并通过 Dockerfile 命令运行 make,启动镜像只是为了创建一个容器,最后复制出所需的工件,如 .deb。似乎有效,感谢您发布此内容。
  • 此解决方案会为您留下一个包含 ./m2/ 中所有文件的图像 - 您需要的一个和您不需要的一个 - 这可能会导致大量生产图像,这是不希望的!通过挂载到外部依赖目录,只有需要的文件会被复制到镜像中。
  • 如果您打算发布图像,最好等待,让 maven 每次都重新下载自己的依赖项。仅当您正在暂存用于测试的图像时,此 hack 才有意义——最终用户永远不会接触到的图像。
【解决方案7】:

我认为您可以通过 docker 命令运行构建来完成您想做的事情,该命令本身在 docker 容器中运行。见Docker can now run within Docker | Docker Blog。例如,在探索如何Create the smallest possible Docker container | Xebia Blog 时,使用了类似的技术,但实际上是从容器访问外部 docker。

另一篇相关文章是Optimizing Docker Images | CenturyLink Labs,它解释说,如果您在构建过程中最终下载了东西,您可以通过在一个 RUN 步骤中下载、构建和删除所有下载内容来避免在最终映像中浪费空间.

【讨论】:

    【解决方案8】:

    正如许多人已经回答的那样,在构建期间安装主机卷是不可能的。我只是想添加docker-compose 方式,我认为它会很好,主要用于开发/测试用途

    Dockerfile

    FROM node:10
    WORKDIR /app
    COPY . .
    RUN npm ci
    CMD sleep 999999999
    

    docker-compose.yml

    version: '3'
    services:
      test-service:
        image: test/image
        build:
          context: .
          dockerfile: Dockerfile
        container_name: test
        volumes:
          - ./export:/app/export
          - ./build:/app/build
    

    然后通过docker-compose up -d --build 运行您的容器

    【讨论】:

      【解决方案9】:

      这是使用构建和提交的两步方法的简化版本,没有 shell 脚本。它涉及:

      1. 部分构建映像,无卷
      2. 运行带有卷的容器,进行更改,然后提交结果,替换原始映像名称。

      通过相对较小的更改,额外的步骤只会增加几秒钟的构建时间。

      基本上:

      docker build -t image-name . # your normal docker build
      
      # Now run a command in a throwaway container that uses volumes and makes changes:
      docker run -v /some:/volume --name temp-container image-name /some/post-configure/command
      
      # Replace the original image with the result:
      # (reverting CMD to whatever it was, otherwise it will be set to /some/post-configure/command)   
      docker commit --change="CMD bash" temp-container image-name 
      
      # Delete the temporary container:
      docker rm temp-container
      

      在我的用例中,我想预先生成一个 maven toolchains.xml 文件,但是我的许多 JDK 安装都在一个直到运行时才可用的卷上。我的一些图像与所有 JDKS 不兼容,因此我需要在构建时测试兼容性并有条件地填充 toolchains.xml。请注意,我不需要图像是可移植的,我不会将其发布到 Docker Hub。

      【讨论】:

        【解决方案10】:

        如果您正在寻找一种“挂载”文件的方法,例如将 -v 用于docker run,您现在可以使用--secret 标志用于docker build

        echo 'WARMACHINEROX' > mysecret.txt
        docker build --secret id=mysecret,src=mysecret.txt .
        

        在您的 Dockerfile 中,您现在可以访问此密钥

        # syntax = docker/dockerfile:1.0-experimental
        FROM alpine
        
        # shows secret from default secret location:
        RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
        
        # shows secret from custom secret location:
        RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar
        

        More in-depth information about --secret available on Docker Docs

        【讨论】:

        • 不像--secret支持文件夹
        猜你喜欢
        • 2021-01-13
        • 2017-01-01
        • 1970-01-01
        • 2021-03-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多