Docker Scratch 镜像:从零开始的容器构建艺术
什么是 Scratch 镜像?
在 Docker Hub 的官方镜像仓库中,有一个名为 scratch 的特殊镜像。它不是一个可以下载和运行的普通镜像,而是一个虚拟的空白基础镜像——它不包含任何文件、目录、操作系统、Shell 或运行时环境。
Scratch 镜像是 Docker 中最小的镜像,是所有其他镜像的“祖先”。
你无法通过 docker pull scratch 或 docker run scratch 来操作它。scratch 的唯一用途,是在 Dockerfile 中作为 FROM 指令的基础,向 Docker 构建系统发出信号:接下来的每一层都将从零开始构建。
核心特性
1. 完全空白
scratch 镜像不包含任何内容——没有 /bin 目录,没有 Shell,没有 PATH 环境变量,没有任何系统工具或库。它本质上是一个“空画布”,等待你放入唯一需要的东西:你的应用程序。
2. 无法独立运行
由于 scratch 没有任何可执行文件或 Shell,你无法直接运行它。docker run -it scratch 这样的命令注定会失败。
3. 仅可在 Dockerfile 中引用
scratch 的存在意义就是在 Dockerfile 中被引用。它是构建最小化镜像的起点,而非终点。
为什么使用 Scratch?
极致的镜像体积
使用 scratch 构建的镜像,大小就等于你的应用程序二进制文件本身。没有任何操作系统、运行时或额外组件的开销。
例如,一个用 Go 编写的 Hello World 程序,编译后只有几 MB,放入 scratch 镜像后,整个镜像也只有几 MB。相比之下,一个基于完整 Ubuntu 的镜像可能动辄上百 MB。
更高的安全性
scratch 镜像中没有任何操作系统组件、Shell、包管理器或额外的软件包。这意味着攻击面被降至最低——没有 bash 漏洞、没有 OpenSSL 漏洞、没有多余的二进制文件可以被利用。
更好的可移植性
极小的镜像体积意味着更快的拉取和推送速度,以及更少的存储和带宽消耗。对于微服务架构和云原生部署,这尤为重要。
如何使用 Scratch?
核心前提:静态编译
使用 scratch 有一个硬性要求:你的应用程序必须是完全静态编译的,不依赖任何外部动态链接库(如 C 库 glibc)。
适合使用 scratch 的语言包括:
- Go:原生支持静态编译
- Rust:默认生成静态二进制
- C/C++:需使用
-static编译选项
不适合的场景包括:
- 需要 Python、Node.js、Java 等运行时的应用
- 依赖系统库(如 OpenSSL、CA 证书)的程序
- 需要 Shell 执行脚本的场景
实战示例:多阶段构建
最推荐的使用方式是利用 Docker 的多阶段构建(Multi-stage Build) 功能。在第一阶段使用完整的编译环境构建程序,在第二阶段将编译好的二进制文件复制到 scratch 镜像中。
# 第一阶段:使用完整的 Go 镜像进行编译
FROM golang:1.21-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/app ./cmd/app
# 第二阶段:使用 scratch 作为最终运行镜像
FROM scratch
# 将编译好的二进制文件复制到 scratch 镜像中
COPY --from=builder /bin/app /bin/app
# 使用绝对路径指定启动命令(scratch 没有 PATH 环境变量)
ENTRYPOINT ["/bin/app"]
通过这种方式,最终镜像的大小可以从 150MB 骤降至仅 8.45MB。
注意事项
-
使用绝对路径:
scratch镜像没有PATH环境变量,因此CMD和ENTRYPOINT必须使用绝对路径。 -
CMD 必须使用 JSON 数组格式:如果使用
CMD ["/app"]这样的 JSON 数组格式,Docker 会直接执行该程序。如果使用CMD /app这样的字符串格式,Docker 会尝试用 Shell 去启动,而scratch没有 Shell,会导致失败。 -
确保程序可执行:复制到镜像中的二进制文件必须具有可执行权限。
适用场景总结
| 场景 | 是否推荐 |
|---|---|
| Go 微服务 | |
| Rust 应用 | |
| 静态编译的 C/C++ 程序 | |
| 需要 Python/Node.js 的应用 | |
| 需要 Shell 脚本的应用 | |
| 需要 CA 证书或系统库的应用 |
延伸阅读:与 Distroless 的对比
除了 scratch,Google 维护的 Distroless 镜像也是一个追求极简和安全的选择。Distroless 只包含应用程序及其运行时依赖,不包含 Shell 和包管理器。
两者的区别在于:
- Scratch:完全空白,适合静态编译的二进制文件
- Distroless:包含必要的运行时依赖(如 glibc、CA 证书),适合需要这些依赖但又不想包含完整操作系统的场景
结语
scratch 镜像是一个强大而专业的工具。它并非适用于所有场景的通用基础镜像,而是为追求极致性能和最高安全性的特定场景(如云原生微服务)而设计的。
当你需要一个“除了我的程序,什么都不带”的容器镜像时,scratch 就是那个最完美的起点——从零开始,只装下你真正需要的东西。