Remove blank lines after code block markers

This commit is contained in:
yeasy
2026-03-21 22:36:09 -07:00
parent 312f8fea42
commit 9ac19d79ee
132 changed files with 0 additions and 1517 deletions

View File

@@ -9,7 +9,6 @@
```bash
docker pull [选项] [Registry地址/]仓库名[:标签]
```
#### 镜像名称格式
Docker 镜像名称由 Registry 地址用户名仓库名和标签组成其标准格式如下
@@ -21,7 +20,6 @@ docker.io / library / ubuntu : 24.04
Registry地址 用户名 仓库名 标签
(可省略) (可省略)
```
| 组成部分 | 说明 | 默认值 |
|---------|------|--------|
| Registry 地址 | 镜像仓库地址 | `docker.io` (Docker Hub)|
@@ -32,7 +30,6 @@ Registry地址 用户名 仓库名 标签
#### 示例
```bash
## 完整格式
$ docker pull docker.io/library/ubuntu:24.04
@@ -57,7 +54,6 @@ $ docker pull bitnami/redis:latest
$ docker pull ghcr.io/username/myapp:v1.0
```
---
### 4.1.2 下载过程解析
@@ -74,7 +70,6 @@ Digest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
Status: Downloaded newer image for ubuntu:24.04
docker.io/library/ubuntu:24.04
```
#### 输出解读
| 输出内容 | 说明 |
@@ -98,7 +93,6 @@ flowchart TD
L3 --- L2 --- L1
end
```
如果本地已有相同的层Docker 会跳过下载节省带宽和时间
---
@@ -120,7 +114,6 @@ flowchart TD
```bash
$ docker pull --platform linux/amd64 nginx
```
---
### 4.1.4 拉取后运行
@@ -128,7 +121,6 @@ $ docker pull --platform linux/amd64 nginx
拉取镜像后可以基于它启动容器
```bash
## 拉取镜像
$ docker pull ubuntu:24.04
@@ -141,7 +133,6 @@ PRETTY_NAME="Ubuntu 24.04 LTS"
...
root@e7009c6ce357:/# exit
```
**参数说明**
| 参数 | 说明 |
@@ -167,7 +158,6 @@ root@e7009c6ce357:/# exit
]
}
```
配置后重启 Docker
```bash
@@ -175,7 +165,6 @@ $ sudo systemctl restart docker # Linux
## 或在 Docker Desktop 中重启
```
详见[镜像加速器](../03_install/3.9_mirror.md)章节
---
@@ -191,7 +180,6 @@ $ docker images --digests ubuntu
REPOSITORY TAG DIGEST IMAGE ID
ubuntu 24.04 sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26 ca2b0f26964c
```
#### 使用摘要拉取
用摘要拉取可确保获取完全相同的镜像
@@ -199,7 +187,6 @@ ubuntu 24.04 sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9
```bash
$ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
```
> 笔者建议生产环境使用摘要而非标签因为标签可能被覆盖摘要则是不可变的
---
@@ -219,7 +206,6 @@ $ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9
```bash
Error: pull access denied, repository does not exist
```
可能原因
- 镜像名拼写错误
@@ -229,7 +215,6 @@ Error: pull access denied, repository does not exist
#### Q磁盘空间不足
```bash
## 清理未使用的镜像
$ docker image prune
@@ -238,5 +223,4 @@ $ docker image prune
$ docker system prune
```
---

View File

@@ -14,7 +14,6 @@ nginx latest 05a60462f8ba 5 days ago 181MB
ubuntu 24.04 329ed837d508 3 days ago 78MB
ubuntu noble 329ed837d508 3 days ago 78MB
```
> 💡 `docker images` `docker image ls` 的简写两者等效
---
@@ -59,7 +58,6 @@ ubuntu:24.04 nginx:latest redis:latest
▼ │
共享基础层 ◄───────────────────┘
```
因此`docker image ls` 中各镜像大小之和 > 实际磁盘占用
#### 查看实际空间占用
@@ -72,7 +70,6 @@ Containers 5 2 100MB 80MB (80%)
Local Volumes 8 2 500MB 400MB (80%)
Build Cache 0 0 0B 0B
```
---
### 4.2.4 过滤镜像
@@ -82,7 +79,6 @@ Build Cache 0 0 0B 0B
#### 按仓库名过滤
```bash
## 列出所有 ubuntu 镜像
$ docker images ubuntu
@@ -91,7 +87,6 @@ ubuntu 24.04 329ed837d508 78MB
ubuntu noble 329ed837d508 78MB
ubuntu 22.04 a1b2c3d4e5f6 72MB
```
#### 按仓库名和标签过滤
```bash
@@ -99,7 +94,6 @@ $ docker images ubuntu:24.04
REPOSITORY TAG IMAGE ID SIZE
ubuntu 24.04 329ed837d508 78MB
```
#### 使用过滤器 --filter
| 过滤条件 | 说明 | 示例 |
@@ -111,7 +105,6 @@ ubuntu 24.04 329ed837d508 78MB
| `reference=pattern` | 按名称模式 | `-f reference='*:latest'` |
```bash
## 列出 nginx 之后创建的镜像
$ docker images -f since=nginx:latest
@@ -124,7 +117,6 @@ $ docker images -f reference='*:latest'
$ docker images -f label=maintainer=example@email.com
```
---
### 4.2.5 虚悬镜像
@@ -140,7 +132,6 @@ $ docker images
REPOSITORY TAG IMAGE ID SIZE
<none> <none> 00285df0df87 342MB
```
#### 产生原因
1. **镜像重新构建**新镜像使用了旧镜像的标签旧镜像标签被移除
@@ -149,7 +140,6 @@ REPOSITORY TAG IMAGE ID SIZE
#### 处理虚悬镜像
```bash
## 列出虚悬镜像
$ docker images -f dangling=true
@@ -158,7 +148,6 @@ $ docker images -f dangling=true
$ docker image prune
```
---
### 4.2.6 中间层镜像
@@ -170,7 +159,6 @@ $ docker image prune
```bash
$ docker images -a
```
会显示很多无标签镜像这些是构建过程中产生的中间层被其他镜像依赖
> 不要删除中间层镜像它们是其他镜像的依赖删除会导致上层镜像无法使用删除顶层镜像时会自动清理不再需要的中间层
@@ -189,11 +177,9 @@ $ docker images -q
05a60462f8ba
329ed837d508
```
常用于配合其他命令
```bash
## 删除所有镜像
$ docker rmi $(docker images -q)
@@ -202,13 +188,11 @@ $ docker rmi $(docker images -q)
$ docker rmi $(docker images -q redis)
```
#### 显示完整 ID
```bash
$ docker images --no-trunc
```
#### 显示摘要
```bash
@@ -216,13 +200,11 @@ $ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
```
#### 自定义格式
使用 Go 模板语法自定义输出
```bash
## 只显示 ID 和仓库名
$ docker images --format "{{.ID}}: {{.Repository}}"
@@ -238,7 +220,6 @@ redis latest 183MB
nginx latest 181MB
ubuntu 24.04 78MB
```
#### 可用模板字段
| 字段 | 说明 |
@@ -256,7 +237,6 @@ ubuntu 24.04 78MB
### 4.2.8 常用命令组合
```bash
## 列出所有镜像及其大小,按大小排序(需要系统 sort 命令)
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -h
@@ -269,5 +249,4 @@ $ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | grep -E "^[0-9]
$ docker images --format "{{.Repository}}:{{.Tag}}" > images.txt
```
---

