如何包含Docker构建上下文之外的文件?

如何使用Docker文件中的“ADD”命令包含来自Docker构建上下文之外的文件?

从Docker留档:

路径必须在构建的上下文中;您不能添加…/某事/某事,因为Docker构建的第一步是将上下文目录(和子目录)发送到docker守护程序。

我不想仅仅为了适应Docker而重组我的整个项目。我想将我所有的Docker文件保存在同一个子目录中。

此外,Docker似乎还没有(也可能永远不会)支持符号链接:Dockerfile ADD命令不遵循主机#1676上的符号链接。

我能想到的唯一一件事就是包含一个预构建步骤,将文件复制到Docker构建上下文中(并将我的版本控制配置为忽略这些文件)。

454991 次浏览

如果你读了问题2745中的讨论,不仅docker可能永远不支持符号链接,他们可能永远不支持在你的上下文之外添加文件。似乎是一种设计理念,即进入docker构建的文件应该明确地成为其上下文的一部分,或者来自一个URL,在那里它可能也部署了一个固定版本,这样构建就可以与docker容器附带的众所周知的URL或文件重复。

我更喜欢从版本控制的源代码构建-即docker构建-t的东西http://my.git.org/repo-否则我从一些随机的地方随机文件构建。

从根本上说,不……—SvenDowideit,Docker Inc

只是我的观点,但我认为你应该重组以分离代码和docker存储库。这样容器就可以是通用的,并在运行时而不是构建时拉入任何版本的代码。

或者,使用docker作为您的基本代码部署工件,然后将dockerfile放在代码存储库的根目录中。如果您走这条路线,可能有一个父docker容器用于更通用的系统级详细信息,并有一个子容器用于特定于您的代码的设置。

在Linux您可以挂载其他目录而不是符号链接它们

mount --bind olddir newdir

查看https://superuser.com/questions/842642了解更多详情。

我不知道其他操作系统是否也有类似的功能。我还尝试使用Samba共享一个文件夹并将其重新加载到Docker上下文中,这也有效。

解决这个问题的最佳方法是使用-f独立于构建上下文指定Dockerfile。

例如,此命令将允许ADD命令访问当前目录中的任何内容。

docker build -f docker-files/Dockerfile .

更新:Docker现在允许在构建上下文之外拥有Dockerfile(在18.03.0-ce中修复)。所以你也可以做类似的事情

docker build -f ../Dockerfile .

您还可以创建图像首先需要的tarball,并将其用作您的上下文。

https://docs.docker.com/engine/reference/commandline/build/#/tarball-contexts

我相信更简单的解决方法是改变“上下文”本身。

所以,例如,而不是给予:

docker build -t hello-demo-app .

它将当前目录设置为上下文,假设您想要父目录作为上下文,只需使用:

docker build -t hello-demo-app ..

我经常发现自己为此目的使用--build-arg选项。例如,在将以下内容放入Dockerfile后:

ARG SSH_KEYRUN echo "$SSH_KEY" > /root/.ssh/id_rsa

你可以只做:

docker build -t some-app --build-arg SSH_KEY="$(cat ~/file/outside/build/context/id_rsa)" .

但请注意Docker留档中的以下警告:

警告:不建议使用构建时变量来传递github密钥、用户凭据等机密。使用docker历史命令的图像的任何用户都可以看到构建时变量值。

我花了很多时间试图找出一个好的模式,以及如何更好地解释这个功能支持的情况。我意识到解释它的最佳方式如下…

  • Dockerfile:只会看到自己相对路径下的文件
  • 上下文:“空间”中的一个位置,您要共享的文件和您的Dockerfile将被复制到该位置

所以,话虽如此,这是一个需要重用名为start.sh的文件的Dockerfile示例

dockerfile

它将总是从其相对路径加载,将自身的当前目录作为对您指定的路径的当地引用。

COPY start.sh /runtime/start.sh

文件

考虑到这个想法,我们可以考虑为Dockerfile构建多个副本,但它们都需要访问start.sh

./all-services//start.sh/service-X/Dockerfile/service-Y/Dockerfile/service-Z/Dockerfile./docker-compose.yaml

考虑到这种结构和上面的文件,这里有一个docker-compose.yml

docker-compose.yaml

  • 在此示例中,共享上下文目录是运行时目录。
    • 这里的心理模型相同,认为该目录下的所有文件都移动到所谓的背景
    • 同样,只需指定要复制到同一目录的Dockerfile。您可以使用dockerfile指定。
  • 您的主要内容所在的目录是要设置的实际上下文。

docker-compose.yml如下

version: "3.3"services:  
service-Abuild:context: ./all-servicedockerfile: ./service-A/Dockerfile
service-Bbuild:context: ./all-servicedockerfile: ./service-B/Dockerfile
service-Cbuild:context: ./all-servicedockerfile: ./service-C/Dockerfile
  • all-service被设置为上下文,共享文件start.sh以及每个dockerfile指定的Dockerfile被复制到那里。
  • 每个都可以以自己的方式构建,共享开始文件!

