From 09fd556c18ca9db857161fe0a5d25b8673880203 Mon Sep 17 00:00:00 2001 From: yeasy Date: Mon, 27 Apr 2026 08:54:10 -0700 Subject: [PATCH] Refresh image CLI docs --- 04_image/4.1_pull.md | 26 ++++++++++++------------- 04_image/4.2_list.md | 22 ++++++++++----------- 04_image/4.3_rm.md | 31 +++++++++++++++++++++-------- 04_image/4.5_build.md | 8 ++++---- 04_image/4.6_other.md | 42 +++++++++++++++++++++------------------- 04_image/4.7_internal.md | 8 ++++---- 6 files changed, 77 insertions(+), 60 deletions(-) diff --git a/04_image/4.1_pull.md b/04_image/4.1_pull.md index 166dcec..83d6d63 100644 --- a/04_image/4.1_pull.md +++ b/04_image/4.1_pull.md @@ -13,24 +13,24 @@ 从镜像仓库获取镜像的命令是 `docker pull`: ```bash -docker pull [选项] [Registry地址/]仓库名[:标签] +docker pull [选项] [Registry地址/][命名空间/]仓库名[:标签|@摘要] ``` #### 镜像名称格式 -Docker 镜像名称由 Registry 地址、用户名、仓库名和标签组成。其标准格式如下: +Docker 镜像名称通常由 Registry 地址、命名空间、仓库名和标签(或摘要)组成。其常见格式如下: ```bash -docker.io / library / ubuntu : 24.04 -────┬──── ───┬─── ──┬─── ──┬── - │ │ │ │ -Registry地址 用户名 仓库名 标签 - (可省略) (可省略) +docker.io/library/ubuntu:24.04 +────┬──── ─────┬──── ──┬─── ──┬── + │ │ │ │ +Registry地址 命名空间 仓库名 标签 + (可省略) (可省略) ``` | 组成部分 | 说明 | 默认值 | |---------|------|--------| | Registry 地址 | 镜像仓库地址 | `docker.io` (Docker Hub)| -| 用户名 | 镜像所属用户/组织 | `library` (官方镜像)| +| 命名空间 | 镜像所属用户/组织;官方镜像常为 `library` | `library` (官方镜像)| | 仓库名 | 镜像名称 | 必须指定 | | 标签 | 版本标识 | `latest` | @@ -89,19 +89,19 @@ docker.io/library/ubuntu:24.04 #### 分层下载 -从输出可以看到,镜像是 **分层下载** 的: +从输出可以看到,镜像是 **按层拉取** 的: ```mermaid flowchart TD subgraph Image ["ubuntu:24.04 镜像"] direction TB - L3["第3层 c8299583700a
(已存在,跳过下载)"] - L2["第2层 be13a9d27eb8
(下载中... 完成)"] - L1["第1层 92dc2a97ff99
(下载中... 完成)"] + L3["第3层 c8299583700a
(拉取完成)"] + L2["第2层 be13a9d27eb8
(拉取完成)"] + L1["第1层 92dc2a97ff99
(拉取完成)"] L3 --- L2 --- L1 end ``` -如果本地已有相同的层,Docker 会跳过下载,节省带宽和时间。 +如果本地已有相同的层,Docker 通常会显示 `Already exists`;本例中三层都显示 `Pull complete`,表示此次拉取流程都完成了相应的层处理。 --- diff --git a/04_image/4.2_list.md b/04_image/4.2_list.md index 821b203..c7dc266 100644 --- a/04_image/4.2_list.md +++ b/04_image/4.2_list.md @@ -46,21 +46,21 @@ Docker 镜像的大小可能与我们通常理解的文件大小有所不同, | 位置 | 显示大小 | 说明 | |------|---------|------| -| Docker Hub | 29MB | 压缩后的网络传输大小 | -| docker image ls | 78MB | 本地解压后的实际大小 | +| Registry / Docker Hub 页面 | 常见为压缩后的分发大小或单平台视角 | 便于传输与分发 | +| `docker image ls` | 本地镜像及其父层累计占用大小 | 更接近主机上的实际展开占用 | #### 实际磁盘占用 -由于镜像是分层存储,不同镜像可能共享相同的层: +由于镜像是分层存储,**只有基于相同父层构建** 的镜像才会共享相同层: ```bash -ubuntu:24.04 nginx:latest redis:latest - │ │ │ - └───────┬───────┘ │ - ▼ │ - 共享基础层 ◄───────────────────┘ +python:3.12-slim myapp:web myapp:worker + │ │ │ + └───────┬───────┘ │ + ▼ │ + 共享父层与依赖层 ◄──────────┘ ``` -因此,`docker image ls` 中各镜像大小之和 > 实际磁盘占用。 +因此,`docker image ls` 中各镜像大小之和通常会大于实际磁盘占用;共享层只会在磁盘上保存一次。 #### 查看实际空间占用 @@ -164,9 +164,9 @@ $ docker image prune ```bash $ docker images -a ``` -会显示很多无标签镜像——这些是构建过程中产生的中间层,被其他镜像依赖。 +会显示默认隐藏的中间层和虚悬镜像,其中一部分可能来自构建过程并被其他镜像复用。 -> ⚠️ 不要删除中间层镜像。它们是其他镜像的依赖,删除会导致上层镜像无法使用。删除顶层镜像时会自动清理不再需要的中间层。 +> ⚠️ 一般不要手动逐个处理这些无标签项。优先删除顶层镜像或使用 `docker image prune` 清理无用镜像,让 Docker 自行回收不再被引用的层。 --- diff --git a/04_image/4.3_rm.md b/04_image/4.3_rm.md index 3bcec72..cbe8de5 100644 --- a/04_image/4.3_rm.md +++ b/04_image/4.3_rm.md @@ -19,7 +19,7 @@ $ docker image rm [选项] <镜像1> [<镜像2> ...] | 方式 | 说明 | 示例 | |------|------|------| -| **短 ID** | ID 的前几位 (通常 3-4 位)| `docker rmi 501` | +| **短 ID** | ID 的前几位,只要足够唯一即可 | `docker rmi 501` | | **长 ID** | 完整的镜像 ID | `docker rmi 501ad78535f0...` | | **镜像名:标签** | 仓库名和标签 | `docker rmi redis:7.0` | | **镜像摘要** | 精确的内容摘要 | `docker rmi nginx@sha256:...` | @@ -144,7 +144,7 @@ $ docker image prune -f $ docker image prune -a -## 保留最近 24 小时的 +## 只清理 24 小时前的未使用镜像 $ docker image prune -a --filter "until=24h" ``` @@ -152,13 +152,17 @@ $ docker image prune -a --filter "until=24h" #### 按条件删除 ```bash -## 删除所有 redis 镜像 +## 先列出所有 redis 相关镜像引用 -$ docker rmi $(docker images -q redis) +$ docker image ls --filter reference='redis*' -## 删除 mongo:8.0 之前的所有镜像 +## 确认后,按仓库名:标签逐个删除需要的镜像 -$ docker rmi $(docker images -q -f before=mongo:8.0) +$ docker image rm redis:7 redis:7-alpine + +## 先列出所有比 mongo:8.0 更早创建的镜像 + +$ docker image ls --filter before=mongo:8.0 ## 删除某个时间之前的镜像 @@ -217,14 +221,25 @@ Error: image has dependent child images ### 4.3.6 常用过滤条件 +不同命令支持的过滤条件并不相同。最常见的是 `docker image ls` 用来筛选查看对象,`docker image prune` 用来清理未使用镜像。 + +#### `docker image ls` 常用过滤条件 + | 过滤条件 | 说明 | 示例 | |---------|------|------| | `dangling=true` | 虚悬镜像 | `-f dangling=true` | -| `before=镜像` | 在某镜像之前 | `-f before=mongo:3.2` | -| `since=镜像` | 在某镜像之后 | `-f since=mongo:3.2` | +| `before=镜像` | 仅匹配比某镜像更早创建的镜像 | `-f before=mongo:3.2` | +| `since=镜像` | 仅匹配比某镜像更晚创建的镜像 | `-f since=mongo:3.2` | | `label=key=value` | 按标签过滤 | `-f label=version=1.0` | | `reference=pattern` | 按名称模式 | `-f reference='*:latest'` | +#### `docker image prune` 常用过滤条件 + +| 过滤条件 | 说明 | 示例 | +|---------|------|------| +| `until=<时间>` | 仅清理某个时间点之前创建的未使用镜像 | `--filter "until=24h"` | +| `label=key=value` | 仅清理带指定标签的未使用镜像 | `--filter "label=stage=temp"` | + --- ### 4.3.7 清理策略 diff --git a/04_image/4.5_build.md b/04_image/4.5_build.md index 32af734..469b29d 100644 --- a/04_image/4.5_build.md +++ b/04_image/4.5_build.md @@ -86,7 +86,7 @@ RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html $ docker build -t nginx:v3 . ``` -在当前版本的 Docker 中,`docker build` 默认会通过 Buildx 调用 BuildKit,因此你更常看到的是 `[+] Building ...` 这类输出。为了帮助理解“每一步如何形成镜像历史”,下面仍展示一种较容易阅读的经典输出形式: +在当前版本的 Docker 中,`docker build` 默认会通过 Buildx 调用 BuildKit;但如果你显式设置了 `DOCKER_BUILDKIT=0`,或正在使用 Windows container mode,行为会回到 legacy builder。因此你更常看到的是 `[+] Building ...` 这类输出。为了帮助理解“每一步如何形成镜像历史”,下面仍展示一种较容易阅读的 legacy builder 经典输出形式: ```bash Sending build context to Docker daemon 2.048 kB @@ -98,7 +98,7 @@ Step 2 : RUN echo '