View File

@@ -9,7 +9,6 @@
```bash
$ docker image rm [选项] <镜像1> [<镜像2> ...]
```
> 💡 `docker rmi` `docker image rm` 的简写两者等效
---
@@ -39,7 +38,6 @@ $ docker rmi 501
Untagged: redis:alpine
Deleted: sha256:501ad78535f0...
```
#### 使用镜像名删除
```bash
@@ -47,13 +45,11 @@ $ docker rmi redis:alpine
Untagged: redis:alpine
Deleted: sha256:501ad78535f0...
```
#### 使用摘要删除
摘要删除最精确适用于 CI/CD 场景
```bash
## 查看镜像摘要
$ docker images --digests
@@ -64,7 +60,6 @@ nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
$ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
```
---
### 4.3.3 理解输出信息
@@ -81,7 +76,6 @@ Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
```
#### Untagged vs Deleted
| 操作 | 含义 |
@@ -114,7 +108,6 @@ flowchart TD
Step4 -- "未使用" --> Delete["Deleted (删除该层)"]
end
```
---
### 4.3.4 批量删除
@@ -126,7 +119,6 @@ flowchart TD
虚悬镜像 (dangling)没有标签的镜像通常是旧版本被新版本覆盖后产生的
```bash
## 查看虚悬镜像
$ docker images -f dangling=true
@@ -139,11 +131,9 @@ $ docker image prune
$ docker image prune -f
```
#### 删除所有未使用的镜像
```bash
## 删除所有没有被容器使用的镜像
$ docker image prune -a
@@ -152,11 +142,9 @@ $ docker image prune -a
$ docker image prune -a --filter "until=24h"
```
#### 按条件删除
```bash
## 删除所有 redis 镜像
$ docker rmi $(docker images -q redis)
@@ -169,7 +157,6 @@ $ docker rmi $(docker images -q -f before=mongo:8.0)
$ docker image prune -a --filter "until=168h" # 7天前
```
---
### 4.3.5 删除失败的常见原因
@@ -183,11 +170,9 @@ $ docker rmi nginx
Error: conflict: unable to remove repository reference "nginx"
(must force) - container abc123 is using its referenced image
```
**解决方案**
```bash
## 方案1先删除依赖的容器
$ docker rm abc123
@@ -197,7 +182,6 @@ $ docker rmi nginx
$ docker rmi -f nginx
```
#### 原因二多个标签指向同一镜像
```bash
@@ -211,7 +195,6 @@ Untagged: ubuntu:24.04
## 只是移除标签,镜像仍存在(因为还有 ubuntu:latest 指向它)
```
当同一个镜像有多个标签时`docker rmi` 只是删除指定的标签不会删除镜像本身
#### 原因三被其他镜像依赖中间层
@@ -220,7 +203,6 @@ Untagged: ubuntu:24.04
$ docker rmi some_base_image
Error: image has dependent child images
```
中间层镜像被其他镜像依赖无法删除需要先删除依赖它的镜像
---
@@ -244,7 +226,6 @@ Error: image has dependent child images
#### 开发环境
```bash
## 定期清理虚悬镜像
$ docker image prune -f
@@ -253,16 +234,13 @@ $ docker image prune -f
$ docker system prune -a
```
#### CI/CD 环境
```bash
## 只保留最近使用的镜像
$ docker image prune -a --filter "until=72h" -f
```
#### 查看空间占用
```bash
@@ -273,5 +251,4 @@ Containers 5 2 100MB 80MB (80%)
Local Volumes 8 2 500MB 400MB (80%)
Build Cache 0 0 0B 0B
```
---

View File

@@ -13,7 +13,6 @@
```bash
$ docker run --name webserver -d -p 80:80 nginx
```
这条命令会用 `nginx` 镜像启动一个容器命名为 `webserver`并且映射了 80 端口这样我们可以用浏览器去访问这个 `nginx` 服务器
如果是在本机运行的 Docker那么可以直接访问`http://localhost`如果是在虚拟机云服务器上安装的 Docker则需要将 `localhost` 换为虚拟机地址或者实际云服务器地址
@@ -30,7 +29,6 @@ root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/inde
root@3729b97e8226:/# exit
exit
```
我们以交互式终端方式进入 `webserver` 容器并执行了 `bash` 命令也就是获得一个可操作的 Shell
然后我们用 `<h1>Hello, Docker!</h1>` 覆盖了 `/usr/share/nginx/html/index.html` 的内容
@@ -60,7 +58,6 @@ A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp
```
现在我们定制好了变化我们希望能将其保存下来形成镜像
要知道当我们运行一个容器的时候 (如果不使用卷的话)我们做的任何文件修改都会被记录于容器存储层里 Docker 提供了一个 `docker commit` 命令可以将容器的存储层保存下来成为镜像换句话说就是在原有镜像的基础上再叠加上容器的存储层并构成新的镜像以后我们运行这个新镜像的时候就会拥有原有容器最后的文件变化
@@ -70,7 +67,6 @@ A /var/cache/nginx/uwsgi_temp
```bash
docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]
```
我们可以用下面的命令将容器保存为镜像
```bash
@@ -81,7 +77,6 @@ $ docker commit \
nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214
```
其中 `--author` 是指定修改的作者 `--message` 则是记录本次修改的内容这点和 `git` 版本控制相似不过这里这些信息可以省略留空
我们可以在 `docker image ls` 中看到这个新定制的镜像
@@ -93,7 +88,6 @@ nginx v2 07e334659748 9 seconds ago
nginx 1.27 05a60462f8ba 12 days ago 181.5 MB
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB
```
我们还可以用 `docker history` 具体查看镜像内的历史记录如果比较 `nginx:latest` 的历史记录我们会发现新增了我们刚刚提交的这一层
```bash
@@ -109,13 +103,11 @@ e43d811ce2f4 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "da
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:23aa4f893e3288698c 123 MB
```
新的镜像定制好后我们可以来运行这个镜像
```bash
docker run --name web2 -d -p 81:80 nginx:v2
```
这里我们命名为新的服务为 `web2`并且映射到 `81` 端口访问 `http://localhost:81` 看到结果其内容应该和之前修改后的 `webserver` 一样
至此我们第一次完成了定制镜像使用的是 `docker commit` 命令手动操作给旧的镜像添加了新的一层形成新的镜像对镜像多层存储应该有了更直观的感觉