我在一个项目和一些数据文件中遇到了同样的问题,由于HIPAA原因,我无法在repo上下文中移动这些文件。我最终使用了2个Dockerfile。一个构建主应用程序,而没有我在容器之外需要的东西,并将其发布到内部存储库。然后第二个dockerfile拉取该映像并添加数据并创建一个新映像,然后部署该映像,永远不会存储在任何地方。不理想,但它适用于我将敏感信息排除在存储库之外的目的。

使用docker-compose,我通过创建一个挂载我需要的卷并提交容器映像的服务来实现这一点。然后,在后续服务中,我依赖于之前提交的映像,它将所有数据存储在挂载位置。然后,您必须将这些文件复制到它们的最终目的地,因为运行docker commit命令时主机挂载的目录不会提交

你不必使用docker-compose来完成这个,但它让生活变得更容易

# docker-compose.yml
version: '3'services:stage:image: alpinevolumes:- /host/machine/path:/tmp/container/pathcommand: bash -c "cp -r /tmp/container/path /final/container/path"setup:image: stage
# setup.sh
# Start "stage" servicedocker-compose up stage
# Commit changes to an image named "stage"docker commit $(docker-compose ps -q stage) stage
# Start setup service off of stage imagedocker-compose up setup

一个简单的解决方法可能是在运行卷并以这种方式访问文件时简单地将卷(使用-v或--Mount标志)挂载到容器中。

例子:

docker run -v /path/to/file/on/host:/desired/path/to/file/in/container/ image_name

更多请见:https://docs.docker.com/storage/volumes/

一种快速而肮脏的方法是根据需要将构建上下文设置为任意级别-但这可能会产生后果。如果你在微服务架构中工作,看起来像这样:

./Code/Repo1./Code/Repo2...

您可以将构建上下文设置为父目录Code,然后访问所有内容,但事实证明,对于大量存储库,这可能会导致构建花费很长时间。

一个例子是,另一个团队在Repo1中维护了一个数据库模式,而你的团队在Repo2中的代码依赖于此。你想用自己的一些种子数据来Dockerise这种依赖关系,而不用担心模式更改或污染其他团队的存储库(当然,这取决于你可能仍然需要更改你的种子数据脚本)第二种方法是hacky,但绕过了长构建的问题:

./Code/Repo2中创建sh(或ps1)脚本来复制您需要的文件并调用您想要的docker命令,例如:

#!/bin/bashrm -r ./db/schemamkdir ./db/schema
cp  -r ../Repo1/db/schema ./db/schema
docker-compose -f docker-compose.yml downdocker container prune -fdocker-compose -f docker-compose.yml up --build

在docker-compose文件中,只需将上下文设置为Repo2根,并在dockerfile中使用./db/schema目录的内容,而不必担心路径。请记住,您将冒着不小心将此目录提交到源代码控制的风险,但脚本清理操作应该很容易。

在我的例子中,我的Dockerfile就像一个包含占位符的模板,我正在使用我的配置文件将其替换为实值。

所以我不能直接指定这个文件,而是像这样将它管道到docker构建中:

sed "s/%email_address%/$EMAIL_ADDRESS/;" ./Dockerfile | docker build -t katzda/bookings:latest . -f -;

但是由于管道的原因,COPY命令不起作用。但是上面的方法通过-f -解决了它(明确表示未提供文件)。只做-而没有-f标志,不提供上下文和Dockerfile,这是一个警告。

这个中所述,构建实际上发生在/tmp/docker-12345中,因此像../relative-add/some-file这样的相对路径相对于/tmp/docker-12345。因此,它将搜索/tmp/relative-add/some-file,这也显示在错误消息中。*

不允许包含来自构建目录之外的文件,因此这会导致“禁止路径”消息。”

使用链接的解决方法:

ln path/to/file/outside/context/file_to_copy ./file_to_copy

在Dockerfile上,简单地说:

COPY file_to_copy /path/to/file

创建一个包装docker构建外壳脚本,该脚本获取文件,然后调用docker build,然后删除文件。

一个简单的解决方案没有在我的快速浏览中提到:

  • 有一个名为docker_build.sh的包装脚本
  • 让它创建tarball,将大文件复制到当前工作目录
  • 调用docker build
  • 清理tarball、大文件等

这个解决方案很好,因为(1.)它没有复制SSH私钥的安全漏洞(2.)另一个解决方案使用sudo bind,所以那里有另一个安全漏洞,因为它需要root权限才能执行bind

如何在两个Dockerfile之间共享打字稿代码

我也遇到过同样的问题,但是在两个打字稿项目之间共享文件。其他一些答案对我不起作用,因为我需要保留共享代码之间的相对导入路径。我通过像这样组织代码来解决它:

