Moby: 无法使用排除的文件进行复制

创建于 2015-08-22  ·  82评论  ·  资料来源: moby/moby

我需要将上下文目录的 _part_ 复制到容器中(另一部分受另一个 COPY 的约束)。 不幸的是,目前的可能性并不理想:

  1. 复制和修剪。 无限制复制后,我可以删除不需要的材料。 问题是不需要的材料可能已更改,因此缓存无效。
  2. 在它自己的 COPY 指令中复制每个文件。 这会在图像中添加 _lot_ 的不必要层。
  3. 围绕“docker build”调用编写一个包装器,以某种方式准备上下文,以便 Dockerfile 可以轻松地复制所需的材料。 繁琐且难以维护。
arebuilder kinenhancement kinfeature

最有用的评论

+1 对于这个问题,我认为它可以像许多 glob 库支持它一样得到支持:

这是一个复制除node_modules之外的所有内容的建议

COPY . /app -node_modules/

所有82条评论

请参阅https://docs.docker.com/reference/builder/#dockerignore -file
您可以将条目添加到项目根目录中的 .dockerignore 文件中。

.dockerignore 不能解决这个问题。 正如我所写,“另一部分受另一份副本的约束”。

所以你想根据其他副本有条件地复制?

上下文包含许多目录 A1...A10 和目录 B。A1...A10 有一个目的地,B 有另一个:

COPY A1 /some/where/A1/
COPY A2 /some/where/A2/
...
COPY A10 /some/where/A10/
COPY B some/where/else/B/

这很尴尬。

哪部分尴尬? 单独列出它们?

COPY A* /some/where/
COPY B /some/where/else/

这行得通吗?

A1..A10、B 的名字是假的。 此外, COPY A* ...将目录的_contents_ 放在一起。

我承认有几个选择,但我认为它们都很尴尬。 我在原始帖子中提到了三个。 第四种选择是永久重新排列我的源代码,以便将 A1..A10 移动到新目录 A 中。我希望这不是必需的,因为不需要额外的嵌套级别,并且我当前的工具需要然后是我的码头化项目的特例。