View File

@@ -11,7 +11,6 @@ Docker 提供了 `docker init` 命令,可以根据项目类型自动生成 Doc
```bash
$ docker init
```
该命令会交互式地询问项目类型 ( GoNode.jsPythonRust )并生成符合最佳实践的配置文件对于新项目这是推荐的起步方式
### 4.5.2 手动创建 Dockerfile
@@ -25,14 +24,12 @@ $ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
```
其内容为
```docker
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
```
这个 Dockerfile 很简单一共就两行涉及到了两条指令`FROM` `RUN`
### 4.5.3 FROM 指定基础镜像
@@ -49,7 +46,6 @@ RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
FROM scratch
...
```
如果你以 `scratch` 为基础镜像的话意味着你不以任何镜像为基础接下来所写的指令将作为镜像第一层开始存在
不以任何系统为基础直接将可执行文件复制进镜像的做法并不罕见对于 Linux 下静态编译的程序来说并不需要有操作系统提供运行时支持所需的一切库都已经在可执行文件里了因此直接 `FROM scratch` 会让镜像体积更加小巧使用 [Go 语言](https://golang.google.cn/)开发的应用很多会使用这种方式来制作镜像,这也是有人认为 Go 是特别适合容器微服务架构的语言的原因之一。
@@ -63,7 +59,6 @@ FROM scratch
```docker
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
```
* *exec* 格式`RUN [“可执行文件”, “参数1”, “参数2”]`这更像是函数调用中的格式
Dockerfile 中每一个指令都会建立一层`RUN` 也不例外每一个 `RUN` 的行为就和刚才我们手工建立镜像的过程一样新建立一层在其上执行这些命令执行结束后`commit` 这一层的修改构成新的镜像
@@ -93,7 +88,6 @@ Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
Removing intermediate container 9cdc27646c7b
Successfully built 44aa4490ce2c
```
从命令的输出结果中我们可以清晰的看到镜像的构建过程 `Step 2` 如同我们之前所说的那样`RUN` 指令启动了一个容器 `9cdc27646c7b`执行了所要求的命令并最后提交了这一层 `44aa4490ce2c`随后删除了所用到的这个容器 `9cdc27646c7b`
这里我们使用了 `docker build` 命令进行镜像构建其格式为
@@ -101,7 +95,6 @@ Successfully built 44aa4490ce2c
```bash
docker build [选项] <上下文路径/URL/->
```
在这里我们指定了最终镜像的名称 `-t nginx:v3`构建成功后我们可以像之前运行 `nginx:v2` 那样来运行这个镜像其结果会和 `nginx:v2` 一样
### 4.5.6 镜像构建上下文
@@ -119,7 +112,6 @@ docker build [选项] <上下文路径/URL/->
```docker
COPY ./package.json /app/
```
这并不是要复制执行 `docker build` 命令所在的目录下的 `package.json`也不是复制 `Dockerfile` 所在目录下的 `package.json`而是复制 **上下文 (context)** 目录下的 `package.json`
因此`COPY` 这类指令中的源文件的路径都是*相对路径*这也是初学者经常会问的为什么 `COPY ../package.json /app` 或者 `COPY /opt/xxxx /app` 无法工作的原因因为这些路径已经超出了上下文的范围Docker 引擎无法获得这些位置的文件如果真的需要那些文件应该将它们复制到上下文目录中去
@@ -133,7 +125,6 @@ $ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...
```
理解构建上下文对于镜像构建是很重要的避免犯一些不应该的错误比如有些初学者在发现 `COPY /opt/xxxx /app` 不工作后于是干脆将 `Dockerfile` 放到了硬盘根目录去构建结果发现 `docker build` 执行后在发送一个几十 GB 的东西极为缓慢而且很容易构建失败那是因为这种做法是在让 `docker build` 打包整个硬盘这显然是使用错误
一般来说应该会将 `Dockerfile` 置于一个空目录下或者项目根目录下如果该目录下没有所需文件那么应该把所需文件复制一份过来如果目录下有些东西确实不希望构建时传给 Docker 引擎那么可以用 `.gitignore` 一样的语法写一个 `.dockerignore`该文件是用于剔除不需要作为上下文传递给 Docker 引擎的
@@ -151,7 +142,6 @@ Sending build context to Docker daemon 2.048 kB
或许你已经注意到了`docker build` 还支持从 URL 构建比如可以直接从 Git repo 中构建
```bash
## $env:DOCKER_BUILDKIT=0
## export DOCKER_BUILDKIT=0
@@ -168,7 +158,6 @@ Removing intermediate container d2a513a760ed
---> 038ad4142d2b
Successfully built 038ad4142d2b
```
这行命令指定了构建所需的 Git repo并且指定分支为 `master`构建目录为 `/amd64/hello-world/`然后 Docker 就会自己去 `git clone` 这个项目切换到指定分支并进入到指定目录后开始构建
#### 用给定的 tar 压缩包构建
@@ -176,7 +165,6 @@ Successfully built 038ad4142d2b
```bash
$ docker build http://server/context.tar.gz
```
如果所给出的 URL 不是个 Git repo而是个 `tar` 压缩包那么 Docker 引擎会下载这个包并自动解压缩以其作为上下文开始构建
#### 从标准输入中读取 Dockerfile 进行构建
@@ -184,13 +172,11 @@ $ docker build http://server/context.tar.gz
```bash
docker build - < Dockerfile
```
```bash
cat Dockerfile | docker build -
```
如果标准输入传入的是文本文件则将其视为 `Dockerfile`并开始构建这种形式由于直接从标准输入中读取 Dockerfile 的内容它没有上下文因此不可以像其他方法那样可以将本地文件 `COPY` 进镜像之类的事情
#### 从标准输入中读取上下文压缩包进行构建
@@ -198,5 +184,4 @@ cat Dockerfile | docker build -
```bash
$ docker build - < context.tar.gz
```
如果发现标准输入的文件格式是 `gzip``bzip2` 以及 `xz` 的话将会使其为上下文压缩包直接将其展开将里面视为上下文并开始构建

View File

@@ -18,7 +18,6 @@ $ docker import \
Downloading from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz
sha256:412b8fc3e3f786dca0197834a698932b9c51b69bd8cf49e100c35d38c9879213
```
这条命令自动下载了 `ubuntu-16.04-x86_64.tar.gz` 文件并且作为根文件系统展开导入并保存为镜像 `openvz/ubuntu:16.04`
导入成功后我们可以用 `docker image ls` 看到这个导入的镜像
@@ -28,7 +27,6 @@ $ docker image ls openvz/ubuntu
REPOSITORY TAG IMAGE ID CREATED SIZE
openvz/ubuntu 16.04 412b8fc3e3f7 55 seconds ago 505MB
```
如果我们查看其历史的话会看到描述中有导入的文件链接
```bash
@@ -36,7 +34,6 @@ $ 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
```
### 4.6.2 Docker 镜像的导入和导出 `docker save` `docker load`
Docker 还提供了 `docker save` `docker load` 命令用以将镜像保存为一个文件然后传输到另一个位置上再加载进来这是在没有 Docker Registry 时的做法现在已经不推荐镜像迁移应该直接使用 Docker Registry无论是直接使用 Docker Hub 还是使用内网私有 Registry 都可以
@@ -52,7 +49,6 @@ $ docker image ls alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest baa5d63471ea 5 weeks ago 4.803 MB
```
保存镜像的命令为
```bash
@@ -60,7 +56,6 @@ $ docker save alpine -o filename
$ file filename
filename: POSIX tar archive
```
这里的 filename 可以为任意名称甚至任意后缀名但文件的本质都是归档文件
**注意如果同名则会覆盖 (没有警告)**
@@ -70,14 +65,12 @@ filename: POSIX tar archive
```bash
$ docker save alpine | gzip > alpine-latest.tar.gz
```
然后我们将 `alpine-latest.tar.gz` 文件复制到了到了另一个机器上可以用下面这个命令加载镜像
```bash
$ docker load -i alpine-latest.tar.gz
Loaded image: alpine:latest
```
如果我们结合这两个命令以及 `ssh` 甚至 `pv` 的话利用 Linux 强大的管道我们可以写一个命令完成从一个机器将镜像迁移到另一个机器并且带进度条的功能
```bash

View File

@@ -38,7 +38,6 @@ flowchart TD
end
Note["所有的写操作都在容器层这里"] -.-> L4
```
* **读取文件**当容器需要读取文件时Docker 会从最上层 (容器层) 开始向下层 (镜像层) 寻找直到找到该文件为止
* **修改文件**当容器需要修改某个文件时Docker 会从下层镜像中将该文件复制到上层的容器层然后对副本进行修改这被称为 **写时复制 (Copy-on-WriteCoW)** 策略
* **删除文件**当容器删除某个文件时Docker 并不是真的去下层删除它 (因为下层是只读的)而是在容器层创建一个特殊的 白障 (Whiteout) 文件用来标记该文件已被删除从而在容器视图中隐藏它