Docker 镜像瘦身的四个层次:从 1.2G 到 200M
一个 Node.js 服务的镜像做到了 1.2G,这在生产里很常见。拉取慢、推送慢、扫描慢,出了漏洞还得把所有层重新打一遍。
镜像瘦身有明确的方法论。按见效快慢排四个层次,做完通常能压到 200M 以内。
第一层:换基础镜像(收益最大,改动最小)
体积的大头几乎总在基础镜像上。
基础镜像 | 典型大小
node:latest | 1.1 GB
node:18 | 1.0 GB
node:18-slim | 220 MB
node:18-alpine | 130 MB
从 node:18 换成 node:18-slim,一行改动,体积掉 80%。
但有代价,要提前想清楚:
- alpine 用的是 musl libc,不是 glibc。 有些 npm 包带原生扩展(bcrypt、sharp、canvas),在 alpine 上要重新编译,装不上就是一堆报错。
- alpine 缺一些常见工具(bash、curl、git)。构建脚本里用了就会失败。
- slim 版本删了文档和部分工具,但保留了 glibc,兼容性风险小得多。
实践建议: 先试 slim,需要极致体积再考虑 alpine,并且在 CI 里跑一遍完整启动测试再合并。
第二层:合并 RUN 指令(减少层数)
Dockerfile 的每条 RUN 都是一层。每层都带着上一层的完整快照,所以:
# 差:三层,且 apt 缓存被固化在镜像里
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# 好:一层,缓存和安装在同一层里清理掉
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/*
关键在于:安装和清理必须在同一个 RUN 里。分成两条 RUN,即使后面删了文件,被删的内容仍然存在于前一层里,镜像体积不会变小。
--no-install-recommends 也很关键,它跳过推荐依赖,通常能省几十 MB。
第三层:多阶段构建(把构建工具留在构建阶段)
这是最能体现“专业度”的一步。核心思路:构建需要的东西,运行时一个都不需要。
# 构建阶段:带着全套工具
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 运行阶段:只拷产物
FROM node:18-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json ./
CMD ["node", "dist/index.js"]
构建阶段的编译器、源码、测试依赖全部不会进入最终镜像。
进一步:只装生产依赖。
RUN npm ci --omit=dev
--omit=dev 是 npm 7+ 的写法,老版本是 --production。这一条通常能砍掉一半的 node_modules。
如果是 Go、Rust 这类编译型语言,多阶段的效果更夸张——最终镜像可以只有几 MB。
第四层:细节清理(榨最后一点)
做到这里通常已经 200M 上下了。还有几个地方能再挤一挤:
用 .dockerignore。 不加这个,COPY . . 会把 .git、node_modules、日志、测试数据全塞进去。一个 .git 目录几 MB 到几百 MB 不等。
.git
node_modules
npm-debug.log
*.md
测试数据目录
别把包管理器的缓存留在镜像里。 npm 的缓存默认在 ~/.npm,体积可能上百 MB:
RUN npm ci --omit=dev && npm cache clean --force
注意用户权限。 顺手提一句,很多教程的镜像用 root 跑进程,这是安全问题不是体积问题,但既然在改 Dockerfile,一起修掉:
RUN useradd -r -u 10001 appuser
USER appuser
怎么看瘦身效果
先看现状:
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
想知道每一层占多少(找出到底哪一层最胖):
docker history --no-trunc <镜像名> | head -20
对比前后:
docker build -t myapp:before .
# 改 Dockerfile
docker build -t myapp:after .
一个真实的顺序建议
不要一次全改,否则出问题不知道是哪一步导致的。按这个顺序,每步验证一次:
1. 换基础镜像(slim)→ 验证启动
2. 加 .dockerignore → 验证构建
3. 合并 RUN、加 --no-install-recommends → 验证
4. 改多阶段构建 → 验证
5. 只留生产依赖 → 验证
每一步都是一次提交,出问题能立刻定位。
小结
记住一句话:镜像里每一层都是不可变的历史,删东西不会让历史变小。
所以瘦身不是“事后删”,而是“一开始就不放进去”。多阶段构建之所以有效,正是因为它让不需要的东西从来没进过最终镜像。
- 点赞
- 收藏
- 关注作者
评论(0)