Docker 多阶段构建实战:把 Node 镜像从 1.2GB 压到 180MB
为什么镜像会越来越大
一个典型的 Node 项目,直接 FROM node:20 加 npm install,构建出来的镜像往往超过 1GB。原因不复杂:基础镜像本身约 1GB,node_modules 里塞了 devDependencies,再加上 .git、测试用例、本地构建产物,一股脑全进了最终镜像。
镜像大带来的直接后果是:推送到镜像仓库慢、节点拉取慢、冷启动扩容慢,安全扫描还要多扫几千个无关文件。
多阶段构建:只要产物,不要工具链
核心思路是把「构建环境」和「运行环境」分开,最终镜像只从构建阶段拷贝需要的东西。
---------- 构建阶段 ----------
FROM node:20-alpine AS builder
WORKDIR /app
先只拷依赖清单,利用分层缓存
COPY package*.json ./
RUN npm ci
再拷源码并构建
COPY . .
RUN npm run build
只保留生产依赖
RUN npm ci --omit=dev && npm cache clean --force
---------- 运行阶段 ----------
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
用非 root 用户运行
RUN addgroup -S app && adduser -S app -G app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
USER app
EXPOSE 3000
CMD ["node", "dist/index.js"]
这个写法的收益来自三点:第一,构建工具(TypeScript、webpack、测试框架)全部留在 builder 阶段,不进最终镜像;第二,node:20-alpine 本身只有几十 MB;第三,只拷贝 dist 和 node_modules,源码和 .git 不会泄露到线上。
缓存顺序决定构建速度
COPY package*.json ./ 单独一行、放在 COPY . . 之前,是整套写法里最容易忽略的细节。Docker 的分层缓存以指令为单位,只要某一层的输入没变就直接复用。代码改动频繁、依赖改动很少,把依赖安装和源码拷贝分开,日常改代码时依赖层可以命中缓存,npm ci 不会再跑一遍。
反过来,如果写成 COPY . . 之后再 npm install,任何一次改代码都会让依赖层失效,构建时间从 10 秒变成 3 分钟。
.dockerignore 比想象中重要
多阶段构建解决「不该进最终镜像」的问题,.dockerignore 解决「不该进构建上下文」的问题。少了它,node_modules、.git、日志文件会被打包发给 Docker daemon,构建上下文动辄几百 MB。
node_modules
.git
dist
coverage
*.log
.env*
Dockerfile
docker-compose*.yml
注意 .env 一定要排除。很多人把密钥写进去,镜像推到仓库就等于公开了。
几个容易踩的坑
时区和字符集:alpine 默认没有时区数据,容器里 new Date() 出来的是 UTC。需要本地时间就装 tzdata 并设置 TZ=Asia/Shanghai。
原生模块编译失败:alpine 用 musl libc,某些依赖(比如老版本的 sharp、bcrypt)没有预编译产物,会在 npm ci 阶段编译失败。解决办法是要么换 node:20-slim,要么在 builder 阶段装 python3 make g++。
HEALTHCHECK 别省:镜像能起来不代表服务可用。加一条 HEALTHCHECK CMD wget -qO- http://127.0.0.1:3000/health || exit 1,编排系统才知道什么时候该把流量切过去。
效果对比
同一个项目,改造前后:
· 单阶段:1.18GB,冷启动拉取约 90 秒
· 多阶段 + alpine:182MB,冷启动约 15 秒
体积下降 85%,构建缓存命中后 CI 时间也少了三分之二。改造本身只涉及一个 Dockerfile 和一个 .dockerignore,投入产出比相当高。
最后提醒一句:镜像不是越小越好。为了省几十 MB 把运行时依赖删到不可调试,出问题时连 sh 都进不去,反而得不偿失。体积和安全、可维护性之间要取平衡。
- 点赞
- 收藏
- 关注作者
评论(0)