自 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
VOLUME ~/host_dir ~/container_dir
。讨论相当广泛,si 有一个简短的方法来总结是什么原因?
无法使用 VOLUME
指令告诉 docker what 挂载。这将严重破坏便携性。该指令告诉 docker 这些目录中的内容不会进入图像,并且可以使用 --volumes-from
命令行参数从其他容器访问。您必须使用 -v /path/on/host:/path/in/container
运行容器才能从主机访问目录。
无法在构建期间挂载主机卷。没有特权构建和安装主机也会严重降低便携性。您可能想尝试使用 wget 或 curl 下载构建所需的任何内容并将其放置到位。
首先,回答“为什么 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 或更高版本,无论是在客户端还是服务器上。
更新:有人不会把不作为答案,我非常喜欢它,尤其是对于这个特定的问题。
好消息,现在有办法——
解决方案是 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。
MOUNT ~/code/docker-app-dev/new-editor/:/src/
,而我的 Rocker 构建命令是这样 - rocker build -f Dockerfile .
。我做错了什么?
~
是 Bourne shell 元字符。
Rocker build
不允许 docker run
命令行选项,因此目前不允许 --privileged
之类的东西。
有一种方法可以在构建期间挂载卷,但它不涉及 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
debian:wheezy
,shell 脚本是 build.sh
,那么会使用哪些具体指令?
当您运行容器时,会在您的主机上创建一个目录并将其挂载到容器中。你可以找出这是什么目录
$ docker inspect --format "{{ .Volumes }}" <ID>
map[/export:/var/lib/docker/vfs/dir/<VOLUME ID...>]
如果要在容器内从主机挂载目录,则必须使用 -v
参数并指定目录。在您的情况下,这将是:
docker run -v /export:/export data
因此,您将使用容器内的 hosts 文件夹。
这很丑陋,但我实现了这样的表象:
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 命令运行构建来做您想做的事情,该命令本身在 docker 容器中运行。请参阅Docker can now run within Docker | Docker Blog。例如,在探索如何 Create the smallest possible Docker container | Xebia Blog 时,使用了类似的技术,但实际上是从容器访问外部 docker。
另一篇相关文章是 Optimizing Docker Images | CenturyLink Labs,它解释说,如果您最终在构建期间下载了一些东西,您可以通过在一个 RUN 步骤中下载、构建和删除所有下载内容来避免在最终映像中浪费空间。
正如许多人已经回答的那样,在构建期间安装主机卷是不可能的。我只是想添加 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
运行您的容器
这是使用构建和提交的两步方法的简化版本,没有 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。
如果您正在寻找一种“挂载”文件的方法,例如将 -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
似乎不支持文件夹