style: apply global formatting fixes (struct, spacing, zhlint)

This commit is contained in:
Baohua Yang
2026-02-21 11:08:52 -08:00
parent ad68b2d973
commit 79ac9c639a
159 changed files with 1708 additions and 882 deletions

View File

@@ -116,6 +116,10 @@ flowchart TD
| `--platform` | 指定平台架构 | `docker pull --platform linux/arm64 nginx` |
| `--quiet, -q` | 静默模式 | `docker pull -q nginx` |
#### 概述
总体概述了以下内容
#### 指定平台
Apple Silicon Mac 上拉取 x86 镜像
@@ -180,13 +184,13 @@ $ sudo systemctl restart docker # Linux
```
详见 [镜像加速器](../03_install/3.9_mirror.md) 章节
详见[镜像加速器](../03_install/3.9_mirror.md)章节
---
### 验证镜像完整性
为了确保下载的镜像没有被篡改且内容一致我们可以校验镜像的摘要Digest
为了确保下载的镜像没有被篡改且内容一致我们可以校验镜像的摘要 (Digest)
#### 查看镜像摘要
@@ -214,13 +218,13 @@ $ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9
在使用 `docker pull` 过程中可能会遇到下载速度慢镜像不存在或磁盘空间不足等问题以下是一些常见问题的排查思路
#### Q: 下载速度很慢
#### Q下载速度很慢
1. 配置镜像加速器
2. 检查网络连接
3. 尝试拉取更小的镜像版本 `alpine` 变体
3. 尝试拉取更小的镜像版本 ( `alpine` 变体)
#### Q: 提示镜像不存在
#### Q提示镜像不存在
运行以下命令
@@ -230,10 +234,10 @@ Error: pull access denied, repository does not exist
可能原因
- 镜像名拼写错误
- 私有镜像未登录需要 `docker login`
- 私有镜像未登录 (需要 `docker login`)
- 镜像确实不存在
#### Q: 磁盘空间不足
#### Q磁盘空间不足
运行以下命令

View File

@@ -31,6 +31,10 @@ ubuntu noble 329ed837d508 3 days ago 78MB
| **CREATED** | 创建时间 |
| **SIZE** | 本地占用空间 |
#### 概述
总体概述了以下内容
#### 同一镜像多个标签
注意上面的 `ubuntu:24.04` `ubuntu:noble` 拥有相同的 IMAGE ID它们是同一个镜像的不同标签只占用一份存储空间
@@ -131,7 +135,7 @@ $ docker images -f label=maintainer=example@email.com
---
### 虚悬镜像Dangling Images
### 虚悬镜像
在镜像列表里你可能会看到一些仓库名和标签都为 `<none>` 的镜像这类镜像被称为虚悬镜像
@@ -170,7 +174,11 @@ $ docker image prune
除了虚悬镜像`docker image ls` 默认列出的只是顶层镜像还有一种镜像是为了加速镜像构建重复利用资源而存在的中间层镜像
#### 查看所有镜像包含中间层
#### 概述
总体概述了以下内容
#### 查看所有镜像 (包含中间层)
运行以下命令

View File

@@ -74,7 +74,7 @@ $ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b1
执行删除命令后Docker 会输出一系列的操作记录理解这些信息有助于我们掌握镜像删除的机制
删除镜像时会看到两类信息**Untagged****Deleted**
删除镜像时会看到两类信息**Untagged** **Deleted**
```bash
$ docker rmi redis:alpine
@@ -126,7 +126,7 @@ flowchart TD
#### 删除所有虚悬镜像
虚悬镜像dangling没有标签的镜像通常是旧版本被新版本覆盖后产生的
虚悬镜像 (dangling)没有标签的镜像通常是旧版本被新版本覆盖后产生的
```bash
## 查看虚悬镜像
@@ -221,7 +221,7 @@ Untagged: ubuntu:24.04
```
#### 原因三被其他镜像依赖中间层
#### 原因三被其他镜像依赖 (中间层)
运行以下命令
@@ -248,7 +248,7 @@ Error: image has dependent child images
### 清理策略
针对不同的环境开发环境 vs 生产环境我们应该采用不同的镜像清理策略
针对不同的环境 (开发环境 vs 生产环境)我们应该采用不同的镜像清理策略
#### 开发环境