(顺便说一句,#6094(遵循符号链接)在这种情况下会有所帮助。但显然,这也不是选择。)

@bronger如果COPY的行为与cp完全一样,那会解决您的用例吗?

我不确定我是否 100% 理解。
也许@duglin可以看看。

@bronger我认为@cpuguy83提出了正确的问题,如果你使用'cp',你会如何解决这个问题? 我看了看并没有注意到“cp”上的某种排除选项,所以我也不确定您将如何在“docker build”之外解决这个问题。

对于 cp 行为,我可以通过说来改善这种情况

COPY ["A1", ... "A10", "/some/where/"]

这仍然是一个轻微的维护问题,因为如果我添加一个“A11”目录,我将不得不考虑这一行。 但这是可以接受的。

此外, cp 不需要排除,因为复制所有内容并删除不需要的部分除了复制本身之外几乎没有性能影响。 使用 docker 的 COPY,这意味着每次更改 B 时都会错误地使缓存无效,以及更大的图像。

@bronger你可以这样做:

COPY a b c d /some/where

就像你建议的那样。

至于在RUN rm ...之后执行COPY ... ,是的,您将拥有额外的层,但您仍然应该能够使用缓存。 如果您因此看到缓存未命中,请告诉我,我认为您不应该这样做。

COPY a b c d /some/where/

将目录 abcd 的内容复制到一起,而不是创建目录 /some/where/{a,b,c,d}。 它的工作方式类似于 rsync,在 src 目录中附加了一个斜杠。 因此,_four_指令

COPY a /some/where/a/
COPY b /some/where/b/
COPY c /some/where/c/
COPY d /some/where/d/

是需要的。

至于缓存......如果我说

COPY . /some/where/
RUN rm -Rf /some/where/e

如果 e 发生变化,则不使用缓存,尽管 e 没有有效地包含在操作中。

@bronger是的,很遗憾你是对的。 我想我们可以添加一个--exclude zzz类型的标志,但是根据https://github.com/docker/docker/blob/master/ROADMAP.md#22 -dockerfile-syntax 它可能不会得到很多现在的牵引力。

很公平。 那我就暂时用个COPY+rm,加个FixMe评论。 感谢您的时间!

只是为了:+1:这个问题。 我经常后悔 COPY 没有反映 rsync 的尾部斜杠语义。 这意味着您不能在单个语句中复制多个目录,从而导致层扩散。

我经常遇到这样一种情况,我想复制除一个以外的许多目录(稍后将复制它,因为我希望它具有不同的层失效效果),所以--exclude也会很有用。

此外,从man rsync

       A trailing slash on the source changes this behavior to avoid  creating
       an  additional  directory level at the destination.  You can think of a
       trailing / on a source as meaning "copy the contents of this directory"
       as  opposed  to  "copy  the  directory  by name", but in both cases the
       attributes of the containing directory are transferred to the  contain‐
       ing  directory on the destination.  In other words, each of the follow‐
       ing commands copies the files in the same way, including their  setting
       of the attributes of /dest/foo:

              rsync -av /src/foo /dest
              rsync -av /src/foo/ /dest/foo

我想现在如果不破坏很多狂野的Dockerfile就无法更改它。

作为一个具体的例子,假设我有一个如下所示的目录:

/vendor
/part1
/part2
/part3
/...
/partN

我想要的东西看起来像:

COPY /vendor /docker/vendor
RUN /vendor/build
COPY /part1 /part2 ... /partN /docker/ # copy directories part1-N to /docker/part{1..N}/
RUN /docker/build1-N.sh

所以 part1-N 不会使/vendor的构建无效。 (因为 /vendor 与 part1-N 相比很少更新)。

我以前通过将 part1-N 放在他们自己的目录中来解决这个问题,所以:

/vendor
/src/part1-N

但是我也遇到过这个问题,我不能轻易地重新安排。

@praller很好的例子,我们面临着完全相同的问题。 主要问题是 Go 的 filepath.Match 与正则表达式相比并没有太多的创造力(即没有反模式)

我只是想出了一个有点脑筋急转弯的解决方法。 COPY 不能排除目录,但是 ADD _can_ expand tgz。

这是一个额外的构建步骤:
焦油 --exclude='./deferred_copy' -czf all_but_deferred.tgz 。
码头工人建造...

然后在你的 Dockerfile 中:
添加 ./all_but_deferred.tgz /application_dir/
.. 很少变化的层中的东西..
添加 。 /application_dir/
.. 经常变化的层中的东西

这提供了 tar 的完整语法,用于包含/排除/任何内容,而无需尝试包含/排除大量浪费的层。

@jason-kane 这是一个不错的技巧,感谢分享。 一个小问题:您似乎无法将z (gzip) 标志添加到tar — 它会更改 sha256 校验和值,这会使 Docker 缓存无效。 否则,这种方法对我很有用。

+1 对于这个问题,我认为它可以像许多 glob 库支持它一样得到支持:

这是一个复制除node_modules之外的所有内容的建议

COPY . /app -node_modules/

我也遇到了同样的问题,当我的 Java webapps 大约 900MB 但几乎 80% 很少更改时,这对我来说有点痛苦。
这是我的应用程序的早期状态,并且文件夹结构有些稳定,所以我不介意添加 6-7 COPY 层以能够使用缓存,但是从长远来看,当越来越多的文件和目录时肯定会受到伤害被添加

👍

我有同样的问题,尽管使用docker cp ,我想从一个文件夹中复制所有文件,除了一个

这里完全相同的问题。 我想复制一个 git repo 并排除 .git 目录。

@oaxlin ,您可以为此使用 .dockerignore 文件。

@antoineco你确定这会奏效吗? 我已经有一段时间没有尝试过了,但我很确定.dockerignore不能与docker cp一起使用,至少当时是这样

@kkozmic-seek 绝对确定 :) 但是您提到的docker cp CLI 子命令与 Dockerfile 中的COPY语句不同,这是此问题的范围。

docker cp确实与 Dockerfile 和 . dockerignore,但另一方面,它不用于构建图像。

真的很喜欢这个 - 为了加快构建速度,我可以在构建的早期部分复制一些文件夹,然后缓存会帮助我......

我不确定我是否理解用例是什么,但在 COPY 解决问题之前不会只触摸要排除的文件吗?

RUN touch /app/node_modules
COPY . /app
RUN rm /app/node_modules

AFAIK COPY不会覆盖文件,这就是我认为这可能有效的原因。

哎呀,没关系,看起来COPY实际上会覆盖文件。 我现在对 npm 安装然后执行COPY . /usr/src/apphttps://nodejs.org/en/docs/guides/nodejs-docker-webapp/有点困惑。 我猜它假设node_modules被 docker 忽略了? 另一方面,使用COPY_NO_OVERWRITE (需要更好的名称)命令可能是在复制期间实现忽略文件的一种方法(您必须为要忽略的内容创建空文件/目录)。

