Hello
← 博客

Docker 多阶段构建:从 1.2GB 到 80MB 的镜像瘦身之路

最近接手一个 Node.js 微服务,发现它的 Docker 镜像有 1.2GB。部署一次要传输半天,CI/CD 流水线也被拖慢。花了一个下午优化,最终压到了 80MB。记录一下过程。

问题分析

原始 Dockerfile 大致长这样:

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

问题很明显:

第一步:使用 Alpine 基础镜像

FROM node:20-alpine

仅这一步就从 900MB 降到 ~180MB。Alpine 基于 musl libc,体积极小。

第二步:多阶段构建

核心思路是把「构建环境」和「运行环境」分离:

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

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

关键点:

第三步:进一步压缩 —— distroless

如果项目没有依赖 native 模块,可以用 Google 的 distroless 镜像:

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

Distroless 镜像不包含 shell、包管理器等工具,安全性也更好。

第四步:优化构建缓存

Docker 的层缓存机制意味着指令顺序很重要:

# 先复制 package 文件
COPY package*.json ./
RUN npm ci

# 再复制源码
COPY . .
RUN npm run build

这样只要 package.json 没变,npm ci 那层就会命中缓存。如果反过来先 COPY . .,任何源码改动都会让依赖安装重跑。

最终效果对比

版本镜像大小构建时间(冷启动)
原始1.2 GB4m 30s
Alpine310 MB3m 20s
多阶段120 MB2m 10s
distroless80 MB2m 15s

注意事项

镜像瘦身不只是节省磁盘。更小的镜像意味着更快的拉取、更快的滚动更新、更小的攻击面。值得投入时间优化。