View File

@@ -1,8 +1,8 @@
## 4.4 利用 commit 理解镜像构成
> 注意如果您是初学者您可以暂时跳过后面的内容直接学习 [容器](../05_container/) 一节
> 注意如果您是初学者您可以暂时跳过后面的内容直接学习[容器](../05_container/)一节
注意 `docker commit` 命令除了学习之外还有一些特殊的应用场合比如被入侵后保存现场等但是不要使用 `docker commit` 定制镜像定制镜像应该使用 `Dockerfile` 来完成如果你想要定制镜像请查看下一小节
注意`docker commit` 命令除了学习之外还有一些特殊的应用场合比如被入侵后保存现场等但是不要使用 `docker commit` 定制镜像定制镜像应该使用 `Dockerfile` 来完成如果你想要定制镜像请查看下一小节
镜像是容器的基础每次执行 `docker run` 的时候都会指定哪个镜像作为容器运行的基础在之前的例子中我们所使用的都是来自于 Docker Hub 的镜像直接使用这些镜像是可以满足一定的需求而当这些镜像无法直接满足需求时我们就需要定制这些镜像接下来的几节就将讲解如何定制镜像
@@ -16,7 +16,7 @@ $ docker run --name webserver -d -p 80:80 nginx
这条命令会用 `nginx` 镜像启动一个容器命名为 `webserver`并且映射了 80 端口这样我们可以用浏览器去访问这个 `nginx` 服务器
如果是在本机运行的 Docker那么可以直接访问`http://localhost` 如果是在虚拟机云服务器上安装的 Docker则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址
如果是在本机运行的 Docker那么可以直接访问`http://localhost`如果是在虚拟机云服务器上安装的 Docker则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址
直接用浏览器访问的话我们会看到默认的 Nginx 欢迎页面
@@ -63,7 +63,7 @@ A /var/cache/nginx/uwsgi_temp
现在我们定制好了变化我们希望能将其保存下来形成镜像
要知道当我们运行一个容器的时候如果不使用卷的话我们做的任何文件修改都会被记录于容器存储层里 Docker 提供了一个 `docker commit` 命令可以将容器的存储层保存下来成为镜像换句话说就是在原有镜像的基础上再叠加上容器的存储层并构成新的镜像以后我们运行这个新镜像的时候就会拥有原有容器最后的文件变化
要知道当我们运行一个容器的时候 (如果不使用卷的话)我们做的任何文件修改都会被记录于容器存储层里 Docker 提供了一个 `docker commit` 命令可以将容器的存储层保存下来成为镜像换句话说就是在原有镜像的基础上再叠加上容器的存储层并构成新的镜像以后我们运行这个新镜像的时候就会拥有原有容器最后的文件变化
`docker commit` 的语法格式为
@@ -120,12 +120,16 @@ docker run --name web2 -d -p 81:80 nginx:v2
至此我们第一次完成了定制镜像使用的是 `docker commit` 命令手动操作给旧的镜像添加了新的一层形成新的镜像对镜像多层存储应该有了更直观的感觉
### 概述
总体概述了以下内容
### 慎用 `docker commit`
使用 `docker commit` 命令虽然可以比较直观的帮助理解镜像分层存储的概念但是实际环境中并不会这样使用
首先如果仔细观察之前的 `docker diff webserver` 的结果你会发现除了真正想要修改的 `/usr/share/nginx/html/index.html` 文件外由于命令的执行还有很多文件被改动或添加了这还仅仅是最简单的操作如果是安装软件包编译构建那会有大量的无关内容被添加进来将会导致镜像极为臃肿
此外使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作生成的镜像也被称为 **黑箱镜像**换句话说就是除了制作镜像的人知道执行过什么命令怎么生成的镜像别人根本无从得知而且即使是这个制作镜像的人过一段时间后也无法记清具体的操作这种黑箱镜像的维护工作是非常痛苦的
此外使用 `docker commit` 意味着所有对镜像的操作都是黑箱操作生成的镜像也被称为**黑箱镜像**换句话说就是除了制作镜像的人知道执行过什么命令怎么生成的镜像别人根本无从得知而且即使是这个制作镜像的人过一段时间后也无法记清具体的操作这种黑箱镜像的维护工作是非常痛苦的
而且回顾之前提及的镜像所使用的分层存储的概念除当前层外之前的每一层都是不会发生改变的换句话说任何修改的结果仅仅是在当前层进行标记添加修改而不会改动上一层如果使用 `docker commit` 制作镜像以及后期修改的话每一次修改都会让镜像更加臃肿一次所删除的上一层的东西并不会丢失会一直如影随形的跟着这个镜像即使根本无法访问到这会让镜像更加臃肿