FWIW,我觉得这很丑陋。

我找到了另一个黑客解决方案:

示例项目结构:
应用程序/
配置/
脚本/
规格/
静止的/
...

我们想要:

  1. 复制静态/
  2. 复制其他文件
  3. 复制应用/

破解解决方案:
ADD ./static /home/app
ADD ["./[^s^a]*", "./s[^t]*", "/home/app/"]
ADD ./app /home/app

第二个 ADD 等价于:复制所有,例如“./st ”和“./a ”。
有什么改进的想法吗?

评论的状态是什么?

👍

👍

👍

👍

以与 .gitignore 相同的方式拥有一个 .dockerignore 文件怎么样?

@mirestrepo请参阅此问题的前两个后续内容。

目前,这是 C# / dotnet 开发的大型性能削弱。

我想要的是:

  • 首先将所有外部 dll 复制到 docker 映像(除 My*.dll 之外的所有内容)
  • 然后复制我所有的 dll(从 My.*.dll 开始)。

现在看来这(很容易)不可能,因为我无法复制所有内容。

因此,要么将 dll 复制双倍,这会增加 docker 文件的大小,要么将所有内容复制到一层中。
后者是一个超级 nerf,因为每次都复制外部 dll 而不是缓存。

@adresdvila感谢我能够将其拆分为的解决方案:

COPY ["[^M][^y]*","/app/"] 
COPY ./My* /app/

尽管这仍然存在在第一个命令处复制 .json 文件的问题

只是插话感谢@antoineco ,我的问题已经解决了。 我不再将 .git 目录复制到我的 docker 映像中。

这极大地改善了图像大小,并使我的图像对 docker 缓存系统更加友好。

我也有同样的问题。 我有一个大文件,我想在其余文件之前复制它,因此上下文中的任何更改都不会重复,因为复制需要很长时间(7 GB bin 文件)。 有没有新的解决方法?

COPY 和剪枝方法的问题是剪枝之前的层仍然继续包含所有数据。

COPY . --exclude=a --exclude=b将非常有用。 你怎么看,@ cpuguy83?

@Nowaker我喜欢它。 无论如何,似乎与tarrsync一致。
我想这应该支持与dockerignore相同的格式?

@tonistiigi @dnephin

我认为这种情况将由#32507 处理。

@cpuguy83是的。 最值得注意的是,符合COPY --chown=uid:gid

@dnephin RUN --mount听起来像是一个完全不同的用例,其中心是根据我们在生成输出后不需要的数据生成一些东西。 (例如用 Go 编译,从 Markdown 文件生成 HTML 等)。 RUN --mount是涂料,我肯定会在我目前正在从事的项目中使用它(使用 Sphinx 生成 API 文档)。

COPY somedir --exclude=excludeddir1 --exclude=excludeddir2以复制数据为中心,这些数据必须最终出现在图像中,但分散在多个 COPY 语句中,而不仅仅是一个。 当项目在根目录中有很多目录并且可能会更改/增加时,目标是避免显式COPY first second third .... eleventh destination/

就我而言,我想首先复制大多数文件,除了那些非必要的文件,以确保在源文件没有更改的情况下使用缓存。 然后,编译/生成 - 如果复制的文件没有更改(耶),则使用缓存。 一开始复制我之前排除的文件,这些文件可能自上一次构建以来已更改,但它们的更改不会影响编译/生成。 显然,我在 . 我想先复制,最后只有一对我想复制的地方。

这个想法是RUN --mount能够解决很多问题。 COPY --exclude只解决了一个问题。

我宁愿添加一些可以解决很多问题的东西,也不愿添加一堆语法来解决个别问题。 您将使用RUN --mount... rsync --exclude ... (或一些复制个别事物的脚本),它相当于COPY --exclude

@dnephin哦,我没想到RUN --mount rsync ! 优秀的! 👍

这确实很棒。 但是,您将无法有效地利用缓存@Nowaker ,因为如果挂载目录中有任何更改,缓存将失效,而不仅仅是您想要 rsync 的内容。

如果您将该 rsync 的输出用作其他内容的输入,并且其中没有实际更改的文件,则缓存将再次启动。 如果你真的准备好了,你现在可以使用https://gist.github.com/tonistiigi/38ead7a4ed60565996d207a7d589d9c4#file -gistfile1-txt-L130-L140 之类的东西来做到这一点。 RUN --mount (或 buildkit 中的 LLB)的唯一变化是您不必在阶段之间实际复制文件,而是可以直接访问它们,因此速度要快得多。