Hello, Docker!

' > /usr/share/nginx/html/index.html Removing intermediate container 9cdc27646c7b Successfully built 44aa4490ce2c ``` -从命令的输出结果中,我们可以清晰的看到镜像的构建过程。在 `Step 2` 中,如同我们之前所说的那样,`RUN` 指令启动了一个容器 `9cdc27646c7b`,执行了所要求的命令,并最后提交了这一层 `44aa4490ce2c`,随后删除了所用到的这个容器 `9cdc27646c7b`。 +这段输出清晰展示了 legacy builder 的构建过程。在 `Step 2` 中,`RUN` 指令对应一次临时构建容器:执行命令、生成新的镜像层,再删除中间容器。默认的 BuildKit 后端通常不会把这些中间容器和中间镜像以这种形式暴露给用户,但“每条会改文件系统的指令都会生成新的层”这一点并没有改变。 这里我们使用了 `docker build` 命令进行镜像构建。其格式为: @@ -111,7 +111,7 @@ docker build [选项] <上下文路径/URL/-> 如果注意,会看到 `docker build` 命令最后有一个 `.`。`.` 表示当前目录,而 `Dockerfile` 就在当前目录,因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径,这么理解其实是不准确的。如果对应上面的命令格式,你可能会发现,这是在指定 **上下文路径**。那么什么是上下文呢? -首先要理解 `docker build` 的工作原理。今天的 `docker build` 默认会通过 Buildx 向 BuildKit 后端发起构建请求;无论后端运行在本机还是远端,位置参数指定的都是 **构建上下文**,也就是构建器可以访问到的文件集合。 +首先要理解 `docker build` 的工作原理。今天的 `docker build` 通常会通过 Buildx 向 BuildKit 后端发起构建请求;无论后端运行在本机还是远端,位置参数指定的都是 **构建上下文**,也就是构建器可以访问到的文件集合。 当我们进行镜像构建的时候,并非所有定制都会通过 `RUN` 指令完成,经常还需要把本地文件复制进镜像,比如通过 `COPY` 指令、`ADD` 指令等。因此,构建器必须能够访问这些文件,而它能访问的范围正是你传给 `docker build` 的那个上下文。 @@ -124,7 +124,7 @@ COPY ./package.json /app/ ``` 这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`,也不是复制 `Dockerfile` 所在目录下的 `package.json`,而是复制 **上下文 (context)** 目录下的 `package.json`。 -因此,`COPY` 这类指令中的源文件路径都应该以构建上下文为基准来理解。对于 legacy builder,像 `COPY ../package.json /app` 这样的写法会直接报错;而在 BuildKit 下,前导的越界 `../` 会被剥离并重新解释为上下文内路径。无论是哪种情况,构建器都无法读取上下文之外的宿主机文件;如果真的需要那些文件,应该先把它们放进上下文目录,或重新选择合适的上下文。 +因此,`COPY` 这类指令中的源文件路径都应该以构建上下文为基准来理解。像 `COPY ../package.json /app` 这样的写法,前导的越界 `../` 会被规范化掉,最终仍只能在上下文根内解析;换句话说,构建器并不能借此读取上下文之外的宿主机文件。如果真的需要那些文件,应该先把它们放进上下文目录,或重新选择合适的上下文。 现在就可以理解刚才的命令 `docker build -t nginx:v3 .` 中的这个 `.`,实际上是在指定上下文目录,而不是单纯指定 `Dockerfile` 所在目录。 diff --git a/04_image/4.6_other.md b/04_image/4.6_other.md index 01f3034..6105748 100644 --- a/04_image/4.6_other.md +++ b/04_image/4.6_other.md @@ -6,44 +6,44 @@ 格式:`docker import [选项] <文件>||- [<仓库名>[:<标签>]]` -压缩包可以是本地文件、远程 Web 文件,甚至是从标准输入中得到。压缩包将会在镜像 `/` 目录展开,并直接作为镜像第一层提交。 +压缩包可以是本地文件、远程 Web 文件,甚至是从标准输入中得到。压缩包将会在镜像 `/` 目录展开,并直接作为镜像的文件系统内容导入。若还需要补充 `CMD`、`ENV` 等元数据,可以通过 `--change` 追加;但它不支持 `RUN`、`COPY` 这类依赖构建上下文或会生成新文件系统层的 Dockerfile 指令。 -比如我们想要创建一个 [OpenVZ](https://openvz.org) 的 Ubuntu 16.04 [模板](https://wiki.openvz.org/Download/template/precreated)的镜像: +例如在 Debian/Ubuntu 主机上,可以先用 `debootstrap` 生成 Ubuntu 24.04(`noble`)的 rootfs,再通过标准输入导入为基础镜像: > **版本提示**:示例中的 Ubuntu 16.04 (Xenial) 已于 2021 年 4 月停止支持。如果用于生产环境,建议更新至 Ubuntu 22.04 LTS 或更新版本。 ```bash -$ docker import \ - http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz \ - openvz/ubuntu:16.04 +$ sudo debootstrap noble noble > /dev/null +$ sudo tar -C noble -c . | docker import - ubuntu:noble -Downloading from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz -sha256:412b8fc3e3f786dca0197834a698932b9c51b69bd8cf49e100c35d38c9879213 +sha256:81ec9a55a92a5618161f68ae691d092bf14d700129093158297b3d01593f4ee3 ``` -这条命令自动下载了 `ubuntu-16.04-x86_64.tar.gz` 文件,并且作为根文件系统展开导入,并保存为镜像 `openvz/ubuntu:16.04`。 +这组命令会先得到 `noble/` 目录下的 rootfs,再把它打包后导入成镜像 `ubuntu:noble`。这种方式更适合制作自定义基础镜像或把现成的根文件系统快速封装成镜像。 导入成功后,我们可以用 `docker image ls` 看到这个导入的镜像: ```bash -$ docker image ls openvz/ubuntu +$ docker image ls ubuntu REPOSITORY TAG IMAGE ID CREATED SIZE -openvz/ubuntu 16.04 412b8fc3e3f7 55 seconds ago 505MB +ubuntu noble 81ec9a55a92a 5 seconds ago 78MB ``` -如果我们查看其历史的话,会看到描述中有导入的文件链接: +如果需要在导入时补上镜像元数据,可以配合 `--change` 选项: ```bash -$ docker history openvz/ubuntu:16.04 -IMAGE CREATED CREATED BY SIZE COMMENT -f477a6e18e98 About a minute ago 214.9 MB Imported from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz +$ docker import \ + --change 'CMD ["/bin/bash"]' \ + --change 'ENV LANG=C.UTF-8' \ + rootfs.tar \ + custom/base:noble ``` ### 4.6.2 Docker 镜像的导入和导出 `docker save` 和 `docker load` -Docker 还提供了 `docker save` 和 `docker load` 命令,用以将镜像保存为一个文件,然后传输到另一个位置上,再加载进来。这是在没有 Docker Registry 时的做法,现在已经不推荐,镜像迁移应该直接使用 Docker Registry,无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以。 +Docker 还提供了 `docker save` 和 `docker load` 命令,用以将镜像保存为一个归档文件,然后传输到另一个位置上,再加载进来。它们仍然适合离线备份、气隙环境分发,或在两台主机之间手工迁移镜像;如果是团队常规分发、版本管理和协作,优先使用 Docker Registry(无论是 Docker Hub 还是内网私有 Registry)会更合适。 #### 保存镜像 -使用 `docker save` 命令可以将镜像保存为归档文件。 +使用 `docker save` 命令可以将镜像保存为归档文件。归档中会包含镜像的父层以及所指定的标签;如果本地镜像仓库里已有多个平台变体,还可以通过 `--platform` 仅导出某个平台版本。 比如我们希望保存这个 `alpine` 镜像。 @@ -57,7 +57,7 @@ alpine latest baa5d63471ea 5 weeks ago 保存镜像的命令为: ```bash -$ docker save alpine -o filename +$ docker image save -o filename alpine $ file filename filename: POSIX tar archive ``` @@ -68,14 +68,16 @@ filename: POSIX tar archive 若使用 `gzip` 压缩: ```bash -$ docker save alpine | gzip > alpine-latest.tar.gz +$ docker image save alpine | gzip > alpine-latest.tar.gz ``` -然后我们将 `alpine-latest.tar.gz` 文件复制到了到了另一个机器上,可以用下面这个命令加载镜像: +然后我们将 `alpine-latest.tar.gz` 文件复制到了另一个机器上,可以用下面这个命令加载镜像: ```bash -$ docker load -i alpine-latest.tar.gz +$ docker image load -i alpine-latest.tar.gz Loaded image: alpine:latest ``` +`docker load` 会恢复归档中的镜像和标签;同样地,在需要时也可以通过 `--platform` 只加载其中一个平台变体。 + 如果我们结合这两个命令以及 `ssh` 甚至 `pv` 的话,利用 Linux 强大的管道,我们可以写一个命令完成从一个机器将镜像迁移到另一个机器,并且带进度条的功能: ```bash diff --git a/04_image/4.7_internal.md b/04_image/4.7_internal.md index 7932870..1d5d648 100644 --- a/04_image/4.7_internal.md +++ b/04_image/4.7_internal.md @@ -40,8 +40,8 @@ flowchart TD ``` * **读取文件**:当容器需要读取文件时,Docker 会从最上层 (容器层) 开始向下层 (镜像层) 寻找,直到找到该文件为止。 -* **修改文件**:当容器需要修改某个文件时,Docker 会从下层镜像中将该文件复制到上层的容器层,然后对副本进行修改。这被称为 **写时复制 (Copy-on-Write,CoW)** 策略。 -* **删除文件**:当容器删除某个文件时,Docker 并不是真的去下层删除它 (因为下层是只读的),而是在容器层创建一个特殊的 “白障 (Whiteout)” 文件,用来标记该文件已被删除,从而在容器视图中隐藏它。 +* **修改文件**:当容器需要修改某个文件时,底层存储后端会以 **写时复制 (Copy-on-Write,CoW)** 的方式记录变化。在 `overlay2` 这类联合文件系统后端上,常见表现是先把文件 `copy_up` 到容器层再修改;而 `btrfs`、`zfs` 等 CoW 文件系统的内部实现会有所不同。 +* **删除文件**:当容器删除某个文件时,Docker 不会直接改写下层只读层,而是由当前存储后端在上层记录“把下层内容屏蔽掉”的元数据。在 `overlay2` 中,这常表现为 whiteout 或 opaque directory;其他后端会用各自的机制达到相同效果。 这就是为什么: @@ -58,10 +58,10 @@ Docker 镜像的每一层都有一个唯一的 ID,这个 ID 是根据该层的 ### 4.7.4 联合文件系统 -Docker 使用联合文件系统 (Union FS) 与写时复制思路来实现这种分层挂载。传统的实现方式常见于 `overlay2`、`aufs`、`btrfs`、`zfs` 等存储驱动;而在 Docker Engine 29.0 及之后的全新安装中,默认镜像后端已经变为 containerd image store,它使用 snapshotter 来管理这些层。 +Docker 用“分层 + CoW”的思路来实现镜像与容器层叠加,但具体后端并不完全相同。`overlay2`、`aufs` 这类后端属于联合文件系统;`btrfs`、`zfs` 更接近具备快照/克隆能力的 CoW 文件系统;而在 Docker Engine 29.0 及之后的全新安装中,默认镜像后端已经变为 containerd image store,它使用 snapshotter 来管理这些层。 > **版本背景**:Docker Engine 29.0(发布于 2024 年 2 月)是一个重要版本分界点,引入了 containerd image store 作为默认镜像存储后端。这对镜像管理、OCI 合规性和供应链安全都有深远影响。如果你的 Docker 版本低于 29.0,镜像存储仍使用传统的 classic store 路径。 -虽然底层实现细节不同,但它们都遵循上述的 **分层 + CoW** 模型;因此,无论你看到的是 `overlay2` 还是 containerd snapshotter,理解镜像层、容器层和写时复制的方式都是一样重要的。 +虽然底层实现细节不同,但它们都遵循“下层只读、上层记录差异”的总体模型;因此,无论你看到的是 `overlay2`、`zfs` 还是 containerd snapshotter,理解镜像层、容器层和写时复制的核心思想都是一样重要的。 > 想要深入了解 Overlay2 等文件系统的具体实现原理,包括 WorkDir、UpperDir、LowerDir 等底层细节,请阅读 **[第十二章 底层实现](../12_implementation/README.md)** 中的 **[联合文件系统](../12_implementation/12.4_ufs.md)** 章节。