
镜像大小直接影响 CI/CD 速度、镜像仓库带宽、云部署成本——尤其在 Kubernetes 频繁拉镜像的场景下,每多 100M 都是真金白银。
下面用一个 ReactJS 应用做演示,镜像从 1.43GB 一路压到 22.4MB ——压缩率 98.5%。每一步只改一件事,一目了然:
关于镜像版本 :本文 Dockerfile 全部用 node:20 / node:20-alpine(2026 年 LTS) 。下面的体积截图是当年 demo 录制时用 node:12 实拍——绝对数字会有 5%-15% 差异,但每一步的相对压缩率(-60% / -83% / -77%)几乎不变 ,思路完全通用。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 视频教程:https://doc.iocoder.cn/video/
用 CRA 脚手架快速起一个项目(一切瘦身的起点都是要有"东西"先跑起来):
npx create-react-app react-demo
进入目录、装依赖、本地跑起来:
cd react-demo
yarn install
yarn start
浏览器访问 http://localhost:3000 看到默认欢迎页就 OK:

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/yudao-cloud
- 视频教程:https://doc.iocoder.cn/video/
最直白的写法——直接把项目塞进 node:20 镜像里跑:
FROM node:20
WORKDIR /app
COPY package.json ./
RUN yarn install
COPY . .
EXPOSE 3000
CMD ["yarn", "start"]
构建并查看:
docker build -t react-demo .
docker images

结果:1.43GB ——一个静态页面背了 1.4G 的壳。
跑起来验证一下能用:
docker run --rm -it -p 3000:3000/tcp react-demo:latest
为什么这么大?node:20 默认基于 Debian(Bookworm),自带的系统组件 + 全套 npm 依赖 + 项目源代码——三样东西全在最终镜像里。
第一刀砍系统层 :node:20-alpine 用 Alpine Linux 替代 Ubuntu,体积小一个数量级。
各种 Node 基础镜像大小对比:

只改 FROM 行:
FROM node:20-alpine
WORKDIR /app
COPY package.json ./
RUN yarn install
COPY . .
EXPOSE 3000
CMD ["yarn", "start"]
重新构建:

结果:580MB ——直接砍掉 60%。但还能更狠。
上一版镜像里还带着完整的源代码和 node_modules ——但生产运行只需要 build 产物 (/build 目录里的 html/js/css),其它都是负重。
多阶段构建(Multi-stage Build)的核心思想 :构建用一个镜像,运行用另一个镜像,构建产物从前者复制到后者。
# STAGE 1: 构建阶段
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json ./
RUN yarn install
COPY . /app
RUN yarn build
# STAGE 2: 运行阶段
FROM node:20-alpine
WORKDIR /app
RUN npm install -g webserver.local
COPY --from=build /app/build ./build
EXPOSE3000
CMD webserver.local -d ./build
第二阶段是干净的 node:20-alpine——只装一个轻量 webserver,再把第一阶段的 build 产物拷过来 ,源码和 node_modules 一概不要。
构建:

结果:97.5MB ——再砍 83%。漂亮。
最后一步:别再用 Node 跑静态资源了 ——React 编译产物是纯静态文件,Nginx 是这种场景下绝对的最优解 :
最终版:
# STAGE 1: 构建
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json ./
RUN yarn install
COPY . /app
RUN yarn build
# STAGE 2: 运行
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
构建:

结果:22.4MB ——从 1.43GB 一路下来,体积压缩了 98.5% ,性能反而更好。
启动验证:
docker run --rm -it -p 3000:80/tcp react-demo:latest
注意端口映射:Nginx 容器内部默认监听 80,所以宿主机 3000 → 容器 80 。
回头看 1.43GB → 22.4MB 这条路,真正起决定作用的就三件事 ——次序也很重要,按这个顺序砍最省力:
| | | |
|---|
| 1. Alpine 替 Ubuntu | | 1.43GB → 580MB | |
| 2. 多阶段构建 |
| 580MB → 97.5MB | |
| 3. 静态资源换 Nginx | | 97.5MB → 22.4MB | |
四条放在哪都不会错的工程原则 :
- 运行时镜像 ≠ 构建时镜像 ——别把构建工具链带进生产镜像
- 基础镜像优先 Alpine 系列 ——除非业务需要 glibc,否则 Alpine 永远是首选
- 静态资源用 Nginx,不要用 Node ——Node 跑 web 服务给静态文件是烧 CPU 烧内存
- CI 跑
docker images 看体积告警 ——超过预期值就回头查,别等仓库账单提醒你
这套思路不局限于 React——任何 NodeJS / 前端构建产物 / 单一可执行文件的服务 都适用。Java 项目也类似:构建用 maven 镜像,运行用 distroless / alpine + JRE,容易压到 80MB 量级 。
最后说一句 :CI/CD 时间和镜像仓库账单是真金白银——花一个小时优化 Dockerfile 的 ROI,比加一个月班都高。