使用https://docs.docker.com/develop/develop-images/multistage-build/ 怎么样?

FROM php:7.2-apache as source
COPY ./src/ /var/www/html/
RUN rm -rf /var/www/html/vendor/
RUN rm -rf /var/www/html/tests/

FROM php:7.2-apache
COPY --from=source /var/www/html/ /var/www/html/

@antoineco Welp,那么它不再优秀了。 谢谢指出。。

@MartijnHols这是一个很好的解决方法。 感谢您发布。

对维护者:也就是说,我们可以说“为什么要在 COPY 中实现 --chown,你可以在多阶段构建中使用 RUN chown”。 我们需要--exclude来保持理智。 这些天在 Dockerfiles 中有太多的变通方法。

我有一个可以从COPY --exclude中受益的用例。 我有一个大数据文件夹,需要将其全部复制到容器中。 该目录的内容会经常更改。 为了提高缓存性能,我想将目录中的一个大文件复制到自己的层中,然后再复制其余文件。 到目前为止,描述这种类型的容器是不必要的冗长。

以 requirements.txt 为中心使用分层缓存的正确方法是什么

我有这个:

/root-app
 - /Dockerfile
 - /requirements.txt
 - /LICENSE
 - /helloworld.py
 - /app-1
     -/app-1/script1
     -/app-1/script2
     -/app-1/script3
 - /app-2
     -/app-2/script1

和 Dockerfile:

FROM python:slim
COPY ./requirements.txt /
RUN pip install --trusted-host pypi.python.org -r /requirements.txt
WORKDIR /root-app
COPY . /helloworld
CMD ["python", "helloworld.py"]

使用第二个 COPY 命令排除需求构建缓存的正确方法是什么......如果它们没有太大变化,同样将我的 app-1 和 app-2 分层?

@axehayz不确定这是否是您要问的,但我会做类似于https://medium.com/@guillaumejacquart/node -js-docker-workflow-12febcc0eed8 中的节点工作流程的事情。

即,您的第二个副本可以是COPY . ; 只要您的pip install之前出现,这不会使已安装软件包的缓存无效。

面临同样的问题。 目前我更喜欢在不同的目录中展开文件。

我有另一个 COPY --exclude=... --exclude=...

我正在尝试执行 COPY --from=oldimage 以减小图像大小,并且我需要复制大部分文件,但没有其中的一些。 我可以按目录逐个目录进行操作,这很痛苦,但可以工作……但是能够 --exclude 一个目录/文件列表或提供多个 --exclude 选项会更好,也更容易维护。

所以三年半之后根本没有承认?

@asimonf有大量的确认和来回理解用例。 我想你的意思是没有人做过这项工作? 那是对的。 我们都必须对我们所做的事情做出选择。

老实说,这可以很容易地使用现有功能完成,即使这意味着您必须在 Dockerfile 中编写一些额外的内容才能实现。

# haven't tested exact syntax, but this is the general idea
FROM myRsync AS files
COPY . /foo
RUN mkdir /result && rsync -r --exclude=<pattern> /foo/ /result/

