Docker 镜像瘦身实战:多阶段构建把 1.2GB 应用镜像压到 120MB

举报
yd_292302676 发表于 2026/10/27 09:39:13 2026/10/27
【摘要】 从 1.2GB 压到 118MB 的完整改造过程:多阶段构建、基础镜像选型、RUN 层合并与 .dockerignore,附前后对比数据与四个常见坑。

一、1.2GB 的镜像到底贵在哪

我们组有个 Java 服务,镜像推上去 1.2GB。一开始没人管,直到两件事发生:一是每次发布流水线光拉取镜像就要三分多钟,节点磁盘三天两头告警;二是安全扫描报出一百多个 CVE,绝大多数来自构建阶段带进去的编译工具链,运行时根本用不到。

镜像体积不是一个"好看"指标,它直接换算成钱和时间:拉取时间、节点磁盘占用、镜像仓库存储、Pod 冷启动延迟,以及漏洞修复的工作量。这几项里,最后一项最疼。

二、先搞清楚镜像里有什么

不要凭感觉删。先看分层和目录占用:

docker history --no-trunc myapp:1.2.0 | head -20
docker run --rm -it --entrypoint sh myapp:1.2.0
du -sh /* 2>/dev/null | sort -h | tail -10

一般会看到三类东西:构建工具(maven、gcc、头文件)、包管理器缓存(/root/.m2、apt lists)、以及多份重复的依赖副本。我们的镜像里 /root/.m2 单独占了 480MB,src 目录还留着全量源码。

三、多阶段构建:把编译环境和运行环境切开

这是收益最大的一步。构建阶段用完整镜像,运行阶段只拿产物。

# 构建阶段
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /src
COPY pom.xml .
RUN mvn -B dependency:go-offline
COPY src ./src
RUN mvn -B clean package -DskipTests

# 运行阶段
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY --from=build /src/target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-XX:MaxRAMPercentage=75","-jar","app.jar"]

关键点是 COPY --from 只搬运行时要的东西。构建层的 maven、源码、.m2 全部留在中间镜像里,不进最终镜像。

四、换基础镜像:alpine 不是万能解

把 eclipse-temurin:17-jre-jammy(约 270MB)换成 alpine 版能再省一百多兆,但要先确认两件事:一是应用是否依赖 glibc 特有行为,musl 对 DNS 解析和 locale 的处理不同,我们有个服务在 alpine 上偶发 UnknownHostException;二是 native 依赖有没有预编译的 musl 版本,没有就得在镜像里装编译链,反而更大。

不想碰 alpine 的话,distroless 是另一个方向:没有 shell、没有包管理器,攻击面最小,代价是进容器排查只能靠日志和临时 debug 镜像。

五、合并 RUN、清缓存、写 .dockerignore

每条 RUN 都是一层,删文件必须在同一层里删,否则被删的内容仍然留在上一层。

# 反例:两层,缓存仍在镜像里
RUN apt-get update && apt-get install -y curl
RUN apt-get clean

# 正例:一层完成
RUN apt-get update \
 && apt-get install -y --no-install-recommends curl ca-certificates \
 && rm -rf /var/lib/apt/lists/*

.dockerignore 同样重要。我们之前没写,COPY . . 把 target/、.git/、node_modules 全送进了构建上下文,构建慢,还容易误打包本地配置:

.git
target
node_modules
*.log
.idea

六、改造前后对比

指标 改造前 改造后
镜像体积 1.21 GB 118 MB
层数 23 7
拉取耗时(同机房) 3 分 20 秒 18 秒
扫描高危漏洞 112 9
Pod 冷启动 26 秒 11 秒

七、几个容易踩的坑

时区丢失。换了精简基础镜像后容器里是 UTC,日志时间对不上,需要显式装 tzdata 并设 TZ=Asia/Shanghai。

CA 证书缺失。极简镜像可能没有 ca-certificates,调 HTTPS 接口直接报证书校验失败,报错信息还很不直观。

用 latest 标签。基础镜像一变,构建结果跟着变,出问题无法回溯。基础镜像必须锁版本,关键场景连 digest 一起锁。

只优化体积,没优化构建缓存。把 COPY pom.xml 和 COPY src 分开,依赖层才能复用,否则每次改一行代码都要重下全部依赖,构建时间反而变长。

八、落地顺序建议

先上多阶段构建(收益最大、风险最低),再补 .dockerignore,然后合并 RUN 清缓存,最后才考虑换基础镜像。每步都用 docker history 和镜像扫描结果验证一次,别一次改完再统一排查,出了问题很难定位是哪一步引入的。

镜像瘦身本质上是把"构建环境"和"运行环境"彻底分开。想清楚运行时到底需要哪几个文件,剩下的自然就该扔掉。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。