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


236

原始问题:如何在Dockerfile中使用VOLUME指令?

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

对我而言,其背后的原因是在Docker中构建东西时,我不希望将(apt-get install)缓存锁定在单个Docker中,而是共享/重用它们。这就是我问这个问题的主要原因。

最新更新:

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

有一种在构建期间挂载卷的方法,但是它不涉及Dockerfiles。

但是,这是一个措辞不佳,组织有序且没有得到支持的答案。当我重新安装docker contains时,我偶然发现了以下文章:

Dockerize apt-cacher-ng服务
https://docs.docker.com/engine/examples/apt-cacher-ng/

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

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

选择适合您的。


是:曾经有一个解决方案-摇杆,它不是来自Docker,但是现在摇杆已经停产了,我再次将答案恢复为“不可能”


旧更新:答案是“不可能”。我可以接受它作为答案,因为我知道问题已经在https://github.com/docker/docker/issues/3156上进行了广泛讨论。我可以理解,对于Docker开发人员而言,可移植性是至关重要的问题。但是作为docker用户,我不得不说我对该功能的缺失感到非常失望。让我在前面的讨论中引用一句话来结束我的论点:“ 我想将Gentoo用作基本图像,但是绝对不希望在构建图像后将> 1GB的Portage树数据放在任何层中。您如果不是因为巨大的可移植树不必在安装过程中出现在映像中,则可能会有一些紧凑的容器。“是的,我可以使用wget或curl来下载所需的文件,但事实是,仅出于可移植性考虑,现在每次我构建Gentoo基本映像时都迫使我下载> 1GB的Portage树,这既无效率,也不友好。此外,软件包存储库将始终位于/ usr / portage下,因此始终位于Gentoo下。再次,我尊重这一决定,但同时也请允许我对我表示失望。


详细的原始问题

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

它说数据卷功能“自Docker Remote API版本1起就已经可用”。我的泊坞窗版本为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
Jesse Glick

确实有广泛的讨论,不允许在构建过程中链接主机目录和容器目录,例如VOLUME ~/host_dir ~/container_dir。讨论非常广泛,因为有简短的方法可以总结原因是什么?
查理·帕克

Answers:


34

首先,回答“为什么不起作用VOLUME?” VOLUME在Dockerfile中定义a 时,只能定义目标,而不能定义卷的源。在构建期间,您将仅从中获得一个匿名卷。该匿名卷将在每个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或更高版本的安装,并使用启用buildkit export DOCKER_BUILDKIT=1

如果您收到不支持mount标志的错误消息,则表明您未使用上述变量启用buildkit,或者您未在Dockerfile顶部的语法行中启用实验性语法其他任何行,包括评论。请注意,仅当您的docker安装具有内置的buildkit支持时,切换buildkit的变量才起作用,该客户端和服务器上都需要Docker的版本18.09或更高版本。


2
不幸的是,版本18.09尚不支持Windows Buildkit
Wesley

1
看起来“ armhf”也不支持“ mount”。
迈克

2
我在OSX上收到“来自守护程序的错误响应:Dockerfile分析错误行xx:未知标志:安装”
ChristoKiwi

1
还没有对docker-compose的支持,但是您不需要compose来构建映像。要跟踪的问题:github.com/moby/buildkit/issues/685
BMitch,


116

这是不可能的使用VOLUME指令告诉搬运工什么安装。那会严重破坏便携性。该指令告诉docker这些目录中的内容不会出现在图像中,并且可以使用--volumes-from命令行参数从其他容器中进行访问。您必须运行-v /path/on/host:/path/in/container用于从主机访问目录的容器。

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


2
谢谢。问题修改。我要解决的实际问题是-如何在构建期间将主机卷挂载到Dockerfile中的Docker容器中。谢谢。
xpt 2014年

2
不可能。参见修改后的答案。
Andreas Steffan 2014年

3
我可以体会到可移植性带来的“潜在”不良影响,但是使用此选项也有一个有效的用例。就我而言,我希望能够告诉用户“将目录移动到目录并运行'docker run'命令”,并将$(PWD)安装到某些容器目录中。$(PWD)确保保持可移植性。尽管这可能是一个极端的情况,但是它将为我为用户提供的脚本分发运行时环境提供极大帮助。
ntwrkguru

