ChatGPT解决这个技术问题 Extra ChatGPT

如何在构建期间将主机卷挂载到 Dockerfile 中的 docker 容器中

自 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。讨论相当广泛,si 有一个简短的方法来总结是什么原因?
不幸的是,链接 docker.readthedocs.org/en/v0.7.3/use/working_with_volumes 已损坏。不过,感谢彻底的问答。
感谢@ramhiser 的通知,指向 docker.readthedocs.org/en/v0.7.3/use/working_with_volumes 的下划线链接现已更新,可点击。

A
Andreas Steffan

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

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


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

首先,回答“为什么 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 顶部的语法行启用实验性语法任何其他行,包括注释。请注意,仅当您的 docker install 内置了 buildkit 支持时,切换 buildkit 的变量才有效,这需要 Docker 的 18.09 或更高版本,无论是在客户端还是服务器上。


不幸的是,18.09 版尚不支持 Windows Buildkit
看起来“armhf”也不支持“mount”。
我在 OSX 上收到“来自守护程序的错误响应:Dockerfile 解析错误行 xx:未知标志:挂载”
尚不支持 docker-compose,但您不需要 compose 来构建图像。要跟踪的问题:github.com/moby/buildkit/issues/685
m
mahemoff

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

好消息,现在有办法——

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

John Yani said“IMO,它解决了 Dockerfile 的所有弱点,使其适合开发。”

摇杆

https://github.com/grammarly/rocker

通过引入新命令,Rocker 旨在解决以下用例,这些用例对于普通 Docker 来说是很痛苦的: 在构建阶段挂载可重用卷,因此依赖管理工具可以在构建之间使用缓存。与 build 共享 ssh 密钥(用于拉取私有存储库等),同时不要将它们留在生成的映像中。在不同的镜像中构建和运行应用程序,能够轻松地将工件从一个镜像传递到另一个镜像,理想情况下在单个 Dockerfile 中具有此逻辑。直接从 Dockerfiles 标记/推送图像。从 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 和选定的答案。
K
Keith Mason

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

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

这不仅会删除您不想要的多余文件(这对安全文件也有好处,例如 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,那么会使用哪些具体指令?
B
Behe

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

$ 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 指令?即使到今天,它仍然处于问题的最开始。你的答案是关于运行时间的,而我的问题一直是关于构建时间的,这就是 Dockerfile 的用途。
M
MatrixManAtYrService

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

Dockerfile:

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

图像构建.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 才有意义——最终用户永远不会接触到的图像。
n
nealmcb

我认为您可以通过 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 步骤中下载、构建和删除所有下载内容来避免在最终映像中浪费空间。


Y
Yegor Zaremba

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

Dockerfile

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

码头工人-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 运行您的容器


A
Akom

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

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

通过相对较小的更改,附加步骤仅增加了几秒钟的构建时间。

基本上:

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。


o
orby

如果您正在寻找一种“挂载”文件的方法,例如将 -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 似乎不支持文件夹