- CLI
- 开发工具
【免费下载链接】cli
The Docker CLI
导读
本文以 docs/reference/commandline/image_build.md 为骨架,系统讲解 Docker CLI 中docker image build(即 legacy builder)的完整行为:从全部命令行选项、构建上下文(build context)的传输与安全边界,到--isolation、--security-opt、--squash三个 legacy builder 专属特性的深入用法,并结合 cli/command/image/build.go 及上下文处理源码,剖析其底层实现与调用链。读完本文,你将理解 legacy builder 与 BuildKit 的行为差异,掌握在 Windows 容器场景下正确构建镜像的实战能力。
[!IMPORTANT] 本文描述的
docker build属于legacy(pre-BuildKit)构建后端。Docker CLI 默认使用 Buildx(BuildKit),legacy builder 仅在构建 Windows 容器或显式设置DOCKER_BUILDKIT=0时生效。与 BuildKit 共有的通用特性(如--tag、--target),请参考docker buildx build的文档。
命令概览:别名的多重身份
docker image build是构建命令的"本体",但它同时拥有三个别名,全部指向同一实现:
docker image builddocker builddocker builder build
在 cli/command/image/build.go 中,命令的定义为build [OPTIONS] PATH | URL | -,并强制要求恰好一个位置参数(cli.ExactArgs(1)),这个位置参数就是构建上下文的来源。命令的 Annotations 中记录了完整的别名列表(cli/command/image/build.go#L107-L110),这与文档表格完全一致。
完整选项表:legacy builder 的全部参数
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
--add-host | list | 添加自定义 host 到 IP 的映射(host:ip) | |
--build-arg | list | 设置构建时变量 | |
--cache-from | stringSlice | 作为缓存来源的镜像 | |
--cgroup-parent | string | 为构建期间的RUN指令设置父 cgroup | |
--compress | bool | 使用 gzip 压缩构建上下文 | |
--cpu-period | int64 | 0 | 限制 CPU CFS(完全公平调度器)周期 |
--cpu-quota | int64 | 0 | 限制 CPU CFS(完全公平调度器)配额 |
-c,--cpu-shares | int64 | 0 | CPU 份额(相对权重) |
--cpuset-cpus | string | 允许执行的 CPU 集合(如0-3、0,1) | |
--cpuset-mems | string | 允许执行的内存节点集合(如0-3、0,1) | |
-f,--file | string | Dockerfile 名称(默认是PATH/Dockerfile) | |
--force-rm | bool | 始终移除中间容器 | |
--iidfile | string | 将镜像 ID 写入指定文件 | |
--isolation | string | 容器隔离技术(详见下文) | |
--label | list | 为镜像设置元数据 | |
-m,--memory | bytes | 0 | 内存限制 |
--memory-swap | bytes | 0 | 交换限制(等于内存加交换,-1表示不限交换) |
--network | string | default | 为构建期间的RUN指令设置网络模式 |
--no-cache | bool | 构建时不要使用缓存 | |
--platform | string | 当服务端支持多平台时设置平台 | |
--pull | bool | 总是尝试拉取镜像的更新版本 | |
-q,--quiet | bool | 抑制构建输出,成功时仅打印镜像 ID | |
--rm | bool | true | 构建成功后移除中间容器 |
--security-opt | stringSlice | 安全选项(详见下文) | |
--shm-size | bytes | 0 | /dev/shm的大小 |
--squash | bool | 将新构建的层压缩为单个新层(实验特性) | |
-t,--tag | list | 以name:tag格式指定名称和标签 | |
--target | string | 设置要构建的目标构建阶段 | |
--ulimit | ulimit | Ulimit 选项 |
以上选项与 cli/command/image/build.go 中注册的 flags 一一对应。其中几个值得注意的实现细节:
--rm的默认值是true:flags.BoolVar(&options.rm, "rm", true, ...)说明成功构建后默认清理中间容器;--force-rm则进一步强制清理(包括失败时的中间容器)。--platform的默认值来自环境变量:flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), ...)(build.go)。也就是说,你可以通过设置DOCKER_DEFAULT_PLATFORM环境变量来提供默认平台值。--squash被标记为 experimental:flags.SetAnnotation("squash", "experimental", nil)(build.go)。--disable-content-trust已废弃:源码中该 flag 被显式标记为 deprecated,说明 Docker 内容信任支持已被移除(build.go)。--network选项要求 Docker API 版本不低于 1.25,--platform要求不低于 1.38,--squash要求不低于 1.25。
legacy builder 与 BuildKit:为什么还要用它?
定位与适用场景
legacy builder 从 Dockerfile 构建镜像的方式,是依次执行一系列提交(commit)操作——即每执行一个 Dockerfile 指令就生成一个新的镜像层,这一机制与docker commit类似(参见 container_commit.md)。相比之下,BuildKit 在构建效率和特性丰富度上全面领先,因此 legacy builder 已被弃用,唯一的例外场景是构建 Windows 容器——因为 BuildKit 尚未在 Windows 平台上达到完整的特性对等。
默认情况下docker build走的是 Buildx(BuildKit),除非满足以下任一条件:
- 你在Windows 容器模式下运行 Docker Engine;
- 你显式设置了环境变量
DOCKER_BUILDKIT=0来退出 BuildKit。
从源码角度,cli/command/image/build.go 中的imageBuildOptions函数显式设置了Version: buildtypes.BuilderV1,这正是在 API 层面声明使用 legacy(Builder V1)构建器的关键证据——它把 CLI 侧收集到的所有选项映射为 Docker Engine 的ImageBuildOptions请求参数。
行为差异:只在 legacy builder 中不同的部分
本文只覆盖 legacy builder独有、或与 BuildKit行为不一致的内容。对于两者共通的选项(如--tag、--target、--build-arg等),其行为与docker buildx build一致,本文不展开赘述。
构建上下文:legacy builder 的传输模型
上下文是什么
构建上下文(build context)是调用构建命令时传入的位置参数。在下面的例子中,上下文是.,即当前工作目录:
$ docker build .从源码看,构建上下文的类型由 context_detect.go 中的DetectContextType函数判定,支持四类来源:
stdin(-):从标准输入读取 tar 归档或 Dockerfile;local:本地目录(默认方式);git:Git URL,命令会先git clone到临时目录(context.go);remote:远程 URL,作为 Dockerfile 或 tar 归档下载(context.go)。
关键差异:整包发送 vs 按需传输
legacy builder 将整个构建上下文完整发送给 daemon。它不会预先计算构建真正需要哪些文件——即使你只用到了上下文里的一小部分文件,大上下文也会导致传输耗时很长。而 BuildKit 只传输构建实际用到的文件。
因此,使用 legacy builder 时,精心设计上下文内容格外重要:
- 只把构建必需的目录或文件指定为上下文;
- 使用
.dockerignore文件排除不需要发送的文件和目录。
.dockerignore的处理逻辑位于 dockerignore.go:ReadDockerignore读取上下文目录下的.dockerignore并解析出排除模式列表。随后在runBuild中,这些排除模式会与上下文目录一起交给archive.TarWithOptions(build.go)打包成 tar 流发送给 daemon——这正是"排除文件不进入上下文"的实现机制。
一个值得了解的细节:.dockerignore和指定的 Dockerfile 本身即使被排除规则命中,也会通过TrimBuildFilesFromExcludes(dockerignore.go)以!否定模式被"保回来",确保它们仍存在于上下文中供 daemon 使用。
访问构建上下文之外的路径
legacy builder 在 Dockerfile 中使用相对路径访问构建上下文之外的文件时会直接报错:
FROM alpine COPY ../../some-dir .$ docker build . ... Step 2/2 : COPY ../../some-dir . COPY failed: forbidden path outside the build context: ../../some-dir ()而 BuildKit 会剥离那些穿越构建上下文边界的相对路径前缀:上面的COPY ../../some-dir .在 BuildKit 下会被等价地解析为COPY some-dir .。
这一点在 CLI 源码中也有呼应:runBuild会检查 Dockerfile 的相对路径是否以../开头,若 Dockerfile 本身位于构建上下文之外,则单独读取该文件并注入上下文(build.go);Git 上下文场景下若 Dockerfile 必须位于上下文内,否则报错(context.go)。测试用例 build_test.go(TestRunBuildDockerfileOutsideContext)验证了"上下文外的 Dockerfile 也能被正确注入构建请求"的行为。
实战示例:legacy builder 专属特性
--isolation:指定容器隔离技术
该选项在 Windows 上运行 Docker 容器时非常有用。--isolation=<value>用于设置容器的隔离技术:
- 在Linux上,唯一支持的值是
default(使用 Linux namespaces); - 在Microsoft Windows上,可以指定以下值:
| 值 | 说明 |
|---|---|
default | 使用 Docker daemon 的--exec-opt指定的值。若 daemon 未指定隔离技术,Microsoft Windows 默认使用process |
process | 仅使用命名空间隔离 |
hyperv | 基于 Hyper-V 虚拟机分区隔离 |
在源码中,--isolation选项通过container.Isolation(options.isolation)转换后写入构建请求(build.go),是 Windows 容器构建路径上直接影响容器运行时隔离级别的参数。
--security-opt:可选安全选项
该 flag仅在 Windows 上运行的 daemon 中受支持,且只支持credentialspec选项。credentialspec的取值格式必须是file://spec.txt或registry://keyname,用于为 Windows 容器指定凭据规范。
在源码中它作为stringSlice收集并直接透传给构建请求的SecurityOpt字段(build.go),不对值做额外校验——格式合法性由 daemon 侧负责。
--squash:合并镜像层(实验特性)
[!NOTE]
--squash属于实验特性,不应被视为稳定功能。
原理概述
镜像构建完成后,该 flag 会把新产生的层合并成一个单一新层,生成一个新镜像。关键点:
- Squashing不会破坏任何已有镜像,而是创建一个内容等价、但层被合并的新镜像;
- 效果上相当于所有
Dockerfile指令看起来是在一个层中完成的; --squash保留构建缓存(缓存中的各层原样保留,squash 只是额外生成一份合并后的镜像)。
什么时候该用(或不该用)
有益的场景:Dockerfile 产生了多层且多次修改同一批文件,例如一个步骤创建文件、另一个步骤又删除它——squash 可以消除这些"层间冗余"。
可能有害的场景:
- 多层的镜像被拉取时,daemon 可以并行拉取各层,并且允许镜像之间共享层以节省空间;squash 会破坏这种层共享能力;
- 单一层在下载时无法并行化,提取也更慢,可能对性能产生负面影响。
更好的替代方案:对于大多数场景,多阶段构建(multi-stage builds)是更优选择——它提供更细粒度的构建控制,并能利用 builder 未来的优化能力。
已知限制
- 合并层后,生成的镜像无法与其他镜像共享层(基础镜像的共享仍然支持),可能显著占用更多空间;
- 由于同时保存"含完整缓存层的构建缓存镜像"和"合并后的版本"两份拷贝,磁盘占用可能明显增加;
- 合并可能产生更小的镜像,但单层提取时间更长、无法并行下载单层,性能上可能有负面影响;
- 若被 squash 的镜像没有对文件系统做任何修改(例如 Dockerfile 只包含
ENV指令),squash 步骤会失败。
前置条件:启用实验模式
本页示例基于 Docker 23.03 的 experimental 模式(原文档示例输出展示的是 Docker 28.5.1 的版本信息,二者仅作运行环境参考)。启用方式有两种:
- 启动 Docker daemon 时使用
--experimentalflag; - 在
daemon.json配置文件中设置"experimental": true。
默认情况下实验模式是禁用的。可以使用docker version查看当前配置,检查Engine部分的Experimental行:
Client: Docker Engine - Community Version: 28.5.1 API version: 1.51 Go version: go1.24.8 Git commit: e180ab8 Built: Wed Oct 8 12:16:17 2025 OS/Arch: darwin/arm64 Context: desktop-linux Server: Docker Engine - Community Engine: Version: 28.5.1 API version: 1.51 (minimum version 1.24) Go version: go1.24.8 Git commit: f8215cc Built: Wed Oct 8 12:18:25 2025 OS/Arch: linux/arm64 Experimental: true [...]Experimental: true即表示实验模式已开启。
实操:用--squash构建镜像
以下是一个使用--squash构建的完整示例。先准备如下Dockerfile:
FROM busybox RUN echo hello > /hello RUN echo world >> /hello RUN touch remove_me /remove_me ENV HELLO=world RUN rm /remove_me注意此 Dockerfile 的设计意图:/hello被两次写入、remove_me被创建后又删除——正是前面提到的"多层修改同一批文件"的典型场景。
使用--squash构建名为test的镜像:
$ docker build --squash -t test .构建完成后,查看镜像历史(history)。可以看到:各层名称显示为<missing>,且出现了一个COMMENT为merge的新层——这就是被合并出来的层:
$ docker history test IMAGE CREATED CREATED BY SIZE COMMENT 4e10cb5b4cac 3 seconds ago 12 B merge sha256:88a7b0112a41826885df0e7072698006ee8f621c6ab99fca7fe9151d7b599702 to sha256:47bcc53f74dc94b1920f0b34f6036096526296767650f223433fe65c35f149eb <missing> 5 minutes ago /bin/sh -c rm /remove_me 0 B <missing> 5 minutes ago /bin/sh -c #(nop) ENV HELLO=world 0 B <missing> 5 minutes ago /bin/sh -c touch remove_me /remove_me 0 B <missing> 5 minutes ago /bin/sh -c echo world >> /hello 0 B <missing> 6 minutes ago /bin/sh -c echo hello > /hello 0 B <missing> 7 weeks ago /bin/sh -c #(nop) CMD ["sh"] 0 B <missing> 7 weeks ago /bin/sh -c #(nop) ADD file:47ca6e777c36a4cfff 1.113 MB注意顶部的merge层SIZE仅为 12 B——它只包含合并后的差异内容(删除了/remove_me、保留了/hello与HELLO=world),而原始中间层(如touch remove_me /remove_me创建的 0 B 占位层与rm操作)都保留在构建缓存的历史记录中。
验证镜像结果,确认:
/remove_me已被删除;/hello的内容是hello\nworld;- 环境变量
HELLO的值为world。
从源码看构建全流程
结合 cli/command/image/build.go 的runBuild函数,可以把 legacy builder 的完整调用链梳理如下:
- 校验平台:若指定了
--platform,先通过platforms.Parse校验其合法性; - 检测上下文类型:
DetectContextType判定 stdin / 本地 / Git / 远程四类来源; - 禁止双重 stdin:若同时用 stdin 作为 Dockerfile 和构建上下文,直接报错
can't use stdin for both build context and dockerfile; - 准备上下文:本地目录会先读取
.dockerignore、校验目录可读性,再打包成 tar 流;Git 上下文先克隆到临时目录;远程 URL 则下载为 Dockerfile 或 tar; - 压缩与进度上报:
--compress时用 gzip 压缩上下文;--quiet时所有输出被缓冲,最终只打印镜像 ID; - 构造请求:
imageBuildOptions汇总所有选项,其中Version: BuilderV1显式指定 legacy 构建后端,同时附带来自config.json的认证信息; - 发送与解析:调用
ImageBuildAPI,通过jsonstream.Display处理 daemon 返回的 JSON 流,并从aux消息中解析出最终的镜像 ID; - 收尾:
--quiet时输出镜像 ID;--iidfile时将镜像 ID 写入指定文件(若服务端未返回镜像 ID 则报错)。
这套流程解释了文档中的多个行为:为什么-q只输出镜像 ID、为什么--iidfile能在脚本流水线中拿到构建结果、为什么 legacy builder 必须先完整打包上下文再传输——一切都源于这个"客户端整包打包 + API 透传"的架构。
小结
docker image build作为 legacy builder 的命令入口,在 Docker CLI 中仍承担着 Windows 容器构建的职责。使用它的关键在于牢记三点:默认会被 BuildKit 取代(除非 Windows 容器模式或DOCKER_BUILDKIT=0)、构建上下文会整包发送(必须善用.dockerignore)、--isolation/--security-opt/--squash是其专属特性。理解这些差异与底层源码实现,能帮助你在正确的场景中做出正确的构建选择。
- CLI
- 开发工具
【免费下载链接】cli
The Docker CLI
相关推荐
Docker CLI 深度解析:docker-build 命令完全指南
Docker CLI 深度解析:docker build 命令完全指南 前言 Docker 作为现代容器化技术的代表,其镜像构建功能是开发者日常工作中不可或缺的
CLI开发工具LaTeX模板如何让科研写作效率提升300%?5个实战技巧揭秘
LaTeX模板如何让科研写作效率提升300%?5个实战技巧揭秘 还在为科研文档排版耗费大量时间而烦恼吗?国家自然科学基金申请、学术论文撰写、项目报告整理……这些
CLI开发工具CMake Cookbook 项目教程
CMake Cookbook 项目教程 1. 项目的目录结构及介绍 cmake cookbook/ ├── AUTHORS.md ├── CHANGELOG.m
CLI开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考