Docker Scratch 镜像:从零开始的容器构建艺术

Docker Scratch 镜像:从零开始的容器构建艺术

什么是 Scratch 镜像?

在 Docker Hub 的官方镜像仓库中,有一个名为 scratch 的特殊镜像。它不是一个可以下载和运行的普通镜像,而是一个虚拟的空白基础镜像——它不包含任何文件、目录、操作系统、Shell 或运行时环境。

Scratch 镜像是 Docker 中最小的镜像,是所有其他镜像的“祖先”。

你无法通过 docker pull scratchdocker run scratch 来操作它。scratch 的唯一用途,是在 Dockerfile 中作为 FROM 指令的基础,向 Docker 构建系统发出信号:接下来的每一层都将从零开始构建

核心特性

1. 完全空白

scratch 镜像不包含任何内容——没有 /bin 目录,没有 Shell,没有 PATH 环境变量,没有任何系统工具或库。它本质上是一个“空画布”,等待你放入唯一需要的东西:你的应用程序。

2. 无法独立运行

由于 scratch 没有任何可执行文件或 Shell,你无法直接运行它。docker run -it scratch 这样的命令注定会失败。

3. 仅可在 Dockerfile 中引用

scratch 的存在意义就是在 Dockerfile 中被引用。它是构建最小化镜像的起点,而非终点。

为什么使用 Scratch?

:bullseye: 极致的镜像体积

使用 scratch 构建的镜像,大小就等于你的应用程序二进制文件本身。没有任何操作系统、运行时或额外组件的开销。

例如,一个用 Go 编写的 Hello World 程序,编译后只有几 MB,放入 scratch 镜像后,整个镜像也只有几 MB。相比之下,一个基于完整 Ubuntu 的镜像可能动辄上百 MB。

:locked: 更高的安全性

scratch 镜像中没有任何操作系统组件、Shell、包管理器或额外的软件包。这意味着攻击面被降至最低——没有 bash 漏洞、没有 OpenSSL 漏洞、没有多余的二进制文件可以被利用。

:package: 更好的可移植性

极小的镜像体积意味着更快的拉取和推送速度,以及更少的存储和带宽消耗。对于微服务架构和云原生部署,这尤为重要。

如何使用 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。

注意事项

  1. 使用绝对路径scratch 镜像没有 PATH 环境变量,因此 CMDENTRYPOINT 必须使用绝对路径。

  2. CMD 必须使用 JSON 数组格式:如果使用 CMD ["/app"] 这样的 JSON 数组格式,Docker 会直接执行该程序。如果使用 CMD /app 这样的字符串格式,Docker 会尝试用 Shell 去启动,而 scratch 没有 Shell,会导致失败。

  3. 确保程序可执行:复制到镜像中的二进制文件必须具有可执行权限。

适用场景总结

场景 是否推荐
Go 微服务 :white_check_mark: 强烈推荐
Rust 应用 :white_check_mark: 强烈推荐
静态编译的 C/C++ 程序 :white_check_mark: 推荐
需要 Python/Node.js 的应用 :cross_mark: 不适用
需要 Shell 脚本的应用 :cross_mark: 不适用
需要 CA 证书或系统库的应用 :cross_mark: 不适用(除非自行添加)

延伸阅读:与 Distroless 的对比

除了 scratch,Google 维护的 Distroless 镜像也是一个追求极简和安全的选择。Distroless 只包含应用程序及其运行时依赖,不包含 Shell 和包管理器。

两者的区别在于:

  • Scratch:完全空白,适合静态编译的二进制文件
  • Distroless:包含必要的运行时依赖(如 glibc、CA 证书),适合需要这些依赖但又不想包含完整操作系统的场景

结语

scratch 镜像是一个强大而专业的工具。它并非适用于所有场景的通用基础镜像,而是为追求极致性能最高安全性的特定场景(如云原生微服务)而设计的。

当你需要一个“除了我的程序,什么都不带”的容器镜像时,scratch 就是那个最完美的起点——从零开始,只装下你真正需要的东西。