api/Dockerfilesrc/models/index.ts
frontend/Dockerfilesrc/models/index.ts
shared/model1.tsmodel2.tsindex.ts
.dockerignore

说明:将共享代码提取到顶部文件夹后,我避免了更新导入路径,因为我更新了api/models/index.tsfrontend/models/index.ts以从共享导出:(例如export * from '../../../shared

由于构建上下文现在高出一个目录,我不得不做一些额外的更改:

  1. 更新构建命令以使用新上下文:

    docker build -f Dockerfile ..(两个点而不是一个)

  2. 顶级处使用单个.dockerignore来排除所有node_modules。(例如**/node_modules/**

  3. DockerfileCOPY命令前加上api/frontend/

  4. 复制shared(除了api/srcfrontend/src

    WORKDIR /usr/src/app
    COPY api/package*.json ./     <---- Prefix with api/RUN npm ci
    COPY api/src api/ts*.json ./  <---- Prefix with api/COPY shared usr/src/shared    <---- ADDEDRUN npm run build

这是我可以将所有内容发送到docker的最简单方法,同时保留两个项目中的相对导入路径。棘手(烦人)的部分是构建上下文在一个目录中引起的所有更改/后果。

我个人对一些答案感到困惑,所以决定简单地解释一下。

您应该将背景,您在Dockerfile中指定的,传递给docker想要创建图像。

我总是选择项目的根作为Dockerfile中的背景

例如,如果您使用复制命令,例如复制…

第一个点(.)是上下文,第二个点(.)是容器工作目录

假设背景是项目根,点(.),代码结构如下

sample-project/docker/Dockerfile

如果你想构建镜像

并且您的路径(您运行docker build命令的路径)是/full-path/sample-project/,你应该这样做

docker build -f docker/Dockerfile .

如果你的路径是/full-path/sample-project/docker/,你应该这样做

docker build -f Dockerfile ../

此行为由dockerpodman用于将文件呈现给构建过程的上下文目录给出。
这里的一个很好的技巧是在构建指令期间将上下文目录更改为您要向守护程序公开的目录的完整路径。例如:

docker build -t imageName:tag -f /path/to/the/Dockerfile /mysrc/path

使用/mysrc/path而不是.(当前目录),您将使用该目录作为上下文,因此构建过程可以看到它下的任何文件。
在这个例子中,您将把整个/mysrc/path树暴露给docker守护进程。
当使用docker时,触发构建的用户ID必须具有递归读取权限到上下文目录中的任何单个目录或文件。

这在您拥有/home/user/myCoolProject/Dockerfile但希望将不在同一目录中的文件带到此容器构建上下文的情况下很有用。

这是一个使用上下文目录构建的示例,但这次使用podman而不是docker

让我们以Dockerfile为例,在您的Dockerfile中包含COPYADD指令,该指令正在从项目外部的目录复制文件,例如:

FROM myImage:tag......COPY /opt/externalFile ./ADD /home/user/AnotherProject/anotherExternalFile ./...

为了构建这个,使用位于/home/user/myCoolProject/Dockerfile中的容器文件,只需执行以下操作:

cd /home/user/myCoolProjectpodman build -t imageName:tag -f Dockefile /

一些已知的更改上下文目录的用例是当使用容器作为工具链来构建您的源代码时。
例如:

podman build --platform linux/s390x -t myimage:mytag -f ./Dockerfile /tmp/mysrc

或者它可以是相对路径,例如:

podman build --platform linux/s390x -t myimage:mytag -f ./Dockerfile ../../

另一个使用全局路径的示例:

FROM myImage:tag......COPY externalFile ./ADD  AnotherProject ./...

请注意,现在Dockerfile命令层中省略了#0和#1的完整全局路径。
在这种情况下,contex dir必须相对于文件所在的位置,如果externalFileAnotherProject都在/opt目录中,那么用于构建它的context dir必须是:

podman build -t imageName:tag -f ./Dockerfile /opt

在#2中使用#0或#1的上下文目录时请注意
docker守护程序将尝试将上下文目录树上可见的所有文件“流式传输”到守护程序,这可能会减慢构建速度。并要求用户具有上下文目录的递归权限。这种行为可能会更昂贵,特别是在通过API使用构建时。然而,对于podman,构建是即时发生的,不需要递归权限,这是因为podman没有枚举整个上下文目录,也没有使用client/server架构。
当您使用不同的上下文目录面对此类问题时,使用podman而不是docker的构建可能会更有趣。

部分参考:

我认为从今年早些时候开始,Buildx中添加了一个功能来做到这一点。

如果你有dockerfile 1.4+和Buildx 0.8+,你可以这样做

docker buildx build --build-context othersource= ../something/something .

然后在您的docker文件中您可以使用from命令添加上下文

ADD –from=othersource . /stuff

相关文章https://www.docker.com/blog/dockerfiles-now-support-multiple-build-contexts/