镜像从 1.2G 瘦到 120M:多阶段构建与层缓存
一个 Node 项目直接 FROM node 打完包 1.2G,其中 800M 是构建工具和缓存,运行时根本用不到。
多阶段构建把「构建环境」和「运行环境」分开,最终镜像只保留产物和运行时依赖。
先说结论
构建与运行分两阶段,运行阶段用精简基础镜像,层按变更频率排序。
- builder 阶段装构建工具,运行阶段只拷产物
- 优先 slim/distroless,注意 glibc 依赖
- RUN 合并并用 && 清理缓存
- .dockerignore 排除 node_modules 与 .git
先把问题说清楚
镜像层是叠加的:这一层删掉的文件,在上一层仍然占空间。所以 apt install 与 rm 必须写在同一个 RUN 里。
多阶段构建的另一个好处是:运行镜像里不含编译器和源码,攻击面显著变小。
具体怎么做
按顺序执行,每步都验证完再进下一步:
- 拆分 builder 与 runtime 两个阶段
- builder 里完成依赖安装与编译
- runtime 只 COPY --from=builder 产物
- 选择合适基础镜像:alpine(musl)或 debian-slim(glibc)
- 按「最不常变」到「最常变」排序 COPY 与 RUN,利用缓存
参数与成本对照
把关键数值摊开,方便判断该选哪条路:
| 做法 | 体积影响 | 风险 |
|---|---|---|
| 多阶段构建 | 通常减 60%-90% | 需确认运行依赖 |
| alpine 基础镜像 | 减 100M 以上 | musl 兼容性 |
| 合并 RUN + 清理 | 减缓存层 | 可读性下降 |
| distroless | 最小 | 无 shell,排障难 |

几个容易踩的坑
下面这些是最常见的错误做法:
- alpine 里跑 glibc 编译的二进制:报 not found(其实缺动态库)
- 先 COPY . 再 npm install:每次改代码都重装依赖
- 忘记 .dockerignore:把 node_modules 和 .git 打进构建上下文
- 在运行镜像里留 gcc 与源码
最后补两句
镜像瘦身是长期收益:拉取更快、漏洞更少、回滚更迅速。
工具是手段,理解机制才是能力。命令记不住没关系,知道「该看什么」就够了。
评论 (3)
照着做了一遍,确实少走弯路。第二步最容易漏,建议顺手加个检查清单。
比「一键脚本」有用多了,知道为什么比知道怎么做重要。
补充一句:不同版本行为会变,动手前先确认自己的版本号。
发表评论