提问人:ben_frankly 提问时间:11/22/2014 最后编辑:tshepangben_frankly 更新时间:11/15/2023 访问量:525229
如何在 Docker 的构建上下文之外包含文件?
How to include files outside of Docker's build context?
问:
如何使用 Docker 文件中的“ADD”命令包含来自 Docker 构建上下文之外的文件?
在 Docker 文档中:
路径必须位于构建的上下文中;你不能 ADD ../something/something,因为 docker 构建的第一步是 将上下文目录(和子目录)发送到 Docker 守护程序。
我不想仅仅为了适应Docker而重组我的整个项目。我想将我所有的Docker文件保存在同一个子目录中。
此外,Docker 似乎还没有(也可能永远不会)支持符号链接:Dockerfile ADD 命令不遵循主机 #1676 上的符号链接。
我唯一能想到的另一件事是包含一个预构建步骤,将文件复制到 Docker 构建上下文中(并将我的版本控制配置为忽略这些文件)。有比这更好的解决方法吗?
答:
如果您阅读了问题 2745 中的讨论,不仅 docker 可能永远不支持符号链接,而且可能永远不支持在您的上下文之外添加文件。似乎是一种设计理念,即进入 docker build 的文件应该明确地成为其上下文的一部分,或者来自一个 URL,它可能也部署了一个固定版本,以便构建可以使用众所周知的 URL 或 docker 容器附带的文件重复。
我更喜欢从版本控制的源代码构建 - 即docker build -t stuff 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 .
评论
dockerfile:
build:
ADD
-f
build: context: .., dockerfile: dir/Dockerfile
sending build context to Docker deamon
您还可以创建图像首先需要的压缩包,并将其用作上下文。
https://docs.docker.com/engine/reference/commandline/build/#/tarball-contexts
评论
tar zc /dir1 /dir2 |docker build -
我相信更简单的解决方法是更改“上下文”本身。
因此,例如,与其给予:
docker build -t hello-demo-app .
它将当前目录设置为上下文,假设您希望父目录作为上下文,只需使用:
docker build -t hello-demo-app ..
评论
make build
.dockerignore
必须位于上下文的根目录中。如果将上下文从 更改为 ,则必须将文件向上移动一个目录。.
..
.dockerignore
我经常发现自己将该选项用于此目的。例如,在将以下内容放入 Dockerfile 后:--build-arg
ARG SSH_KEY
RUN 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 history 命令,映像的任何用户都可以看到构建时变量值。
评论
docker build
我花了很长时间试图找出一个好的模式,以及如何更好地解释此功能支持发生了什么。我意识到最好的解释方式如下......
- Dockerfile:只能看到其自身相对路径下的文件
- 上下文:在“空间”中要共享的文件和 Dockerfile 将被复制到的位置
因此,话虽如此,这里有一个 Dockerfile 示例,它需要重用一个名为start.sh
Docker文件
它将始终从其相对路径加载,并将自身的当前目录作为对指定路径的本地引用。
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-A
build:
context: ./all-service
dockerfile: ./service-A/Dockerfile
service-B
build:
context: ./all-service
dockerfile: ./service-B/Dockerfile
service-C
build:
context: ./all-service
dockerfile: ./service-C/Dockerfile
all-service
设置为上下文,则共享文件以及每个 .start.sh
dockerfile
- 每个人都可以以自己的方式构建,共享启动文件!
评论
a/b/c
docker build .
c
../file-in-b
a
docker build -f a/b/c/Dockerfile .
.
a
我有一个项目和一些数据文件遇到了同样的问题,由于 HIPAA 原因,我无法在存储库上下文中移动。我最终使用了 2 个 Dockerfile。一个在没有容器外部需要的东西的情况下构建主应用程序,并将其发布到内部存储库。然后,第二个 dockerfile 拉取该映像并添加数据并创建一个新映像,然后部署该映像,并且永远不会存储在任何地方。不理想,但它适用于我将敏感信息排除在存储库之外的目的。
使用 docker-compose,我通过创建一个服务来装载我需要的卷并提交容器的映像来实现这一点。然后,在后续服务中,我依赖于先前提交的映像,该映像将所有数据存储在装载位置。然后,您必须将这些文件复制到其最终目标,因为在运行命令时不会提交主机挂载目录docker commit
您不必使用 docker-compose 来完成此操作,但它使生活更轻松一些
# docker-compose.yml
version: '3'
services:
stage:
image: alpine
volumes:
- /host/machine/path:/tmp/container/path
command: bash -c "cp -r /tmp/container/path /final/container/path"
setup:
image: stage
# setup.sh
# Start "stage" service
docker-compose up stage
# Commit changes to an image named "stage"
docker commit $(docker-compose ps -q stage) stage
# Start setup service off of stage image
docker-compose up setup
评论
build.additional_contexts
一个简单的解决方法可能是在运行容器时简单地将卷(使用 -v 或 --mount 标志)挂载到容器中,并以这种方式访问文件。
例:
docker run -v /path/to/file/on/host:/desired/path/to/file/in/container/ image_name
有关更多信息,请参阅:https://docs.docker.com/storage/volumes/
评论
docker run
一种快速而肮脏的方法是根据需要设置尽可能多的级别 - 但这可能会产生后果。 如果你在如下所示的微服务体系结构中工作:
./Code/Repo1
./Code/Repo2
...
您可以将构建上下文设置为父目录,然后访问所有内容,但事实证明,对于大量存储库,这可能会导致构建需要很长时间。Code
一个示例情况可能是,另一个团队维护一个数据库架构,而团队的代码依赖于此架构。您希望使用自己的一些种子数据对这种依赖项进行 docker 化,而不必担心架构更改或污染其他团队的存储库(当然,根据更改的内容,您可能仍然需要更改种子数据脚本)
第二种方法是 hacky,但可以解决长构建的问题:Repo1
Repo2
创建一个 sh(或 ps1)脚本来复制所需的文件并调用所需的 docker 命令,例如:./Code/Repo2
#!/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 文件中,只需将上下文设置为 root 并使用 dockerfile 中的目录内容,而无需担心路径。
请记住,您将冒着意外将此目录提交到源代码管理的风险,但脚本清理操作应该足够简单。Repo2
./db/schema
就我而言,我的 Dockerfile 编写得像一个包含占位符的模板,我使用我的配置文件将其替换为实际值。
所以我不能直接指定这个文件,而是像这样将其管道输送到 docker 构建中:
sed "s/%email_address%/$EMAIL_ADDRESS/;" ./Dockerfile | docker build -t katzda/bookings:latest . -f -;
但是由于管道的原因,该命令不起作用。但是上述方法通过(明确说明未提供文件)来解决它。仅不使用标志,则不提供上下文和 Dockerfile,这是一个警告。COPY
-f -
-
-f
评论
正如此 GitHub 问题中所述,构建实际上发生在 中,因此相对路径 like 是相对于 的。因此,它将搜索 ,这也显示在错误消息中。/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
评论
ln
npm install
创建一个包装器 docker build shell 脚本,该脚本获取文件,然后调用并删除文件。docker build
一个简单的解决方案,在我的快速浏览中没有提到:
- 有一个名为
docker_build.sh
- 让它创建压缩包,将大文件复制到当前工作目录
- 叫
docker build
- 清理压缩包、大文件等
这个解决方案很好,因为 (1.) 它没有复制 SSH 私钥的安全漏洞 (2.) 另一个解决方案使用,所以那里有另一个安全漏洞,因为它需要 root 权限才能这样做。sudo bind
bind
如何在两个 Dockerfile 之间共享打字稿代码
我遇到了同样的问题,但是在两个打字稿项目之间共享文件。其他一些答案对我不起作用,因为我需要保留共享代码之间的相对导入路径。我通过这样组织我的代码来解决它:
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_modules
**/node_modules/**
)在命令前面加上 或
Dockerfile
COPY
api/
frontend/
复制(除了 或
shared
api/src
frontend/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 <---- ADDED RUN npm run build
这是我可以将所有内容发送到 docker 的最简单方法,同时保留两个项目中的相对导入路径。棘手(烦人)的部分是由于构建上下文位于一个目录上而引起的所有更改/后果。
我个人对一些答案感到困惑,所以决定简单地解释一下。
在以下情况下,应将 Dockerfile 中指定的上下文传递给 docker 想要创建图像。
我总是选择项目的根目录作为 Dockerfile 中的上下文。
因此,例如,如果您使用 COPY 命令,例如 COPY .
第一个 dot(.) 是上下文,第二个 dot(.) 是容器工作目录
假设上下文是项目根目录,dot(.) ,代码结构如下所示
sample-project/
docker/
Dockerfile
如果要构建映像
您的路径(运行 docker build 命令的路径)是 /full-path/sample-project/, 你应该这样做
docker build -f docker/Dockerfile .
如果您的路径是 /full-path/sample-project/docker/, 你应该这样做
docker build -f Dockerfile ../
此行为由 or 用于将文件呈现给生成过程的上下文目录提供。
一个很好的技巧是在构建指令期间将上下文目录更改为要公开给守护程序的目录的完整路径。
例如:docker
podman
docker build -t imageName:tag -f /path/to/the/Dockerfile /mysrc/path
使用 而不是 (当前目录),您将使用该目录作为上下文,因此构建过程可以看到该目录下的任何文件。
在此示例中,您将向 docker 守护程序公开整个树。
当与触发构建的用户 ID 一起使用时,必须对上下文目录中的任何单个目录或文件具有递归读取权限。/mysrc/path
.
/mysrc/path
docker
如果你拥有但想要将不在同一目录中的文件引入此容器生成上下文,这可能很有用。/home/user/myCoolProject/Dockerfile
下面是使用 context dir 构建的示例,但这次使用 而不是 .podman
docker
让我们举个例子,在你的 a 或指令中,它从项目外部的目录复制文件,例如: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
的完整全局路径。
在这种情况下,必须相对于文件所在的位置,如果两者都在目录中,则用于构建它必须是:contex dir
externalFile
AnotherProject
/opt
context dir
podman build -t imageName:tag -f ./Dockerfile /opt
请注意,在 docker
中将 COPY
或 ADD
与上下文目录一起使用时:
守护程序将尝试将上下文目录树上可见的所有文件“流式传输”到守护程序,这可能会减慢构建速度。并要求用户具有来自上下文目录的递归权限。
这种行为可能会更昂贵,尤其是在通过 API 使用构建时。但是,构建是即时发生的,不需要递归权限,这是因为它没有枚举整个上下文目录,也没有使用架构。
当您使用不同的上下文目录遇到此类问题时,使用此类情况的构建可能更有趣,而不是 。docker
podman
podman
client/server
podman
docker
一些参考资料:
- https://docs.docker.com/engine/reference/commandline/build/
- https://docs.podman.io/en/latest/markdown/podman-build.1.html
评论
ADD
/
root
/
/
我认为从今年早些时候开始,buildx 中添加了一个功能来做到这一点。
如果你有 dockerfile 1.4+ 和 buildx 0.8+,你可以做这样的事情:
docker buildx build --build-context othersource= ../something/something .
然后,在 docker 文件中,您可以使用 from 命令添加上下文
ADD –-from=othersource . /stuff
请参阅此相关帖子。
评论
ERROR: failed to get build context path {/builds/my_file <nil>}: not a directory
更改生成上下文是要走的路。
如果有一个 .net core 项目,并且仍想使用 Visual Studio UI 通过 docker 调试/发布项目,则可以通过将“DockerfileContext”添加到项目 .csproj 来更改上下文:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..\.</DockerfileContext>
</PropertyGroup>
...
</Project>
不要忘记相应地更改 Dockerfile 中的路径。
我对这个问题的解决方法。寻找优雅的解决方案,每次构建映像时都不需要重新发明,并决定创建以下 build-docker.sh bash 脚本:
#!/bin/bash
# Check if a tag argument is provided
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <tag>"
exit 1
fi
# Assign the tag argument to a variable
TAG=$1
# Copy the source-code directory
cp -R ../source-code ./temp-source-code
# Run the Docker build command with the specified tag
docker build -t "$TAG" .
# Check if Docker build was successful
if [ $? -eq 0 ]; then
echo "Docker build successful, removing temp-source-code directory..."
rm -rf ./temp-source-code
else
echo "Docker build failed, temp-source-code directory not removed."
fi
在 Dockerfile 中:
# Copy the rest of your application's source code
COPY . .
# Remove the existing symlink
RUN rm -f ./src/source-code
# Copy lambda parser
COPY ./temp-source-code ./src/source-code
综上所述:
- 使用所需的图像标记运行脚本
- 它会将所需的文件夹复制到当前目录中
- 将运行
docker build ...
- 将从当前控制器中删除复制的文件夹
- 在 Dockerfile 中,只需添加删除符号链接的行,否则您的文件夹将不会被复制
- 运行脚本,享受你的生活:)
评论
FROM
docker buildx build --build-context othersource= ../something/something .