使用Dockerfile将私有Git仓库克隆到Docker容器中的最佳策略是什么?利/弊?我知道我可以在Dockerfile上添加命令,以便将我的私有存储库克隆到Docker容器中。但我想知道人们在这个案子上使用了哪些不同的方法。Dockerfile最佳实践指南中没有介绍它。
qvtsj1bj1#
来自Ryan Baumann的博客文章“Git strategies for Docker”将Git源代码导入Docker构建有不同的策略。其中许多都有不同的方式与Docker的缓存机制进行交互,并且可能或多或少适合您的项目以及您打算如何使用Docker。
运行git clone
如果你像我一样,这是当你看到Dockerfile中可用的命令时首先想到的方法。这样做的问题是,它可以以几种不直观的方式与Docker的构建缓存机制进行交互。例如,如果你对git仓库进行了更新,然后重新运行带有RUN git clone命令的docker build,你可能会也可能不会得到新的提交,这取决于前面的Dockerfile命令是否该高速缓存无效。解决这个问题的一个方法是使用docker build --no-cache,但是如果克隆之前有任何时间密集的命令,它们也必须再次运行。另一个问题是,当上游的git仓库更新时,您(或您将Dockerfile分发给的人)可能会意外地返回到一个损坏的构建。在使用RUN git clone的情况下,一个一石二鸟的方法是将它放在一行1中,并带有特定的修订 checkout ,例如:
--no-cache
RUN git clone https://github.com/example/example.git && cd example && git checkout 0123abcdef
然后更新Dockerfile中要 checkout 的修订版本将使该行该高速缓存无效,并导致克隆/ checkout 运行。通常这种方法的一个可能的缺点是你必须在容器中安装git。
运行curl或添加标签/提交tarball URL
这避免了在容器环境中安装git,并且可以明确该高速缓存何时会中断(即如果标签/修订是URL的一部分,则该URL改变将破坏该高速缓存)。请注意,如果您使用Dockerfile ADD命令从远程URL复制,则每次运行构建时都会下载该文件,并且HTTP Last-Modified标头也将用于该高速缓存无效。您可以在golang Dockerfile中看到这种方法。
Dockerfile仓库内的Git子模块
如果您将Dockerfile和Docker构建与源代码放在一个单独的存储库中,或者您的Docker构建需要多个源存储库,则在此存储库中使用git子模块(或git子树)可能是将源存储库放入构建上下文的有效方法。这避免了Docker缓存和上游更新的一些问题,因为您锁定了子模块/子树规范中的上游修订。更新它们会破坏你的Docker缓存,因为它会更改构建上下文。请注意,这只是将文件放入Docker构建上下文,您仍然需要在Dockerfile中使用ADD命令将这些路径复制到容器中您期望的位置。您可以在here中看到这种方法
git仓库内的Dockerfile
在这里,你只需要把你的Dockerfile和你想要构建/测试/部署的代码放在同一个git仓库里,所以它会自动作为构建上下文的一部分发送出去,这样你就可以ADD . /project将上下文复制到容器中。这样做的好处是,你可以测试更改,而不必潜在地提交/推送它们以使它们进入测试Docker构建;缺点是每次修改工作目录中的任何文件时,它都会使ADD命令中该高速缓存无效。发送大型源/数据目录的构建上下文也是非常耗时的。因此,如果您使用这种方法,您可能还希望明智地使用.dockerignore文件,包括忽略.gitignore中的所有内容以及可能的.git目录本身。
卷Map
如果您使用Docker来设置一个开发/测试环境,并希望在主机上的各种源代码库中共享,那么将主机目录挂载为数据卷可能是一个可行的策略。这使您能够指定在Docker运行时要包含哪些目录,并避免对Docker构建缓存的担忧,但这些都不会在Dockerfile或容器映像的其他用户之间共享。
yzckvree2#
通常有两种方法:
2018年更新:参见“How to keep your container secrets secure”,其中包括:
关于第二种方法,请参见“Pulling Git into a Docker image without leaving SSH keys behind”
Dockerfile:
ADD ~/.ssh/mykey /tmp/ RUN ssh-agent /tmp # RUN bundle install or similar command RUN rm /tmp/mykey
现在让我们构建图像:
$ docker build -t original .
docker save original | sudo docker-squash -t squashed | docker load
w8ntj3qf3#
我能想到的策略有几个:
选项A:Dockerfile中的单级:
ADD ssh-private-key /root/.ssh/id_rsa RUN git clone git@host:repo/path.git
这有几个显著的缺点:
RUN
FROM base-image as clone ADD ssh-private-key /root/.ssh/id_rsa RUN git clone git@host:repo/path.git RUN rm -rf /path/.git FROM base-image as build COPY --from=clone /path /path ...
通过使用multi-stage,只要您从不将“clone”stage层推到任何地方,您的ssh凭证现在就只在构建主机上。这稍微好一点,但仍然有缓存问题(请参阅最后的提示)。通过添加rm步骤,以后的COPY --from将不再复制这些文件。由于构建映像或更高版本应该是您提供的所有内容,因此在克隆阶段的层上效率低下就不那么重要了。
rm
COPY --from
选项C:从配置项服务器:
通常情况下,Dockerfile在代码库中,人们倾向于在运行构建之前先克隆它(尽管可以通过使用git repo作为构建上下文来跳过这一点)。因此,您经常会看到CI服务器执行克隆和更新,而不是Dockerfile本身。得到的Dockerfile就是:
COPY path /path
这有几个优点:
git pull
.dockerignore
.git
诚然,这个选项是在对你的问题说“不要这样做”,但它也是我所见过的面临这一挑战的人最受欢迎的选择,这是有充分理由的。
选项D:使用BuildKit:
BuildKit有几个可能有用的实验特性。这些需要较新版本的Docker,可能不是每个构建主机上都有,并且注入选项的语法不是向后兼容的。主要的两个选项是secrets或ssh凭证注入和缓存目录。这两种方法都可以将文件或目录注入到构建步骤中,而不会将其保存到生成的图像层中。以下是它可能的样子(未经测试):
# syntax=docker/dockerfile:experimental FROM base-image ARG CACHE_BUST RUN --mount=type=cache,target=/git-cache,id=git-cache,sharing=locked \ --mount=type=secret,id=ssh,target=/root/.ssh/id_rsa \ if [ ! -d /git-cache/path/.git ]; then \ git clone git@host:repo/path.git /git-cache/path; \ else \ (cd /git-cache/path && git pull --force); \ fi; \ tar -cC /git-cache/path --exclude .git . | tar -xC /path
然后构建看起来像:
DOCKER_BUILDKIT=1 docker build \ --secret id=ssh,src=$HOME/.ssh/id_rsa \ --build-arg "CACHE_BUST=$(date +%s)" \ -t img:tag \ .
这是相当复杂的,但有几个优点:
要了解有关BuildKit实验特性的更多信息,请参阅:https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
提示:对特定行进行缓存破坏:
要在特定行上破坏docker构建缓存,您可以在要重新运行的RUN行之前注入一个构建参数,该参数在每次构建时都会更改。在BuildKit示例中,有:
ARG CACHE_BUST
之前的RUN行,我不想缓存,构建包括:
--build-arg "CACHE_BUST=$(date +%s)"
为每个构建注入唯一变量。这可确保生成始终运行该步骤,即使该命令在其他方面未发生更改。build arg作为环境变量注入到RUN中,因此docker会看到此命令已更改,并且无法该高速缓存中重用。理想情况下,您可以克隆一个特定的标签或提交ID,这允许您缓存使用与以前构建相同的git克隆的构建。但是,如果您正在克隆主服务器,则需要使用此缓存破坏技术。
x0fgdtte4#
添加
ADD https://github.com/youraccount/myscript.git#main .这会将整个repo复制到您的容器中。下面是一个工作示例:
ADD https://github.com/youraccount/myscript.git#main .
FROM node:latest WORKDIR /usr/src/app ADD https://github.com/youraccount/myscript.git#main . COPY package*.json ./ RUN npm install COPY . . EXPOSE 8080
您的存储库中的.dockerignore文件决定了哪些文件夹/文件被过滤/忽略。ADD也可以抓取单个文件。如果是私有回购,文档中会说:
要通过SSH添加私有存储库,请使用以下格式创建Dockerfile:
# syntax=docker/dockerfile:1-labs FROM alpine ADD git@git.example.com:foo/bar.git /bar
4条答案
按热度按时间qvtsj1bj1#
来自Ryan Baumann的博客文章“Git strategies for Docker”
将Git源代码导入Docker构建有不同的策略。其中许多都有不同的方式与Docker的缓存机制进行交互,并且可能或多或少适合您的项目以及您打算如何使用Docker。
运行git clone
如果你像我一样,这是当你看到Dockerfile中可用的命令时首先想到的方法。这样做的问题是,它可以以几种不直观的方式与Docker的构建缓存机制进行交互。例如,如果你对git仓库进行了更新,然后重新运行带有RUN git clone命令的docker build,你可能会也可能不会得到新的提交,这取决于前面的Dockerfile命令是否该高速缓存无效。
解决这个问题的一个方法是使用docker build
--no-cache
,但是如果克隆之前有任何时间密集的命令,它们也必须再次运行。另一个问题是,当上游的git仓库更新时,您(或您将Dockerfile分发给的人)可能会意外地返回到一个损坏的构建。
在使用RUN git clone的情况下,一个一石二鸟的方法是将它放在一行1中,并带有特定的修订 checkout ,例如:
然后更新Dockerfile中要 checkout 的修订版本将使该行该高速缓存无效,并导致克隆/ checkout 运行。
通常这种方法的一个可能的缺点是你必须在容器中安装git。
运行curl或添加标签/提交tarball URL
这避免了在容器环境中安装git,并且可以明确该高速缓存何时会中断(即如果标签/修订是URL的一部分,则该URL改变将破坏该高速缓存)。请注意,如果您使用Dockerfile ADD命令从远程URL复制,则每次运行构建时都会下载该文件,并且HTTP Last-Modified标头也将用于该高速缓存无效。
您可以在golang Dockerfile中看到这种方法。
Dockerfile仓库内的Git子模块
如果您将Dockerfile和Docker构建与源代码放在一个单独的存储库中,或者您的Docker构建需要多个源存储库,则在此存储库中使用git子模块(或git子树)可能是将源存储库放入构建上下文的有效方法。这避免了Docker缓存和上游更新的一些问题,因为您锁定了子模块/子树规范中的上游修订。更新它们会破坏你的Docker缓存,因为它会更改构建上下文。
请注意,这只是将文件放入Docker构建上下文,您仍然需要在Dockerfile中使用ADD命令将这些路径复制到容器中您期望的位置。
您可以在here中看到这种方法
git仓库内的Dockerfile
在这里,你只需要把你的Dockerfile和你想要构建/测试/部署的代码放在同一个git仓库里,所以它会自动作为构建上下文的一部分发送出去,这样你就可以ADD . /project将上下文复制到容器中。这样做的好处是,你可以测试更改,而不必潜在地提交/推送它们以使它们进入测试Docker构建;缺点是每次修改工作目录中的任何文件时,它都会使ADD命令中该高速缓存无效。发送大型源/数据目录的构建上下文也是非常耗时的。因此,如果您使用这种方法,您可能还希望明智地使用.dockerignore文件,包括忽略.gitignore中的所有内容以及可能的.git目录本身。
卷Map
如果您使用Docker来设置一个开发/测试环境,并希望在主机上的各种源代码库中共享,那么将主机目录挂载为数据卷可能是一个可行的策略。这使您能够指定在Docker运行时要包含哪些目录,并避免对Docker构建缓存的担忧,但这些都不会在Dockerfile或容器映像的其他用户之间共享。
yzckvree2#
通常有两种方法:
2018年更新:参见“How to keep your container secrets secure”,其中包括:
关于第二种方法,请参见“Pulling Git into a Docker image without leaving SSH keys behind”
Dockerfile:
现在让我们构建图像:
w8ntj3qf3#
我能想到的策略有几个:
选项A:Dockerfile中的单级:
这有几个显著的缺点:
RUN
行没有改变。选项B:Dockerfile内部的多阶段:
通过使用multi-stage,只要您从不将“clone”stage层推到任何地方,您的ssh凭证现在就只在构建主机上。这稍微好一点,但仍然有缓存问题(请参阅最后的提示)。通过添加
rm
步骤,以后的COPY --from
将不再复制这些文件。由于构建映像或更高版本应该是您提供的所有内容,因此在克隆阶段的层上效率低下就不那么重要了。选项C:从配置项服务器:
通常情况下,Dockerfile在代码库中,人们倾向于在运行构建之前先克隆它(尽管可以通过使用git repo作为构建上下文来跳过这一点)。因此,您经常会看到CI服务器执行克隆和更新,而不是Dockerfile本身。得到的Dockerfile就是:
这有几个优点:
git pull
,这要快得多。.dockerignore
内部的.git
,以排除所有的git内部。因此,您只需将repo的最终状态添加到docker镜像中,从而得到一个小得多的镜像。诚然,这个选项是在对你的问题说“不要这样做”,但它也是我所见过的面临这一挑战的人最受欢迎的选择,这是有充分理由的。
选项D:使用BuildKit:
BuildKit有几个可能有用的实验特性。这些需要较新版本的Docker,可能不是每个构建主机上都有,并且注入选项的语法不是向后兼容的。主要的两个选项是secrets或ssh凭证注入和缓存目录。这两种方法都可以将文件或目录注入到构建步骤中,而不会将其保存到生成的图像层中。以下是它可能的样子(未经测试):
然后构建看起来像:
这是相当复杂的,但有几个优点:
.git
目录,使映像变小。需要此副本,因为该高速缓存目录不会保存到生成的图像层中。RUN
步骤的单个文件只读卷装载,并且该secret的内容没有保存到生成的映像层。要了解有关BuildKit实验特性的更多信息,请参阅:https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
提示:对特定行进行缓存破坏:
要在特定行上破坏docker构建缓存,您可以在要重新运行的RUN行之前注入一个构建参数,该参数在每次构建时都会更改。在BuildKit示例中,有:
之前的
RUN
行,我不想缓存,构建包括:为每个构建注入唯一变量。这可确保生成始终运行该步骤,即使该命令在其他方面未发生更改。build arg作为环境变量注入到
RUN
中,因此docker会看到此命令已更改,并且无法该高速缓存中重用。理想情况下,您可以克隆一个特定的标签或提交ID,这允许您缓存使用与以前构建相同的git克隆的构建。但是,如果您正在克隆主服务器,则需要使用此缓存破坏技术。
x0fgdtte4#
添加
ADD https://github.com/youraccount/myscript.git#main .
这会将整个repo复制到您的容器中。下面是一个工作示例:
您的存储库中的
.dockerignore
文件决定了哪些文件夹/文件被过滤/忽略。ADD也可以抓取单个文件。如果是私有回购,文档中会说:
添加私有git仓库
要通过SSH添加私有存储库,请使用以下格式创建Dockerfile: