Docker 镜像瘦身实战:用多阶段构建把镜像从 1GB+ 降到 200MB

举报
yd_268144123 发表于 2026/10/30 09:53:16 2026/10/30
【摘要】 一、镜像为什么会越做越大很多人构建 Docker 镜像时,习惯把源码、依赖、构建工具、缓存一股脑塞进同一个镜像:apt-get install 装了编译工具、npm install 拉了 devDependencies、构建产生的中间文件也留在镜像层里。结果一个本该几百 MB 的应用,镜像动辄 1GB 以上。镜像大的代价很直接:拉取慢、部署慢、占用仓库存储、扩大攻击面(构建工具、源码都可能...

一、镜像为什么会越做越大

很多人构建 Docker 镜像时,习惯把源码、依赖、构建工具、缓存一股脑塞进同一个镜像:apt-get install 装了编译工具、npm install 拉了 devDependencies、构建产生的中间文件也留在镜像层里。结果一个本该几百 MB 的应用,镜像动辄 1GB 以上。

镜像大的代价很直接:拉取慢、部署慢、占用仓库存储、扩大攻击面(构建工具、源码都可能成为攻击入口)。本文用**多阶段构建(multi-stage build)**和几个工程技巧,把镜像从 1GB+ 瘦身到 200MB 级别。

二、多阶段构建的原理

多阶段构建允许一个 Dockerfile 里有多个 FROM,每个阶段可以基于不同镜像。你可以在“构建阶段”里装编译器、跑构建,只在“运行阶段”里从构建阶段 COPY --from= 拷贝最终产物。最终镜像只包含运行阶段的内容,构建工具和中间文件全部丢弃。

这相当于把“编译环境”和“运行环境”彻底分开。

三、一个 Node 应用的多阶段示例

下面是一个典型的 Node 服务 Dockerfile:

# ---------- 构建阶段 ----------
FROM node:20-bookworm AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build            # 产出 dist/

# ---------- 运行阶段 ----------
FROM node:20-bookworm-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]

关键点:

  • 构建阶段用完整镜像 node:20-bookworm(有编译工具),运行阶段用 -slim;
  • 运行阶段只 npm ci --omit=dev 装生产依赖;
  • 只从 builder 拷贝 dist/,源码与 devDependencies 不进最终镜像;
  • USER node 用非 root 运行,降低安全风险。

四、进一步瘦身的几个技巧

1. 换更小的基础镜像。 alpine 版最小(注意 glibc/musl 差异),或使用 distroless 只保留运行时:

FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist /app/dist
CMD ["/app/dist/main.js"]

2. 合并 RUN 并清理缓存。 每次 RUN 都会生成一层,把安装与清理放同一条命令,避免清理动作产生新层:

RUN apt-get update && apt-get install -y --no-install-recommends \
        curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*

3. 用 .dockerignore 排除无关文件。 避免把 .git、node_modules、测试数据、日志塞进构建上下文:

node_modules
.git
dist
*.log
test

4. 只拷贝需要的文件。 先 COPY package*.json 再 npm ci,最后 COPY . .,既减少缓存失效,也避免把无关文件带进来。

五、构建缓存优化:让层顺序为你服务

Docker 按层缓存,某一层变了,之后所有层都会失效。所以要把变化频率低的放前面,变化频率高的放后面。例如依赖描述文件(package.json)变化频率远低于业务代码,先拷贝它并装依赖,业务代码改动时就不会重复装依赖。

COPY package*.json ./
RUN npm ci
COPY . .            # 业务代码放最后,改代码只需重建这一层

六、对比一下体积

# 单阶段(把所有东西塞一起)
docker images myapp:fat      # 约 1.1GB
# 多阶段 + slim 运行镜像
docker images myapp:slim     # 约 180MB

同一份应用,瘦身 80% 以上,拉取与部署速度显著提升,攻击面也小得多。可以用 docker history 或 dive 工具逐层分析,找出仍然过大的层继续优化。

七、几个常见坑

  1. alpine 的兼容性:部分依赖预编译原生模块(如某些 npm 包),alpine 的 musl 可能不兼容,需在 alpine 内重新编译或用 slim 版。
  2. 时区/证书缺失:slim/distroless 镜像可能没有 ca-certificates、时区数据,调用 HTTPS 或格式化时间会报错,按需安装或拷贝。
  3. 非 root 用户权限:用 USER node 后要确保工作目录属主正确,否则写文件会 Permission denied。
  4. 忘记 .dockerignore:把 node_modules 打进构建上下文,会让构建变慢且可能污染镜像。

八、总结

Docker 镜像瘦身的核心就四句话:多阶段构建分离编译与运行、只拷贝最终产物、选更精简的基础镜像、用好层缓存与 .dockerignore。把这套 Dockerfile 模板沉淀到项目里,CI 构建出的镜像更小、部署更快、也更安全。镜像大小不是小事,它直接决定了你的发布效率与线上攻击面。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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