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

我想解决的问题是——如何在构建过程中将主机卷挂载到Dockerfile中的docker容器中,即在docker构建过程中让docker运行-v /export:/export功能。

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

这就是我问这个问题的主要原因。我今天面临的另一个原因是试图利用来自主机的巨大私有回购,否则我必须使用我的私有ssh密钥从docker中的私有回购中做git克隆,我不知道如何,也没有研究过。

最新更新:

@BMitch回答中的Buildkit

使用RUN——mount语法,你还可以从build-context…

它现在已经内置在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。

否则,你会得到:

——mount选项需要BuildKit。参考https://docs.docker.com/go/buildkit/了解如何在启用BuildKit的情况下构建图像

因此,它将是我上面解释的第二个用例的完美解决方案。

截至2019年5月7日更新:

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

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

然而,这是一个陈述不当、组织不当和支持不足的回答。当我重新安装我的docker contains时,我碰巧发现了以下文章:

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

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

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

挑一个适合你的。


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


Old Update: So the answer is "Not possible". I can accept it as an answer as I know the issue has been extensively discussed at https://github.com/docker/docker/issues/3156. I can understand that portability is a paramount issue for docker developer; but as a docker user, I have to say I'm very disappointed about this missing feature. Let me close my argument with a quote from aforementioned discussion: "I would like to use Gentoo as a base image but definitely don't want > 1GB of Portage tree data to be in any of the layers once the image has been built. You could have some nice a compact containers if it wasn't for the gigantic portage tree having to appear in the image during the install." Yes, I can use wget or curl to download whatever I need, but the fact that merely a portability consideration is now forcing me to download > 1GB of Portage tree each time I build a Gentoo base image is neither efficient nor user friendly. Further more, the package repository WILL ALWAYS be under /usr/portage, thus ALWAYS PORTABLE under Gentoo. Again, I respect the decision, but please allow me expressing my disappointment as well in the mean time. Thanks.


原问题详情:

From

通过卷共享目录 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

当前回答

由于Windows容器仍然不支持BuildKit,一种替代方法是使用网络共享。这将创建一个最小的Docker映像大小,因为安装程序是在单个RUN语句期间从容器中添加/删除的。

将EXE / MSI文件放在主机上的一个文件夹中 共享文件夹 开始构建Docker 从Docker容器内映射共享 复制+运行+删除每个安装程序 移除容器内的映射 Docker构建 取消主机上的共享文件夹

下面是一个创建Java 11 Windows容器的工作示例:

build.cmd:

host=%COMPUTERNAME%
set domainAndUser=whoami
set "pswd=<host-password>"
set "shareName=tmpSmbShare"
set netPath=\\%COMPUTERNAME%\%shareName%
set "localPath=C:/Users/tester/Desktop/docker/Java11/winsrvcore/installers"

powershell New-SmbShare -Name %shareName% -Path %localPath% -FullAccess "Everyone"
docker build ^
--build-arg domainAndUser=%domainAndUser% ^
--build-arg pswd=%pswd% ^
--build-arg netPath=%netPath% ^
-t <docker-username>/java-11.0.16-winsrvcore.ltsc2019:1.0 .
powershell Remove-SmbShare -Name %shareName% -Force

Dockerfile:

FROM mcr.microsoft.com/windows/servercore:ltsc2019-amd64

ARG domainAndUser
ARG pswd
ARG netPath

RUN powershell New-SMBMapping -LocalPath "R:" -RemotePath "$env:netPath" -UserName "$env:domainAndUser" -Password "$env:pswd" \
    && dir "R:/" \
    && copy "R:/jdk-11.0.16_windows-x64_bin.exe" "C:/" \
    && powershell Start-Process -filepath 'C:/jdk-11.0.16_windows-x64_bin.exe' -Wait -PassThru -ArgumentList "/s,/L,install64.log" \
    && powershell Remove-SMBMapping -LocalPath "R:" -Force \
    && del "C:/jdk-11.0.16_windows-x64_bin.exe" 

ENV JAVA_HOME "C:\Program Files\Java\jdk-11.0.16"

其他回答

下面是使用构建和提交的两步方法的简化版本,没有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

更多关于——secret的深入信息可以在Docker Docs上找到

首先,要回答“为什么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/。缓存golang。


答案在这里:使用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安装有内置的buildkit支持时,切换buildkit的变量才有效,这需要docker的18.09或更新版本,无论是在客户端还是服务器端。

更新:有人就是不会把“不”作为答案,我很喜欢,尤其是这个特别的问题。

好消息,现在有办法了

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

约翰·亚尼说, “在我看来,它解决了Dockerfile的所有弱点,使其适合开发。”

摇滚歌手

https://github.com/grammarly/rocker

By introducing new commands, Rocker aims to solve the following use cases, which are painful with plain Docker: Mount reusable volumes on build stage, so dependency management tools may use cache between builds. Share ssh keys with build (for pulling private repos, etc.), while not leaving them in the resulting image. Build and run application in different images, be able to easily pass an artifact from one image to another, ideally have this logic in a single Dockerfile. Tag/Push images right from Dockerfiles. Pass variables from shell build command so they can be substituted to a Dockerfile. And more. These are the most critical issues that were blocking our adoption of Docker at Grammarly.

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

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

有一种方法可以在构建期间挂载卷,但它不涉及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