Docker 多阶段构建实战:把服务镜像从 1.2GB 压到 20MB

举报
yd_298910097 发表于 2026/11/01 10:26:00 2026/11/01
【摘要】 一次把 Docker 交付镜像从 1.2GB 压到 20MB 的完整实践记录:先用 docker history 定位体积大头,再补上 .dockerignore,拆分多阶段构建,最后用 distroless 收敛运行镜像,并附带踩坑记录与优化顺序建议。

过去两年里,我们的交付镜像一直卡在 1.2GB。推一次镜像要等三分多钟,节点拉取又要两分钟,扩容的时候更是在和 CI 的耐心赛跑。后来我们把服务改成 Go 单体并引入 Docker 多阶段构建,镜像体积直接掉到 20MB,构建时间压到 90 秒以内。本文记录这次改造的完整过程和踩过的几个坑。

一、先搞清楚体积到底花在哪

动手改之前,团队里多数人凭直觉认为“大镜像都是基础镜像太大”。真实情况往往不是。一个最省事的检查办法:

docker history --human --no-trunc <image> | head -30

这条命令会按层列出每层的大小和创建命令,对着输出看,通常一两分钟内就能定位到元凶。我们这个项目里,第一层就占了 840MB——那是构建阶段的 golang:1.22 镜像。

二、多阶段构建怎么写

核心思想很简单:构建阶段可以随便用大镜像,运行阶段只保留真正需要的产物。

FROM golang:1.22 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/app ./cmd/server

FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /out/app /app
EXPOSE 8080
ENTRYPOINT ["/app"]

这里有四个细节值得强调:

1. 先 COPY go.mod go.sum 再 RUN go mod download,这样依赖层能被单独缓存住,改代码时不会让依赖重新下载一遍。

2. CGO_ENABLED=0 去掉 C 依赖,程序才能跑在没有 libc 的 distroless 这类精简镜像上。

3. -trimpath 去掉编译时写入的本地路径,-ldflags="-s -w" 去掉调试符号和符号表,这两项还能再省下几 MB。

4. 运行阶段用 distroless,容器里连 shell 都没有,可被利用的入口比完整发行版少得多,日常维护也更省心。

三、最容易漏掉的一步:.dockerignore

改完之后我们的镜像还是 900MB,原因就是漏了 .dockerignore,COPY . . 把整个 node_modules 和 .git 一起塞进了构建上下文。最小可用版本长这样:

.git
node_modules
**/target
**/__pycache__
*.log
.env

四、效果与代价

改造前后的对比:

  • 镜像体积:1.2GB → 20MB
  • 构建耗时:3min → 90s
  • 节点拉取:2min → 5s

代价是失去了进入容器排查问题的能力,distroless 里没有 bash。折中做法是保留一个带 shell 的 target 专门用于排查,平时 CI 只构建 release 标签,需要排查时再构建 debug 标签:

FROM alpine:3.20 AS debug
RUN apk add --no-cache curl strace
COPY --from=builder /out/app /app

五、两个容易被忽略的坑

一是镜像瘦身后,“同一个 tag 推两次”的问题会被放大。如果流水线只 push latest,新旧镜像会互相覆盖,出现“明明没改代码,服务却变了”的诡异现象。我们的做法是 tag 里带上 git short sha,同时让 latest 指向最新构建。

二是基础镜像建议锁 digest,不要用 latest 标签。否则不同机器、不同时间构建出来的镜像会有细微差异,排查问题时非常痛苦。

总结

镜像优化不是一步到位的事,建议按这个顺序推进:

  1. docker history 定位体积大头
  2. 补上 .dockerignore
  3. 拆分多阶段构建
  4. 换最小化运行基础镜像
  5. 用 distroless 或 alpine 替代完整发行版

前两步通常能解决一半问题,后三步才是把收益拉满的关键。改完之后最大的感受是:部署终于不再是 CI 流水线里最慢的那个环节了。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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