Node 多阶段构建
Node 项目常用的两段式写法:构建阶段装全量依赖并编译,运行阶段只装生产依赖并复制构建产物。这样最终镜像里没有构建工具链与开发依赖,体积和攻击面都明显更小。
基本信息
| 阶段划分 | build 负责装全量依赖并编译,runtime 只装生产依赖 |
|---|---|
| 依赖安装 | 两个阶段都需要复制 package.json 与锁文件 |
| 运行用户 | node 官方镜像自带 node 用户(UID 1000),可直接 USER node |
| 环境变量 | 运行阶段设 NODE_ENV=production |
| 产物复制 | COPY --from=build /app/dist ./dist |
典型使用场景
- 前端项目:构建阶段跑打包命令,运行阶段只提供静态文件或 SSR 服务
- 后端服务:构建阶段编译 TypeScript,运行阶段只跑编译后的 JavaScript
- 控制镜像体积,不把开发依赖与构建工具带进生产镜像
- 配合分层缓存:依赖层稳定,改业务代码不会触发重新安装依赖
- 排查生产依赖缺失:确认运行阶段确实执行了生产依赖安装
- 统一多环境构建:同一份文件在 CI 与本地得到一致的产物
示例
示例
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci # 全量依赖,包含构建工具
COPY . .
RUN npm run build # 产出 dist/
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build --chown=node:node /app/dist ./dist
USER node
CMD ["node", "dist/server.js"]两个阶段的分工
| 内容 | 构建阶段是否安装 | 是否进入最终镜像 |
|---|---|---|
| 开发依赖与构建工具 | 是 | 否 |
| 生产依赖 | 是 | 是 |
| 源码 | 是(作为编译输入) | 否,只复制产物 |
| 编译产物 dist | 不涉及 | 是 |
| 依赖安装缓存 | 是 | 不需要写入镜像 |
先复制 package.json 与锁文件再执行安装,最后复制源码:改一行业务代码不会触发依赖整层重装,构建时间差别很明显。
两个阶段都要复制锁文件,运行阶段只装生产依赖时同样需要它锁版本,漏掉会安装失败或版本漂移。
运行阶段用 USER node 之前,记得给产物目录设置属主(--chown),否则非 root 进程可能写不了缓存与日志目录。