View File

@@ -2,17 +2,17 @@
从刚才的 `docker commit` 的学习中我们可以了解到镜像的定制实际上就是定制每一层所添加的配置文件如果我们可以把每一层修改安装构建操作的命令都写入一个脚本用这个脚本来构建定制镜像那么之前提及的无法重复的问题镜像构建透明性的问题体积的问题就都会解决这个脚本就是 Dockerfile
Dockerfile 是一个文本文件其内包含了一条条的 **指令(Instruction)**每一条指令构建一层因此每一条指令的内容就是描述该层应当如何构建
Dockerfile 是一个文本文件其内包含了一条条的**指令 (Instruction)**每一条指令构建一层因此每一条指令的内容就是描述该层应当如何构建
### 使用 docker init 快速创建推荐
### 使用 docker init 快速创建 (推荐)
Docker 提供了 `docker init` 命令可以根据项目类型自动生成 Dockerfile.dockerignore compose.yaml 文件
Docker 提供了 `docker init` 命令可以根据项目类型自动生成 Dockerfiledockerignore compose.yaml 文件
```bash
$ docker init
```
该命令会交互式地询问项目类型 GoNode.jsPythonRust 并生成符合最佳实践的配置文件对于新项目这是推荐的起步方式
该命令会交互式地询问项目类型 ( GoNode.jsPythonRust )并生成符合最佳实践的配置文件对于新项目这是推荐的起步方式
### 手动创建 Dockerfile
@@ -37,7 +37,7 @@ RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
### FROM 指定基础镜像
所谓定制镜像那一定是以一个镜像为基础在其上进行定制就像我们之前运行了一个 `nginx` 镜像的容器再进行修改一样基础镜像是必须指定的 `FROM` 就是指定 **基础镜像**因此一个 `Dockerfile` `FROM` 是必备的指令并且必须是第一条指令
所谓定制镜像那一定是以一个镜像为基础在其上进行定制就像我们之前运行了一个 `nginx` 镜像的容器再进行修改一样基础镜像是必须指定的 `FROM` 就是指定**基础镜像**因此一个 `Dockerfile` `FROM` 是必备的指令并且必须是第一条指令
[Docker Hub](https://hub.docker.com/search?q=&type=image&image_filter=official) 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 [`nginx`](https://hub.docker.com/_/nginx/)、[`redis`](https://hub.docker.com/_/redis/)、[`mongo`](https://hub.docker.com/_/mongo/)、[`mysql`](https://hub.docker.com/_/mysql/)、[`httpd`](https://hub.docker.com/_/httpd/)、[`php`](https://hub.docker.com/_/php/)、[`tomcat`](https://hub.docker.com/_/tomcat/) 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 [`node`](https://hub.docker.com/_/node)、[`openjdk`](https://hub.docker.com/_/openjdk/)、[`python`](https://hub.docker.com/_/python/)、[`ruby`](https://hub.docker.com/_/ruby/)、[`golang`](https://hub.docker.com/_/golang/) 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。
@@ -52,7 +52,7 @@ FROM scratch
如果你以 `scratch` 为基础镜像的话意味着你不以任何镜像为基础接下来所写的指令将作为镜像第一层开始存在
不以任何系统为基础直接将可执行文件复制进镜像的做法并不罕见对于 Linux 下静态编译的程序来说并不需要有操作系统提供运行时支持所需的一切库都已经在可执行文件里了因此直接 `FROM scratch` 会让镜像体积更加小巧使用 [Go 语言](https://golang.google.cn/) 开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
不以任何系统为基础直接将可执行文件复制进镜像的做法并不罕见对于 Linux 下静态编译的程序来说并不需要有操作系统提供运行时支持所需的一切库都已经在可执行文件里了因此直接 `FROM scratch` 会让镜像体积更加小巧使用 [Go 语言](https://golang.google.cn/)开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
### RUN 执行命令
@@ -72,9 +72,9 @@ Dockerfile 中每一个指令都会建立一层,`RUN` 也不例外。每一个
>
> 每一个 `RUN` 指令都会产生一个新的镜像层为了减少镜像体积和层数我们通常会将多个命令合并到一个 `RUN` 指令中执行
>
> 更多关于 `RUN` 指令的详细用法最佳实践如清理缓存使用 pipefail `Union FS` 的层数限制等内容请参阅 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)**中的**[RUN 指令](../07_dockerfile/7.1_run.md)** 小节
> 更多关于 `RUN` 指令的详细用法最佳实践 (如清理缓存使用 pipefail ) `Union FS` 的层数限制等内容请参阅**[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)**中的** [RUN 指令](../07_dockerfile/7.1_run.md)**小节
要想编写优秀的 `Dockerfile`必须了解每一条指令的作用和副作用 **[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)** 我们将对 `COPY`, `ADD`, `CMD`, `ENTRYPOINT` 等指令进行详细讲解
要想编写优秀的 `Dockerfile`必须了解每一条指令的作用和副作用**[第七章 Dockerfile 指令详解](../07_dockerfile/README.md)**我们将对 `COPY``ADD``CMD``ENTRYPOINT` 等指令进行详细讲解
### 构建镜像
@@ -104,11 +104,11 @@ docker build [选项] <上下文路径/URL/->
在这里我们指定了最终镜像的名称 `-t nginx:v3`构建成功后我们可以像之前运行 `nginx:v2` 那样来运行这个镜像其结果会和 `nginx:v2` 一样
### 镜像构建上下文Context
### 镜像构建上下文
如果注意会看到 `docker build` 命令最后有一个 `.``.` 表示当前目录 `Dockerfile` 就在当前目录因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径这么理解其实是不准确的如果对应上面的命令格式你可能会发现这是在指定 **上下文路径**那么什么是上下文呢
如果注意会看到 `docker build` 命令最后有一个 `.``.` 表示当前目录 `Dockerfile` 就在当前目录因此不少初学者以为这个路径是在指定 `Dockerfile` 所在路径这么理解其实是不准确的如果对应上面的命令格式你可能会发现这是在指定**上下文路径**那么什么是上下文呢
首先我们要理解 `docker build` 的工作原理Docker 在运行时分为 Docker 引擎也就是服务端守护进程和客户端工具Docker 的引擎提供了一组 REST API被称为 [Docker Remote API](https://docs.docker.com/develop/sdk/),而如 `docker` 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 `docker` 功能,但实际上,一切都是使用的远程调用形式在服务端Docker 引擎完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
首先我们要理解 `docker build` 的工作原理Docker 在运行时分为 Docker 引擎 (也就是服务端守护进程) 和客户端工具Docker 的引擎提供了一组 REST API被称为 [Docker Remote API](https://docs.docker.com/develop/sdk/),而如 `docker` 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。因此,虽然表面上我们好像是在本机执行各种 `docker` 功能,但实际上,一切都是使用的远程调用形式在服务端 (Docker 引擎) 完成。也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。
当我们进行镜像构建的时候并非所有定制都会通过 `RUN` 指令完成经常会需要将一些本地文件复制进镜像比如通过 `COPY` 指令`ADD` 指令等 `docker build` 命令构建镜像其实并非在本地构建而是在服务端也就是 Docker 引擎中构建的那么在这种客户端/服务端的架构中如何才能让服务端获得本地文件呢
@@ -120,7 +120,7 @@ docker build [选项] <上下文路径/URL/->
COPY ./package.json /app/
```
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`也不是复制 `Dockerfile` 所在目录下的 `package.json`而是复制 **上下文context** 目录下的 `package.json`
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`也不是复制 `Dockerfile` 所在目录下的 `package.json`而是复制**上下文 (context)** 目录下的 `package.json`
因此`COPY` 这类指令中的源文件的路径都是*相对路径*这也是初学者经常会问的为什么 `COPY ../package.json /app` 或者 `COPY /opt/xxxx /app` 无法工作的原因因为这些路径已经超出了上下文的范围Docker 引擎无法获得这些位置的文件如果真的需要那些文件应该将它们复制到上下文目录中去
@@ -146,6 +146,8 @@ Sending build context to Docker daemon 2.048 kB
### 其它 `docker build` 的用法
本节涵盖了相关内容与详细描述主要探讨以下几个方面
#### 直接用 Git repo 进行构建
或许你已经注意到了`docker build` 还支持从 URL 构建比如可以直接从 Git repo 中构建

View File

@@ -41,6 +41,10 @@ f477a6e18e98 About a minute ago 214.9 MB
Docker 还提供了 `docker save` `docker load` 命令用以将镜像保存为一个文件然后传输到另一个位置上再加载进来这是在没有 Docker Registry 时的做法现在已经不推荐镜像迁移应该直接使用 Docker Registry无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以
#### 概述
总体概述了以下内容
#### 保存镜像
使用 `docker save` 命令可以将镜像保存为归档文件
@@ -63,7 +67,7 @@ filename: POSIX tar archive
这里的 filename 可以为任意名称甚至任意后缀名但文件的本质都是归档文件
**注意如果同名则会覆盖没有警告**
**注意如果同名则会覆盖 (没有警告)**
若使用 `gzip` 压缩

View File

@@ -8,22 +8,22 @@ Docker 镜像是怎么实现增量的修改和维护的?为什么容器启动
Docker 镜像并不是一个单纯的文件而是由一组文件系统叠加构成的
最底层的镜像称为 **基础镜像Base Image**通常是各种 Linux 发行版的 root 文件系统 UbuntuDebianCentOS
最底层的镜像称为**基础镜像 (Base Image)**通常是各种 Linux 发行版的 root 文件系统 UbuntuDebianCentOS
当我们在基础镜像之上构建新的镜像时例如安装了 NginxDocker 并不是复制一份基础镜像而是在基础镜像之上**新建一个层Layer**并在该层中仅记录为了安装 Nginx 而发生的文件变更添加修改删除
当我们在基础镜像之上构建新的镜像时 (例如安装了 Nginx)Docker 并不是复制一份基础镜像而是在基础镜像之上**新建一个层 (Layer)**并在该层中仅记录为了安装 Nginx 而发生的文件变更 (添加修改删除)
这种分层存储结构使得镜像的复用分发变得非常高效
* **复用**如果多个镜像都基于同一个基础镜像例如都基于 `ubuntu:24.04`那么宿主机只需要下载一份 `ubuntu:24.04`所有镜像都可以共享它
* **复用**如果多个镜像都基于同一个基础镜像 (例如都基于 `ubuntu:24.04`)那么宿主机只需要下载一份 `ubuntu:24.04`所有镜像都可以共享它
* **轻量**镜像仅仅记录了与基础镜像的差异因此体积非常小
### 容器层与读写
我们要理解的一个关键概念是**镜像的每一层都是只读的Read-only**
我们要理解的一个关键概念是**镜像的每一层都是只读的 (Read-only)**
那么既然镜像只读容器为什么能写文件呢
当容器启动时Docker 会在镜像的最上层添加一个新的**可写层Writable Layer**通常被称为**容器层**
当容器启动时Docker 会在镜像的最上层添加一个新的**可写层 (Writable Layer)**通常被称为**容器层**
```mermaid
flowchart TD
@@ -39,27 +39,27 @@ flowchart TD
Note["所有的写操作都在容器层这里"] -.-> L4
```
* **读取文件**当容器需要读取文件时Docker 会从最上层容器层开始向下层镜像层寻找直到找到该文件为止
* **修改文件**当容器需要修改某个文件时Docker 会从下层镜像中将该文件复制到上层的容器层然后对副本进行修改这被称为**写时复制Copy-on-Write, CoW** 策略
* **删除文件**当容器删除某个文件时Docker 并不是真的去下层删除它因为下层是只读的而是在容器层创建一个特殊的白障Whiteout文件用来标记该文件已被删除从而在容器视图中隐藏它
* **读取文件**当容器需要读取文件时Docker 会从最上层 (容器层) 开始向下层 (镜像层) 寻找直到找到该文件为止
* **修改文件**当容器需要修改某个文件时Docker 会从下层镜像中将该文件复制到上层的容器层然后对副本进行修改这被称为**写时复制 (Copy-on-WriteCoW)** 策略
* **删除文件**当容器删除某个文件时Docker 并不是真的去下层删除它 (因为下层是只读的)而是在容器层创建一个特殊的 白障 (Whiteout) 文件用来标记该文件已被删除从而在容器视图中隐藏它
这就是为什么
1. **容器删除后数据会丢失**因为所有的数据修改都保存在最上层的容器层中容器销毁时这个层也就随之销毁了除非使用了数据卷详见[数据管理](../08_data_network/README.md)
1. **容器删除后数据会丢失**因为所有的数据修改都保存在最上层的容器层中容器销毁时这个层也就随之销毁了(除非使用了数据卷详见[数据管理](../08_data_network/README.md))
2. **镜像不可变**无论我们在容器里删除了多少文件基础镜像的体积并不会减小因为它们依然存在于底层的只读层中
### 内容寻址与镜像 ID
Docker 镜像的每一层都有一个唯一的 ID这个 ID 是根据该层的内容计算出来的哈希值SHA256这意味着
Docker 镜像的每一层都有一个唯一的 ID这个 ID 是根据该层的内容计算出来的哈希值 (SHA256)这意味着
* **内容即 ID**只要层的内容有一丁点变化ID 就会变
* **安全性**确保了镜像内容的完整性下载过程中如果数据损坏ID 校验就会失败
* **去重**如果两个不同的镜像甚至是不同来源的镜像包含相同的层ID 相同Docker 引擎在本地只会存储一份绝不重复下载
* **去重**如果两个不同的镜像 (甚至是不同来源的镜像) 包含相同的层 (ID 相同)Docker 引擎在本地只会存储一份绝不重复下载
### 联合文件系统
Docker 使用联合文件系统Union FS来实现这种分层挂载常见的驱动包括 `overlay2`目前推荐`aufs`早期使用`btrfs``zfs`
Docker 使用联合文件系统 (Union FS) 来实现这种分层挂载常见的驱动包括 `overlay2` (目前推荐)`aufs` (早期使用)`btrfs``zfs`
虽然实现细节不同但它们都遵循上述的 **分层 + CoW** 模型
虽然实现细节不同但它们都遵循上述的**分层 + CoW** 模型
> 想要深入了解 Overlay2 等文件系统的具体实现原理包括 WorkDirUpperDirLowerDir 等底层细节请阅读 **[第十四章 底层实现](../14_implementation/README.md)**中的**[联合文件系统](../14_implementation/14.4_ufs.md)** 章节
> 想要深入了解 Overlay2 等文件系统的具体实现原理包括 WorkDirUpperDirLowerDir 等底层细节请阅读**[第十四章底层实现](../14_implementation/README.md)**中的**[联合文件系统](../14_implementation/14.4_ufs.md)**章节

View File

@@ -1,9 +1,13 @@
# 第四章 使用镜像
# 第四章使用镜像
在之前的介绍中我们知道镜像是 Docker 的三大组件之一
Docker 运行容器前需要本地存在对应的镜像如果本地不存在该镜像Docker 会从镜像仓库下载该镜像
## 概述
总体概述了以下内容
## 本章内容
本章将介绍更多关于镜像的内容包括