site logo

Marico's space

Docker 构建耗时10分钟?这一改动将其缩短至秒级

服务器技术 2026-05-27 20:55:30 5

最近折腾一个多模块 Spring Boot 项目的 Docker 镜像构建,踩了不少坑,这篇把问题说清楚。

场景是这样的:你只改了一行 application.yaml 配置。

然后重新 build 镜像。

然后 Docker 开始下载半个互联网的依赖。

我之前做的一个多模块 Spring Boot 项目,有多个 pom.xml 和巨大的依赖树。每次 rebuild 都让人崩溃,有时候光解析 Maven 依赖就要 8-10 分钟,还没开始打包应用呢。

最坑的是什么?

实际代码改动就那么一点点。

真正的元凶不是 Maven

是 Docker 层策略。

很多 Dockerfile 是这样写的:

COPY . .
RUN mvn package

看起来很简单。

但这完全破坏了 Docker 的层缓存机制。

每个 Docker 指令都会创建一个独立的不可变层。

每个层都有自己的内容哈希。如果任何一个层变了,Docker 会让下面所有层全部失效并重建。

所以如果 Dockerfile 在解析依赖之前先复制了源码:

COPY src ./src
RUN mvn package

那每次源码改动都会强制触发:

  • Maven 依赖解析
  • 插件下载
  • 打包
  • 重新编译

全部重来一遍。

尽管依赖压根没变过。

这就是构建时间浪费的大头。

那个改变一切的优化

我换上了 Docker BuildKit。

在 Dockerfile 顶部加这一行:

# syntax=docker/dockerfile:1.4

这会让 Dockerfile 前端连接到现代的 BuildKit 后端,解锁像缓存挂载这类高级特性。

然后不用普通的依赖解析:

RUN mvn dependency:go-offline

我改成了:

RUN --mount=type=cache,target=/root/.m2 \
 mvn dependency:go-offline

这就是那个 game changer。

--mount=type=cache 到底干了什么

Maven 的依赖通常存在:

/root/.m2

没有 BuildKit 的情况下:

  • 每次全新构建都要重新下载依赖
  • Docker 在层重建后就把它们扔掉了

用 BuildKit 缓存挂载的话:

  • Docker 在宿主机上创建一个持久化缓存目录
  • Maven 依赖被缓存住
  • 后续构建直接复用

所以第一次构建之后:

  • 依赖不再重新下载
  • 重建变得飞快
  • 迭代开发又顺滑起来了

我的重建时间从好几分钟直接掉到几秒钟。

另一个大多数人都忽略的优化

Dockerfile 里的指令顺序非常关键。

这个模式极其重要:

COPY pom.xml .
RUN mvn dependency:go-offline COPY src ./src

为什么?

因为 pom.xml 变化的频率远低于应用源码。

Docker 现在可以把依赖解析和源码改动分开缓存了。

所以:

  • 改 Java 代码只会重建打包层
  • 依赖层纹丝不动
  • 重建保持快速

如果你把顺序反过来:

COPY src ./src
COPY pom.xml .

那每次源码改动都会让后面所有层失效。

对大型 Java 构建来说这是灾难性的。

但是等等……CI/CD 里怎么工作的?

这时候我自己又冒出一个问题。

如果 GitHub Actions 这类 CI runner 每次运行都用全新的临时机器,那缓存到底存在哪?

因为每次流水线跑完:

  • Runner 被销毁
  • 文件系统消失
  • .m2 缓存也没了

那跨构建的缓存是怎么保持的?

答案是 BuildKit 远程缓存导出/导入。

在 GitHub Actions 里可以这样把 BuildKit 缓存持久化到跨 runner:

steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile tags: myimage:latest cache-from: type=gha cache-to: type=gha,mode=max

这玩意儿强得离谱。

这里发生了什么:

  • cache-to 把 BuildKit 缓存推到 GitHub 的临时缓存存储
  • cache-from 在后续工作流运行中恢复它

所以尽管每个 runner 都是全新的:

  • Maven 依赖保持缓存
  • Docker 层保持可复用
  • 跨流水线的构建保持快速

这就是现代生产级 CI/CD 系统在大规模下优化容器构建的方式。

为什么这对生产环境重要

这不只是关乎开发者体验。

在生产工程环境中这直接影响:

  • CI/CD 速度
  • 部署频率
  • 计算成本
  • 反馈周期
  • 开发者生产力

对于部署了几十甚至上百个服务的团队来说,哪怕每次构建节省 5 分钟,都会累积成巨大的工程效率提升。

特别是在这些场景:

  • 微服务架构
  • Monorepo
  • 多模块 Maven 项目
  • Kubernetes 交付流水线
  • 高频部署环境

最后总结

如果你的 Docker 构建慢得要命,别急着甩锅给:

  • Maven
  • Spring Boot
  • 网络延迟

大多数情况下真正的问题是 Dockerfile 层设计糟糕 + 缺少缓存策略。

一个结构合理的 Dockerfile 配合 BuildKit 缓存,能把重建时间从 10 分钟砍到几秒钟。

一旦体验过那种速度差异,就再也回不去了。

原文链接:https://dev.to/docker/docker-builds-were-taking-10-minutes-this-one-change-brought-it-down-to-seconds-4f1m