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

从Docker文档中:

路径必须位于生成的上下文中;您不能添加../something/something,因为docker构建的第一步是将上下文目录(和子目录)发送到docker守护进程。

我不想重组我的整个项目,只是为了在这件事上适应Docker。我想将所有Docker文件保存在同一个子目录中。

此外,Docker似乎还不支持符号链接:Dockerfile ADD命令不支持主机#1676上的符号链接。

我唯一能想到的另一件事是包含一个预构建步骤,将文件复制到Docker构建上下文中(并配置我的版本控制以忽略这些文件)。还有比这更好的解决方法吗?


当前回答

一种快速而肮脏的方法是将构建上下文设置为所需的多个级别,但这可能会产生后果。如果您使用的是这样的微服务架构:

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

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

示例情况可能是另一个团队在Repo1中维护数据库模式,而您的团队在Repo2中的代码依赖于此。您希望在不担心模式更改或污染其他团队的存储库的情况下,将这种依赖关系与您自己的种子数据固定在一起(当然,根据更改的内容,您可能仍然需要更改种子数据脚本)第二种方法很粗糙,但避开了长构建的问题:

在中创建sh(或ps1)脚本/Code/Rep2复制所需文件并调用所需的docker命令,例如:

#!/bin/bash
rm -r ./db/schema
mkdir ./db/schema

cp  -r ../Repo1/db/schema ./db/schema

docker-compose -f docker-compose.yml down
docker container prune -f
docker-compose -f docker-compose.yml up --build

在docker compose文件中,只需将上下文设置为Repo2根目录并使用的内容/dockerfile中的db/schema目录,而不必担心路径。请记住,您可能会意外地将此目录提交给源代码管理,但脚本清理操作应该足够简单。

其他回答

如何在两个Dockerfile之间共享typescript代码

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

api/
  Dockerfile
  src/
    models/
      index.ts

frontend/
  Dockerfile
  src/
    models/
      index.ts

shared/
  model1.ts
  model2.ts
  index.ts

.dockerignore

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

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

更新build命令以使用新上下文:docker build-f Dockerfile。。(两个点而不是一个)在顶层使用一个.dockerignore来排除所有node_module。(例如**/node_modules/**)在Dockerfile COPY命令前面加上api/或frontend/复制共享(除了api/src或frontend/src)工作目录/usr/src/appCOPY api/package*.json./<----前缀为api/运行npm ciCOPY api/src api/ts*.json./<----前缀为api/复制共享usr/src/shared<----添加RUN npm运行构建

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

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

例子:

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

有关详细信息,请参阅:https://docs.docker.com/storage/volumes/

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

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

因此,例如,不要给出:

docker build -t hello-demo-app .

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

docker build -t hello-demo-app ..

该行为由docker或podman用于向构建过程呈现文件的上下文目录提供。这里有一个很好的技巧,就是在构建指令期间将上下文dir更改为要向守护进程公开的目录的完整路径。例如:

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

使用/myrc/path代替。(当前目录),您将使用该目录作为上下文,因此构建过程可以看到该目录下的任何文件。在这个示例中,您将向docker守护进程公开整个/myrc/path树。当使用docker时,触发构建的用户ID必须具有从上下文目录递归读取任何单个目录或文件的权限。

如果您有/home/user/myCoolProject/Dockerfile,但希望将不在同一目录中的文件带到此容器构建上下文中,这可能非常有用。

这里有一个使用上下文dir构建的示例,但这次使用podman而不是docker。

让我们以Dockerfile为例,在Dockerfile中有一个COPY或ADD指令,它从项目外部的目录中复制文件,例如:

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

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

cd /home/user/myCoolProject
podman 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命令层中省略了COPY和ADD的完整全局路径。在这种情况下,context目录必须相对于文件所在的位置,如果externalFile和AnotherProject都在/opt目录中,则用于构建它的context目录应为:

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

在docker中将COPY或ADD与上下文目录一起使用时请注意:docker守护程序将尝试将上下文目录树上可见的所有文件“流式传输”到守护程序,这可能会降低构建速度。并要求用户具有上下文目录的递归权限。特别是在通过API使用构建时,这种行为的成本可能会更高。然而,使用podman,构建是即时进行的,不需要递归权限,这是因为podman不会枚举整个上下文目录,也不会使用客户端/服务器架构。当您使用不同的上下文目录来面对此类问题时,使用podman而不是docker来构建此类案例可能会更有趣。

一些参考文献:

https://docs.docker.com/engine/reference/commandline/build/https://docs.podman.io/en/latest/markdown/podman-build.1.html