自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

当前回答

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

其他回答

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

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

如果要在容器中挂载来自主机的目录,则必须使用-v参数并指定目录。在你的情况下,这将是:

docker run -v /export:/export data

你可以在容器中使用hosts文件夹。

Git克隆从一个私人回购在docker使用我的私人SSH密钥

在使用BuildKit创建Linux映像时,docker build还使用——SSH标志提供SSH代理转发。Docker在build中使用SSH访问私有数据,Dockerfile可以在RUN命令中使用——mount=type= SSH,将SSH身份验证委托给主机的SSH代理:

# syntax=docker/dockerfile:1
FROM alpine
RUN apk add --no-cache openssh-client git

# Download public key of remote server at github.com 
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts

# Enable verbose output (optional)
ENV GIT_SSH_COMMAND='ssh -Tvv'

# Clone using private keys known to SSH agent on the host
RUN --mount=type=ssh git clone git@github.com:myorg/myproject.git myproject

在构建时,上面的程序基本上可以通过简单地运行来使用主机的SSH密钥:

docker build --ssh default .

在主机上,这需要使用ssh-add进行一些配置,如上面链接的文档中所述;如果一切正常,那么echo $SSH_AGENT_SOCK或echo $SSH_AUTH_SOCK中的一个应该会给你一些输出,ssh-add -L应该会显示上面可用的标识。

使用详细日志记录时可能出现的一些错误:

主机密钥验证失败:您没有将远程主机添加到映像的~/.ssh/known_hosts pubkey_prepare: ssh_get_authentication_socket:没有这样的文件或目录:你忘记在docker build中包含——ssh默认参数 pubkey_prepare: ssh_get_authentication_socket: Permission denied: are you using some USER <username>?然后使用——mount=type=ssh,uid=<uid>指定它的用户id,或者使用——mount=type=ssh,mode=0666向所有用户打开套接字

这也适用于PIP/Conda/Mamba直接从版本控制安装Python依赖项,如git+ssh://git@github.com/myorg/myproject.git@mybranch#egg=myproject:

RUN --mount=type=ssh mamba env create -n myenv --file conda_environment.yml

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

由于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"

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

好消息,现在有办法了

解决方案是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。