Docker 多阶段构建实战:把 Node.js 镜像从 1.2GB 压到 180MB

举报
yd_99838092 发表于 2026/10/30 15:48:34 2026/10/30
【摘要】 一、问题:镜像为什么越做越大很多团队的前端项目一开始都是这样构建镜像的:用一个 node:20 基础镜像,把源码拷进去,执行 npm install 和 npm run build,然后把整份目录当作运行镜像推送到镜像仓库。这样做能跑通,但副作用很明显:编译工具链、devDependencies、源码、缓存全部留在镜像里,一个普通的中后台项目轻松做到 1GB 以上,推送到镜像仓库要几分钟,节...

一、问题:镜像为什么越做越大

很多团队的前端项目一开始都是这样构建镜像的:用一个 node:20 基础镜像,把源码拷进去,执行 npm install 和 npm run build,然后把整份目录当作运行镜像推送到镜像仓库。这样做能跑通,但副作用很明显:编译工具链、devDependencies、源码、缓存全部留在镜像里,一个普通的中后台项目轻松做到 1GB 以上,推送到镜像仓库要几分钟,节点拉取镜像又慢,安全扫描还会报出一堆编译期依赖的漏洞。

更麻烦的是,运行阶段根本不需要 npm、node-gyp、TypeScript 编译器这些东西。运行只需要一份构建产物和一台静态服务器。

二、多阶段构建的原理

Docker 从 17.05 开始支持多阶段构建:一个 Dockerfile 里可以写多个 FROM,每个 FROM 开启一个新阶段,阶段之间用 as 命名。最终镜像只保留最后一个阶段的内容,前面的阶段可以当作“临时工作区”,通过 COPY --from=阶段名 把需要的文件复制过来。构建缓存依然按阶段生效,所以构建速度不会明显变慢,但最终镜像的体积会小一个数量级。

三、改造前:单阶段 Dockerfile

FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

这份文件的问题在于:node_modules、源码、构建缓存、npm 缓存全部进入镜像;基础镜像本身还带着完整的编译工具链。最终镜像 1.2GB 是常态。

四、改造后:两阶段 Dockerfile

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

第一个阶段只负责安装依赖和打包,第二个阶段只拿 dist 产物,运行环境换成体积小得多的 nginx-alpine。node、npm、源码、缓存都不会出现在最终镜像里。

五、实测对比

同一个项目,同一台构建机,改造前后的数据大致如下:镜像体积从 1.2GB 降到 180MB;docker push 从约 3 分钟降到 20 秒;节点冷启动拉取镜像从 90 秒降到 12 秒;安全扫描的高危项从 40 多个降到个位数。构建耗时基本持平,因为 npm ci 和打包仍然要做,只是换了一个阶段。

六、几个容易踩的坑

第一,npm ci 需要 package-lock.json,缺锁文件会直接失败,这是好事,能保证依赖可复现。第二,一定要写 .dockerignore,把 node_modules、dist、.git 排除掉,否则 COPY . . 会把宿主机的依赖带进构建上下文,既慢又容易出现平台不一致的问题。第三,跨平台构建时要注意原生模块,苹果芯片机器上安装的依赖不能直接拿到 linux/amd64 镜像里用,最好在构建阶段指定 --platform。第四,COPY --from 的路径要写构建产物真实的位置,Vite 默认是 dist,Create React App 是 build,写错了不会报错,只会在运行时报 404。第五,静态资源如果走 CDN,记得调整 nginx 的缓存头和 gzip 配置。

七、总结

多阶段构建不是新特性,但它是投入产出比最高的镜像瘦身手段之一:改十几行 Dockerfile,换来一个更小、更快、更安全的运行镜像。建议把它作为团队 Dockerfile 的默认写法,同时在 CI 里加一条镜像体积阈值检查,防止体积悄悄回涨。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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