64

更新:有人不会拒绝,我非常喜欢,特别是对于这个特定问题。

好消息,现在有办法-

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

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

摇杆

https://github.com/grammarly/rocker

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

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

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

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

截至2018年初,该容器生态系统比三年前启动该项目时成熟得多。现在,泊坞窗构建或其他良好支持的工具可以轻松涵盖Rocker的一些关键和杰出功能,尽管某些功能确实对Rocker而言是独一无二的。有关更多详细信息,请参见https://github.com/grammarly/rocker/issues/199


我正在尝试使用Rocker解决问题1,但mount命令将不起作用,并且创建的映像不包含主机文件夹。我的Dockerfile挂载命令看起来像这样- MOUNT ~/code/docker-app-dev/new-editor/:/src/而我的Rocker build命令是这个- rocker build -f Dockerfile .。我在做什么错?
亚伦·伊丹

也许尝试使用真实的主机路径?~是Bourne Shell元字符。
杰西·格里克

Rocker build不允许使用docker run命令行选项,因此当前不允许使用--privileged
蒙蒂·怀尔德

@xpt,您好,由于摇摆器现已停产,我们可以得到另一个更新
Shardj

现在不再使用摇杆,我将答案再次恢复为“不可能”。请参阅OP和所选答案。
xpt

14

有一种在构建期间挂载卷的方法,但是它不涉及Dockerfiles。

该技术是根据您要使用的基础创建一个容器(使用该-v选项在容器中装入卷),运行一个shell脚本来完成图像构建工作,然后在完成后将该容器作为一个图像提交

这不仅会删除多余的文件(这对于安全文件也非常有用,例如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

6
+1您能否在第二段中详细说明一下。例如,如果base是debian:wheezyshell脚本build.sh,而shell脚本是,则将使用什么特定的指令?
Drux

6

运行容器时,将在主机上创建一个目录并将其挂载到容器中。您可以找出它所在的目录

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

如果要从主机在容器内装入目录,则必须使用-v参数并指定目录。在您的情况下,这将是:

docker run -v /export:/export data

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


1
谢谢。问题修改。我要解决的实际问题是-如何在构建期间将主机卷挂载到Dockerfile中的Docker容器中。谢谢。
xpt 2014年

请不要以如此激烈的方式修改您的问题。这使我的问题无效,尽管在您进行编辑之前它是完全有效的。考虑改问一个新问题。
Behe 2015年

11
原始问题如何在Dockerfile中使用VOLUME指令?即使到今天,它仍然是问题的开始。您的答案是关于运行时的,而我的问题一直是关于build time的,这就是Dockerfile的用途。
xpt 2015年

4

我认为您可以通过本身在docker容器中运行的docker命令运行构建来完成您想做的事情。查看Docker现在可以在Docker中运行 Docker博客。例如,在探索如何创建尽可能小的Docker容器时,使用了类似的技术,但实际上是通过容器访问外部docker的。Xebia博客

另一篇相关文章是《优化Docker映像》。CenturyLink Labs解释说,如果您最终在构建过程中下载了内容,则可以在一个RUN步骤中下载,构建和删除所有下载内容,从而避免在最终映像中浪费空间。


3

这很丑陋,但我实现了类似的效果:

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)。似乎可以正常工作,谢谢发布。
chrisinmtown

此解决方案使您的映像具有./m2/中的所有文件-一个您需要的文件,一个您不需要的文件-这会导致产生大量的生产映像,这是不希望的!通过挂载到外部依赖关系目录,仅将所需文件复制到映像。
Marko Krajnc

如果您打算发布图像,那么最好等待一下,让maven每次重新下载自己的依赖项。仅当您要分阶段测试图像时,此黑客才有意义-最终用户将永远不会与之接触的图像。
MatrixManAtYrService

1

这是使用构建和提交而不使用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。


1

正如许多人已经回答的那样,无法在构建过程中挂载主机卷。我只是想补充docker-compose一点,我认为它会很不错,主要用于开发/测试用途

Docker文件

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.