多阶段的Docker构建是否可以进行缓存?

54

我最近开始使用多阶段 Docker 构建,看起来在中间构建过程中没有缓存。我不确定这是 Docker 的限制,还是某些功能不可用,或者我做错了什么。

我正在拉取最终构建,并在新构建开始时执行 --cache-from,但它总是运行完整的构建过程。


1
请提供一些输出或日志。您认为为什么没有缓存? - Nestor Sokil
3
@NestorSokil 中间阶段运行时不考虑任何可能影响那些Docker层的更改。 https://docs.docker.com/develop/develop-images/multistage-build/#name-your-build-stages 上的“hello world”也是这样做的。 - Matthew Goslett
之前阶段的层不在最终阶段中,因此在其中使用 --cache-from 不会帮助之前的阶段。保存之前的阶段可以起作用,但似乎只有在使用相同的计算机/文件系统构建时才能匹配层,因此实际上并不能帮助 CI 构建 :( - Aaron
3个回答

57

这似乎是docker本身的一个限制,在这个问题下有详细的描述 - https://github.com/moby/moby/issues/34715

解决方法是:

  1. 使用 --target 构建中间阶段
  2. 将中间镜像推送到镜像仓库
  3. 使用 --target 构建最终镜像,并在 --cache-from 中使用多个路径,列出所有的中间镜像和最终镜像
  4. 将最终镜像推送到镜像仓库
  5. 对于后续的构建,请先从镜像仓库拉取中间+最终镜像

1
它使用第一个匹配项,因此先列出最终图像,然后是中间图像。 这在 CI 情况下不起作用,因为您需要从 push 到不同的机器进行 pull,在 Docker --cache-from 处理中存在一些错误。 https://github.com/moby/moby/issues/34715 - Aaron
5
您如何在docker-compose中使用它?我有一个已缓存的中间镜像,我使用docker load加载,并在docker-compose.yml中将cache_from设置为同一镜像,但是它仍然会重新构建所有阶段。 - thisismydesign

22
自从上一个答案发布以来,现在有一个使用BuildKit后端的解决方案。 这涉及将参数--build-arg BUILDKIT_INLINE_CACHE=1传递给您的docker build命令。您还需要通过设置环境变量DOCKER_BUILDKIT=1来确保使用BuildKit(在Linux上;我认为在使用最新版本的Docker Desktop时,BuildKit可能是Windows的默认后端)。用于CI的完整命令行解决方案可能如下所示:
export DOCKER_BUILDKIT=1
    
# Use cache from remote repository, tag as latest, keep cache metadata
docker build -t yourname/yourapp:latest \
      --cache-from yourname/yourapp:latest \
      --build-arg BUILDKIT_INLINE_CACHE=1 .
    
# Push new build up to remote repository replacing latest
docker push yourname/yourapp:latest

一些其他评论者正在询问关于docker-compose的问题。它也适用于这个问题,尽管您需要额外指定环境变量COMPOSE_DOCKER_CLI_BUILD=1来确保docker-compose使用docker CLI(借助DOCKER_BUILDKIT=1的BuildKit),然后您可以在YAML文件的build:部分的args:部分中设置BUILDKIT_INLINE_CACHE: 1以确保设置所需的--build-arg
供参考:

2
我听说它只存储多阶段构建的最新阶段,它是否真正缓存并重复使用所有构建步骤? - Timofey Yatsenko

7

我想在答案中再添加一个重要的观点

错误的答案

--build-arg BUILDKIT_INLINE_CACHE=1 仅缓存最后一层,并且仅在整个 Dockerfile 中没有更改的情况下才起作用。

因此,为了启用整个构建过程中层的缓存,应该将此参数替换为 --cache-to type=inline,mode=max。请参阅文档

正确的答案

上述文档截至2023年3月28日如下:

生成缓存输出时,--cache-to 参数接受一个 mode 选项,用于定义要包含在导出缓存中的哪些层。除了 inline 缓存之外,所有缓存后端都支持此选项。

这意味着,对于具有所有层的缓存中间状态,需要使用 registry 缓存后端。

我计划在构建时使用相同的image:tag,但带有后缀-buildcache。因此,--cache-from type=registry,ref=org/image:tag-buildcache,mode=max --cache-to type=registry,ref=org/image:tag-buildcache,mode=max


实际上,你应该切换到registry缓存后端而不是inline缓存后端来缓存所有层。从文档中可以看到:
  • 允许将缓存和生成的镜像分离,以便在不包含缓存的情况下分发最终镜像。
  • 它可以高效地在max模式下缓存多阶段构建,而不仅仅是最终阶段。
  • 它与其他导出器一起使用,提供更大的灵活性,而不仅仅是图像导出器。
- dom
你需要一个单独的图像或标签来存储缓存。这完全与目的相反,不是吗? - Felixoid
我认为将缓存放在启动应用程序的容器内并不是必需的。对我来说,能够缓存多阶段构建中的所有层更加重要。 如果您想缓存多阶段构建的所有阶段,则仍然需要使用单独的镜像。似乎他们正在开发Azure Blob存储、Amazon S3和Github Actions作为可行的后端,但这些都是beta版或未发布的。 - dom
1
好的,要么最近有些变化,要么我错过了文档中我提供的重要说明。具体来说,mode被所有缓存后端支持,除了内联缓存。 - Felixoid

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接