构建与优化
层缓存、减小体积、多阶段构建入门。
构建与优化
这一课你将能做到
- 看懂构建输出里的
CACHED,说出「哪些指令会命中缓存、什么情况下缓存会失效」 - 用多阶段构建把 Spring Boot 镜像从「源码 + JDK + Maven」瘦身到「纯 JRE + jar」,体积砍掉一半以上
- 熟练使用
docker build的-t、-f、--build-arg、--no-cache、--platform参数
为什么要学
同样的 Spring Boot 项目,有人构建要 5 分钟、镜像 800MB;有人构建 30 秒、镜像 250MB。差距来自两点:对层缓存的理解、对镜像内容的取舍。CI 上每次构建多等 4 分钟,一天几十次构建就是几个小时的白等;镜像多 500MB,每次发布就多传 500MB。
这一课解决的问题非常具体:改一行 Java 代码,为什么整个镜像要重建?怎么让 Maven 依赖下载只发生一次?最终的镜像里为什么不需要 Maven 和源码?答案就是本课的两个核心:层缓存与多阶段构建。
先建立图像
构建缓存的判定规则:从上到下逐条指令比对,一旦某层失效,其后所有层全部重建。
# 反例:每次改代码,mvn 依赖全部重新下载
FROM eclipse-temurin:21-jre
COPY . /app ← 任何文件变了,这层失效
RUN cd /app && mvn package ← 上一层失效 → 这层必然重跑(重新下载依赖)
# 正例:依赖声明先 COPY,代码后 COPY
COPY pom.xml . ← pom 没变 → 缓存命中
RUN mvn dependency:go-offline ← 缓存命中,依赖一次都不用重新下
COPY src ./src ← 只有 src 变了 → 仅这层起失效
RUN mvn package ← 重跑,但依赖已缓存,快
多阶段构建的分工:
阶段 1 builder:Maven + JDK + 源码 → 产出 jar (构建完即弃)
阶段 2 runtime:纯 JRE + jar → 最终镜像 (只带走结果)
跟着做
步骤 1:先体验一次缓存命中
用上一课的最小 Dockerfile,连续构建两次:
docker build -t demo-app:1.0 .
docker build -t demo-app:1.0 . # 第二次几乎全部 CACHED,秒级完成
然后重新 mvn clean package(jar 内容变了),再构建:
mvn clean package -DskipTests
docker build -t demo-app:1.1 .
# 输出:FROM/WORKDIR 层 CACHED,COPY target/... 层重新执行
现象确认:只有「内容变化的层」及其后的层重建,FROM 这种基础层永远命中。
步骤 2:改造 Dockerfile,利用依赖缓存(单阶段版)
前提:你在本地/CI 先 mvn package。如果想让镜像内完成编译,用下面多阶段版;此处先改造 COPY 顺序养成习惯:
FROM eclipse-temurin:21-jre
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
改动很小的项目,反复构建基本只重建 COPY 这一层,其余全缓存。
步骤 3:多阶段构建——镜像里自己完成编译
# ---- 阶段 1:构建 ----
FROM maven:3.9-eclipse-temurin-21 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -B # 先拉依赖,pom 不变则永远缓存
COPY src ./src
RUN mvn clean package -DskipTests
# ---- 阶段 2:运行 ----
FROM eclipse-temurin:21-jre
ENV TZ=Asia/Shanghai
WORKDIR /app
COPY --from=builder /build/target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]