FROM scratch
COPY --from=files /result/* /

使用 buildkit,您甚至不需要额外的阶段

#syntax=docker/dockerfile:experimental
..
RUN --mount=target=/ctx rsync ... /ctx/ /src/

除非我错过了使用多阶段构建的东西,否则这里的解决方案似乎不是。 缓存在 COPY 阶段仍然无效。

除非我错过了使用多阶段构建的东西,否则这里的解决方案似乎不是。 缓存在 COPY 阶段仍然无效。

这是对的。 因为这是我现在遇到的问题。

多阶段对我来说很棒。

我将构建分解为多阶段,以充分利用缓存,它看起来像这样:

FROM alpine as source

WORKDIR /app
COPY . ./
RUN scripts/stagify-files

FROM node:12.4.0

WORKDIR /app

# Step 0: Setup environments
COPY --from=source /app/stage0 ./
RUN stage0-build.sh

# Step 1: Install npm packages
COPY --from=source /app/stage1 ./
RUN stage1-build.sh

# Step 2: Build project
COPY --from=source /app/stage2 ./
RUN stage2-build.sh

@zenozen该过程的挑战是您必须专门为 docker 构建安排应用程序布局,这是许多人不想做的事情。
在确定如何布局应用程序文件时(例如,可维护性、新员工的易用性、跨项目标准、框架要求等),使用 docker 是需要平衡的众多考虑因素之一。

@cfletcher我不确定我是否完全明白你的意思。

对于我上面提到的更改,我实际上将我的 Dockerfile 移动到了一个子子目录中(这在尝试使用 rsync 来暂存这些文件时给我带来了很多问题),这意味着我试图隐藏我的 Dockerfile。

我提出的方法和我想象的一样通用。 假设您的项目中有 100 个文件。 它只是简单地选择其中的 1 个来制作 stage0,然后选择 1+10 个来制作 stage 1,然后全部 100 个来组成 stage 2。每个 stage 都堆叠在前一个 stage 之上,并且具有不同的构建命令。 对于复杂的项目结构,这只意味着 stagify-files 的逻辑会很复杂。

对我来说,最大的原因是我将代码拆分为模块,并且我需要在运行npm install之前复制所有package.json文件。

还想为副本提供某种排除参数。 我们在根目录中有 20 多个文件和 10 多个目录。 我们对其中的 2 个目录和一些文件进行了大量编码。 我想将它们分成两个 COPY 层。 一个用于我们从不接触的静态文件和目录,另一个用于我们经常接触的文件和目录。

非常可悲的是,这仍然被忽视。 如果我可以排除一个目录以不使缓存无效,这将帮助我每次构建节省 5 分钟。

使用 buildkit,缓存不像 pre-buildkit 那样依赖于父映像。
所以是的,使用提到的 rsync 解决方案,您会受到打击,因为每次有一些更改时您都需要同步,但是后续阶段将根据内容进行缓存,如果传输的内容没有更改,那么。 ..至少在我完整的现场理论中,这些阶段应该使用缓存。

很遗憾,在COPY中添加一个简单的--exclude标志实在是太难了。 它在 TOP30投票最多的门票中,与其他 TOP30 门票相比,实施起来相对容易。 :(

这没有争议,它需要工作。

@cpuguy83 是的。 看起来有争议/有点被拒绝。 这是否意味着COPY --exclude的适当 PR 如果通过质量标准,可能会被接受?

我不能代表每个维护者发言,但我在一个月左右之前与@tonistiigi谈过这个问题,而 IIRC 是这与 dockerignore、语法等相关的最大障碍(事实上 dockerignore 在语法上是不够的)。

更改将需要进入 buildkit。

支持COPY --exclude=... --exclude=... - 在我的单体存储库中也需要

点赞! 我试过COPY !(excludedfile) .应该可以在 Bash 上运行,但在 Docker 上不行

我不喜欢必须为 $#$ Dockerfile $#$ 中的每个COPY语句重复.dockerignore文件中的所有内容的建议。 恕我直言,能够与将成为图像一部分的东西保持干燥而不应该是优先事项。

查看#33923,我认为您想要从构建上下文中排除的内容与您想要从COPY语句中排除的内容完全相同,这并非巧合。 我相信这样的事情会是一个很好的解决方案:

COPY --use-dockerignore <source> <target>

甚至可能是这样的:

COPY --use-ignorefile=".gitignore" <source> <target>

看到.dockerignore通常已经是.gitignore的 90% 的复制品,不得不为每个COPY语句再次重复每个被忽略的文件和文件夹感觉特别烦人。 一定有更好的方法。

@asbjornu .gitignore 和 .dockerignore 根本不是一回事。 特别是对于在构建阶段生成工件并且根本不存在于 git 中的多阶段构建,但应该包含在生成的图像中。
是的,随着多阶段构建的引入,应该能够在每个阶段使用不同的 .dockerignore 文件 - 绝对如此。

我经常想在“docker build”之外复制。 在这些情况下, .dockerignore 什么都不做。 我们需要修改“docker cp”,这是唯一明智的解决方案

这个问题被打开已经5年了。 2020年9月,我还想要这个。 很多人建议使用黑客来解决问题,但几乎所有人和其他人都以某种形式请求exclude标志。 请不要让这个问题在更长时间内得不到解决。

如果你想要某样东西,你需要努力去做或者找人去做。

如果你想要某样东西,你需要努力去做或者找人去做。

首先我们需要知道上游是否想要这个。

在源代码审查之后,我认为我们应该首先在这里https://github.com/tonistiigi/fsutil/blob/master/copy/copy.go扩展复制功能。 之后,我们可以在 libsolver 中扩展 backend.go,只有在这之后才能扩展 AST 和 buildkit 的前端。
但在那之后,副本将比 unix cp 更接近 rsync 语义。

更新:是的,在扩展 copy.go 之后,一切都将接近https://github.com/moby/buildkit/pull/1492以及排除的解析列表。

此页面是否有帮助?
0 / 5 - 0 等级