在 Docker 的世界里,镜像的“层(Layer)”机制既是优势也是痛点。每当你执行一次 RUN 指令,Docker 就会创建一个新的只读层。虽然这有利于缓存,但如果你在同一个 Dockerfile 中先安装了大量依赖,随后又在另一个 RUN 指令中将其删除,那么这些被删除的文件依然存在于之前的镜像层中,占据着宝贵的磁盘空间。
这就是为什么你的镜像明明只运行一个简单的 Go 程序,体积却高达 800MB 的原因。而 docker-squash 正是为了解决这个问题而生的利器。
什么是 docker-squash?
docker-squash 是一个用 Go 语言编写的实用工具,它的核心功能是将 Docker 镜像的多个层“压缩(Squash)”成一个单一的层。
简单来说,它会扫描镜像的所有层,将最终状态的文件系统提取出来,丢弃掉那些在后续层中被删除或覆盖的文件,从而生成一个极简的镜像。这相当于把一个复杂的“历史修改记录”直接快进到了“最终结果”。
为什么你需要它?
1. 消除“幽灵文件”
在传统的 Dockerfile 编写中,我们经常看到这样的模式:
RUN apt-get update && apt-get install -y build-essential RUN go build -o app main.go RUN apt-get purge -y build-essential && apt-get autoremove -y
尽管第三行删除了编译工具,但 build-essential 依然留在第一层中。使用 docker-squash 后,这些中间过程产生的冗余文件会被彻底剔除。
2. 提升传输与部署速度
镜像越小,从 Registry 推送(Push)和拉取(Pull)的速度就越快。在 CI/CD 流水线中,这能显著缩短部署周期。
3. 简化镜像结构
将数十个层压缩为一两个层,可以减少 Docker 存储驱动(如 Overlay2)在挂载文件系统时的开销,在某些极端场景下能提升启动性能。
快速上手指南
安装
由于 docker-squash 是用 Go 编写的,你可以直接通过 Go 安装:
go install github.com/jwilder/docker-squash@latest
或者使用 Docker 运行该工具(无需安装到本地):
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock jwilder/docker-squash
基础使用实例
假设你有一个名为 my-heavy-app:latest 的镜像,它包含了大量的构建依赖。
执行压缩命令:
docker-squash -t my-heavy-app:squashed my-heavy-app:latest
-t: 指定压缩后新镜像的标签。my-heavy-app:latest: 目标待压缩镜像。
执行完成后,你可以通过 docker images 对比两个镜像的大小,通常你会发现 squashed 版本的体积大幅下降。
进阶实战:结合多阶段构建(Multi-stage Build)
虽然 docker-squash 非常强大,但现代 Docker 最佳实践建议将其与“多阶段构建”结合使用。
场景:构建一个高性能 Go 应用
编写如下 Dockerfile:
# 第一阶段:构建环境 FROM golang:1.21-alpine AS builder WORKDIR /app COPY . . RUN apk add --no-cache git gcc musl-dev # 安装构建依赖 RUN go build -o myapp main.go # 第二阶段:运行环境 FROM alpine:latest WORKDIR /root/ COPY --from=builder /app/myapp . # 模拟一些不规范的操作,产生冗余层 RUN apk add --no-cache curl RUN curl -O https://example.com/big-config-file.tar.gz RUN tar -xzvf big-config-file.tar.gz RUN rm big-config-file.tar.gz # 虽然删了,但层还在 CMD ["./myapp"]
如果不使用 squash:
即使你执行了 rm big-config-file.tar.gz,该压缩包依然存在于镜像的某一层中。
使用 squash 优化:
1. 构建镜像:docker build -t my-go-app .
2. 压缩镜像:docker-squash -t my-go-app:slim my-go-app
此时,big-config-file.tar.gz 将在最终镜像中彻底消失,只保留解压后的文件。
docker-squash vs Docker 原生 --squash 标志
你可能会发现 docker build 命令本身带有一个 --squash 参数。那么为什么还要用 docker-squash 这个项目?
| 特性 | Docker 原生 --squash |
docker-squash 项目 |
|---|---|---|
| 可用性 | 需要开启 Docker 守护进程的 experimental 模式 |
直接运行,无需修改 Docker 配置 |
| 灵活性 | 仅在构建时生效 | 可以对任何已存在的镜像进行后处理 |
| 控制力 | 全局压缩 | 支持更细粒度的层控制(通过特定参数) |
| 依赖 | 绑定在 Docker Engine 版本 | 独立二进制文件,易于集成到 CI 脚本 |
注意事项与潜在风险
在使用 docker-squash 之前,你需要了解以下几点:
- 破坏缓存共享:Docker 的层级机制允许不同镜像共享相同的基础层(例如多个镜像都基于
ubuntu:20.04)。一旦你将镜像 squash 掉,它就变成了一个独立的单一层,无法再与其他镜像共享基础层,这可能会导致在同一台宿主机上存储多个 squash 镜像时反而占用更多空间。 - 丢失历史记录:
docker history将不再显示详细的构建步骤,因为所有步骤都被合并成了一层。 - 构建时间:Squash 过程需要导出文件系统并重新导入,对于超大型镜像,这会增加一定的处理时间。
总结
docker-squash 是一个简单而高效的工具,它为那些无法通过优化 Dockerfile 彻底精简镜像的开发者提供了一把“手术刀”。通过将冗余的中间层剔除,它能显著降低镜像体积,优化分发效率。
如果你发现你的镜像在经过多阶段构建后依然臃肿,或者你必须在镜像中执行一些“安装 \(\rightarrow\) 使用 \(\rightarrow\) 删除”的复杂操作,那么 docker-squash 将是你工具箱中不可或缺的一员。



还没有评论,来说两句吧...