Docker 镜像瘦身实战:用多阶段构建把镜像从 1GB+ 降到 200MB
一、镜像为什么会越做越大
很多人构建 Docker 镜像时,习惯把源码、依赖、构建工具、缓存一股脑塞进同一个镜像:apt-get install 装了编译工具、npm install 拉了 devDependencies、构建产生的中间文件也留在镜像层里。结果一个本该几百 MB 的应用,镜像动辄 1GB 以上。
镜像大的代价很直接:拉取慢、部署慢、占用仓库存储、扩大攻击面(构建工具、源码都可能成为攻击入口)。本文用**多阶段构建(multi-stage build)**和几个工程技巧,把镜像从 1GB+ 瘦身到 200MB 级别。
二、多阶段构建的原理
多阶段构建允许一个 Dockerfile 里有多个 FROM,每个阶段可以基于不同镜像。你可以在“构建阶段”里装编译器、跑构建,只在“运行阶段”里从构建阶段 COPY --from= 拷贝最终产物。最终镜像只包含运行阶段的内容,构建工具和中间文件全部丢弃。
这相当于把“编译环境”和“运行环境”彻底分开。
三、一个 Node 应用的多阶段示例
下面是一个典型的 Node 服务 Dockerfile:
# ---------- 构建阶段 ----------
FROM node:20-bookworm AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build # 产出 dist/
# ---------- 运行阶段 ----------
FROM node:20-bookworm-slim AS runtime
ENV NODE_ENV=production
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/main.js"]
关键点:
- 构建阶段用完整镜像
node:20-bookworm(有编译工具),运行阶段用-slim; - 运行阶段只
npm ci --omit=dev装生产依赖; - 只从 builder 拷贝
dist/,源码与 devDependencies 不进最终镜像; USER node用非 root 运行,降低安全风险。
四、进一步瘦身的几个技巧
1. 换更小的基础镜像。 alpine 版最小(注意 glibc/musl 差异),或使用 distroless 只保留运行时:
FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist /app/dist
CMD ["/app/dist/main.js"]
2. 合并 RUN 并清理缓存。 每次 RUN 都会生成一层,把安装与清理放同一条命令,避免清理动作产生新层:
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
3. 用 .dockerignore 排除无关文件。 避免把 .git、node_modules、测试数据、日志塞进构建上下文:
node_modules
.git
dist
*.log
test
4. 只拷贝需要的文件。 先 COPY package*.json 再 npm ci,最后 COPY . .,既减少缓存失效,也避免把无关文件带进来。
五、构建缓存优化:让层顺序为你服务
Docker 按层缓存,某一层变了,之后所有层都会失效。所以要把变化频率低的放前面,变化频率高的放后面。例如依赖描述文件(package.json)变化频率远低于业务代码,先拷贝它并装依赖,业务代码改动时就不会重复装依赖。
COPY package*.json ./
RUN npm ci
COPY . . # 业务代码放最后,改代码只需重建这一层
六、对比一下体积
# 单阶段(把所有东西塞一起)
docker images myapp:fat # 约 1.1GB
# 多阶段 + slim 运行镜像
docker images myapp:slim # 约 180MB
同一份应用,瘦身 80% 以上,拉取与部署速度显著提升,攻击面也小得多。可以用 docker history 或 dive 工具逐层分析,找出仍然过大的层继续优化。
七、几个常见坑
- alpine 的兼容性:部分依赖预编译原生模块(如某些 npm 包),alpine 的 musl 可能不兼容,需在 alpine 内重新编译或用 slim 版。
- 时区/证书缺失:slim/distroless 镜像可能没有
ca-certificates、时区数据,调用 HTTPS 或格式化时间会报错,按需安装或拷贝。 - 非 root 用户权限:用
USER node后要确保工作目录属主正确,否则写文件会 Permission denied。 - 忘记
.dockerignore:把node_modules打进构建上下文,会让构建变慢且可能污染镜像。
八、总结
Docker 镜像瘦身的核心就四句话:多阶段构建分离编译与运行、只拷贝最终产物、选更精简的基础镜像、用好层缓存与 .dockerignore。把这套 Dockerfile 模板沉淀到项目里,CI 构建出的镜像更小、部署更快、也更安全。镜像大小不是小事,它直接决定了你的发布效率与线上攻击面。
- 点赞
- 收藏
- 关注作者
评论(0)