Docker 镜像体积不断增长,常见原因并不只是基础镜像偏大,还可能是同一文件在不同层中被反复写入、压缩包解压后未及时清理,或文件删除发生在后续层。由于镜像层具有不可变特性,后续执行删除操作通常只是让文件在容器视图中不可见,并不会从早期层中真正移除。🔍 Dive 可以逐层展示文件变化,帮助我们定位重复文件和浪费空间,再有针对性地调整 Dockerfile。
一、Dive 能解决什么问题
Dive 是一个用于分析 Docker、OCI 镜像层的开源工具。它能够列出每一层对应的构建指令、层大小以及文件系统变化,并对新增、修改和删除的文件进行区分。界面中还会显示镜像效率和估算的浪费空间,便于快速发现重复写入、移动文件或删除不彻底等问题。相关功能和安装方式可查看 Dive 官方项目。
需要注意的是,效率评分适合用来发现线索,而不是判断镜像质量的唯一标准。某些文件确实需要在不同构建阶段重新生成,基础镜像中的共享内容也可能让结果看起来比较复杂。因此,分析时应将 Dive 的结果与 Dockerfile、应用运行依赖和安全要求结合起来。
二、安装并开始分析镜像
在 macOS 上可以执行 brew install dive。如果不希望在主机安装二进制程序,也可以通过容器方式运行:
docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock docker.io/wagoodman/dive:latest your-image:tag
先使用 docker build -t demo-app:before . 构建待分析镜像,然后执行 dive demo-app:before。Dive 打开后,左侧显示镜像层及其构建命令,右侧显示选中层对应的文件树。切换不同层,可以观察哪些目录突然增大,以及同一路径下的文件是否被重复修改。
三、如何定位重复文件和无效层
1. 从体积异常的层开始
优先检查左侧尺寸明显偏大的层,并结合对应的 RUN、COPY 或 ADD 指令判断来源。例如,一个 COPY 指令突然增加大量内容,可能是把测试报告、本地依赖、编辑器缓存、Git 目录或构建产物一并复制进了镜像。此时应完善 .dockerignore,而不是在下一层再删除这些文件。
2. 检查同一路径的多次修改
如果依赖目录、应用包或配置文件在连续多个层中被修改,旧版本内容仍可能保留在之前的层里。常见场景包括先复制完整项目,再安装依赖,之后又覆盖同一目录;或者多次解压新版本文件到相同位置。📦 应尽量减少对大型目录的反复覆盖,并让依赖清单与业务源码分开复制。
3. 识别“先创建、后删除”
下面这种构建逻辑容易产生浪费:某一层下载安装包并解压,下一层再删除压缩包和缓存。虽然最终容器中看不到这些文件,但它们仍存在于早期镜像层。正确做法是把下载、使用和清理放进同一个 RUN 指令,并使用 && 串联,确保临时内容没有进入最终层。
四、根据分析结果优化 Dockerfile
- 合并相关操作:软件安装、缓存清理和临时文件删除应在同一 RUN 中完成,避免无效数据固化到上一层。
- 完善 .dockerignore:排除 .git、日志、覆盖率报告、测试缓存、本地构建目录和不参与运行的文档。
- 调整 COPY 顺序:先复制依赖清单并安装依赖,再复制变化频繁的业务代码,可减少无意义的缓存失效。
- 使用精简基础镜像:在兼容应用和运维需求的前提下,选择可信且较小的运行时镜像,不要只追求体积而忽略系统库兼容性。
- 采用多阶段构建:在构建阶段保留编译器、开发依赖和源代码,最终阶段只复制可执行文件及必要运行资源。Docker 对多阶段构建的说明可参考 官方文档。
例如,Go、Java、Node.js 前端项目通常都需要较完整的构建环境,但生产运行阶段未必需要编译器、包管理缓存和测试工具。多阶段构建通过多个 FROM 划分环境,再使用 COPY --from=builder 复制最终产物,可以从结构上避免把构建工具带入生产镜像。Docker 也建议通过多阶段构建、合适的基础镜像和合理的构建上下文控制镜像内容,详见 镜像构建最佳实践。
五、优化后进行对比验证
- 将原镜像保留为 demo-app:before,优化后构建 demo-app:after。
- 分别运行 Dive,比较总大小、浪费空间、主要大层和重复文件路径。
- 执行应用启动、健康检查和必要的自动化测试,确认优化没有删除运行依赖。
- 使用 docker history 辅助核对各层大小,避免只看镜像列表中的最终数字。
- 在 CI 中运行 CI=true dive demo-app:after,通过配置阈值阻止浪费空间异常增长的镜像进入发布流程。🚦
镜像优化不应变成一次性的“减肥活动”。当依赖升级、构建脚本改变或基础镜像切换后,重复文件可能再次出现。建议把镜像大小、Dive 分析结果和应用测试共同纳入持续集成,并记录每次发布的变化趋势。
总结
Dive 的核心价值,是把抽象的镜像层转换成可逐层检查的文件变化。面对镜像体积异常,应先定位大层、重复修改和无效删除,再通过合并命令、完善 .dockerignore、调整 COPY 顺序、精简基础镜像及多阶段构建进行治理。最后使用优化前后的镜像做同口径比较,并验证应用功能与运行依赖,才能在缩小镜像的同时保持构建稳定、结果可复现和生产环境可靠。✅