ᕕ( ᐛ )ᕗ Jimyag's Blog

Docker 镜像构建原理:Manifest 与 SHA256 计算详解

· 1670 字 · 约 8 分钟

很多人使用 Docker 很久,却不太清楚镜像到底是怎么构建出来的。manifestconfiglayer 这些概念听起来很抽象,SHA256 计算更是像黑盒。

本文通过一个实际的 Docker 镜像构建案例,带你一步步拆解镜像内部结构,验证 SHA256 的计算方式,让你彻底理解 Docker 镜像的构建原理。

准备一个演示镜像

先创建一个包含多个指令的 Dockerfile,这样能看到多个 layer 的生成过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
FROM alpine:3.18

# 第一层:安装基础工具
RUN apk add --no-cache curl jq

# 第二层:创建应用目录
WORKDIR /app

# 第三层:复制应用文件
RUN echo '#!/bin/sh' > /app/start.sh && \
    echo 'echo "Hello from Docker"' >> /app/start.sh && \
    echo 'echo "Container started at: $(date)"' >> /app/start.sh && \
    chmod +x /app/start.sh

# 第四层:设置环境变量
ENV APP_VERSION=1.0.0
ENV APP_NAME=demo-app

# 第五层:设置启动命令
CMD ["/app/start.sh"]

构建镜像:

1
2
# 禁用 BuildKit 以看到传统的构建过程
DOCKER_BUILDKIT=0 docker build --no-cache -t demo-app:1.0 .

构建过程输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
Sending build context to Docker daemon  2.048kB
Step 1/7 : FROM alpine:3.18
 ---> de2b9975f8fd
Step 2/7 : RUN apk add --no-cache curl jq
 ---> Running in 8798ebf75a3f
...
 ---> 6888b70c8d5f
Step 3/7 : WORKDIR /app
 ---> Running in 2c814f8bfbdf
 ---> 4bb06c8ad8b4
Step 4/7 : RUN echo '#!/bin/sh' > /app/start.sh && ...
 ---> Running in 95a96f3ef551
 ---> f38fbafae081
Step 5/7 : ENV APP_VERSION=1.0.0
 ---> fb3c4c20e49a
Step 6/7 : ENV APP_NAME=demo-app
 ---> 1909f3861474
Step 7/7 : CMD ["/app/start.sh"]
 ---> 43ca17c11025
Successfully built 43ca17c11025
Successfully tagged demo-app:1.0

注意到每个 step 都生成了一个镜像 ID(如 de2b9975f8fd6888b70c8d5f 等),这些就是每一层的结果。

查看镜像构建历史

使用 docker history 查看每一层的详细信息:

1
docker history demo-app:1.0 --no-trunc

输出:

1
2
3
4
5
6
7
8
9
IMAGE                                                                     CREATED         CREATED BY                                                                                                                                                                                              SIZE      COMMENT
sha256:43ca17c1102512f884b06414b71609902362c57b19e6642fc2e32ffdf7ea5426   6 seconds ago   /bin/sh -c #(nop)  CMD ["/app/start.sh"]                                                                                                                                                                0B        
sha256:1909f38614741c93a15a80d70dc063a93faad1bc969bf74e83e184f59d47d974   6 seconds ago   /bin/sh -c #(nop)  ENV APP_NAME=demo-app                                                                                                                                                                0B        
sha256:fb3c4c20e49ae6440badb18d1b820b43e6ab18ac80ed1d26510974c3a4a431f6   6 seconds ago   /bin/sh -c #(nop)  ENV APP_VERSION=1.0.0                                                                                                                                                                0B        
sha256:f38fbafae081301da86a3e443e1709b9572f19f1fbe94eedddc90544f1e43d98   7 seconds ago   /bin/sh -c echo '#!/bin/sh' > /app/start.sh && ...                                                                                                                                                     2.81kB    
sha256:4bb06c8ad8b438583c22f01add8a53474c9659a52807655604dff84ddd3f66f4   7 seconds ago   /bin/sh -c #(nop) WORKDIR /app                                                                                                                                                                          0B        
sha256:6888b70c8d5f3b9ab23b065ebaf4c43db726812c8350e4f0c7901433263c37d3   7 seconds ago   /bin/sh -c apk add --no-cache curl jq                                                                                                                                                                   6.11MB    
sha256:de2b9975f8fd4ab0d5ea39f52592791fadff62c0592a6e7db5640dc0d6469a01   13 months ago   CMD ["/bin/sh"]                                                                                                                                                                                         0B        buildkit.dockerfile.v0
<missing>                                                                 13 months ago   ADD alpine-minirootfs-3.18.12-aarch64.tar.gz / # buildkit                                                                                                                                               7.67MB    buildkit.dockerfile.v0

关键信息:

  • 每一列的 IMAGE 列就是该层的 SHA256(完整版本)
  • SIZE 显示这一层的大小(0B 表示只修改元数据,不产生文件层)
  • #(nop) 表示这是一个元数据操作,不执行 shell 命令
  • empty_layer: true 的层(如 ENV、CMD)不会产生新的文件层,只会更新 config

导出镜像并查看内部结构

Docker 镜像本质上是一组文件,可以用 docker save 导出:

1
docker save -o demo-app.tar demo-app:1.0

解压查看:

1
2
3
mkdir -p demo-extract && cd demo-extract
tar -xf ../demo-app.tar
ls -la

输出:

1
2
3
4
5
6
7
8
total 16
drwxr-xr-x  7 jimyag wheel  224  3月 28 21:43 .
drwxrwxrwt 47 root   wheel 1504  3月 28 21:43 ..
drwxr-xr-x  3 jimyag wheel   96  3月 28 21:43 blobs
-rw-r--r--  1 jimyag wheel  358  1月 1  1970 index.json
-rw-r--r--  1 jimyag wheel 1371  1月 1  1970 manifest.json
-rw-r--r--  1 jimyag wheel   31  1月 1  1970 oci-layout
-rw-r--r--  1 jimyag wheel   88  1月 1  1970 repositories

镜像的结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
demo-extract/
├── blobs/
│   └── sha256/
│       ├── 43ca17c1102512f884b06414b71609902362c57b19e6642fc2e32ffdf7ea5426  (Config)
│       ├── fe19fc8e7d7fe23e67d7b1d6b1b66897eda70c2125e16684bfce26328a4faedc  (OCI Manifest)
│       ├── 171a26c7bc56cc6ba67549042b24db9f5bc7cc7d4f195e8f03aaf58e956b2544  (Layer 1: Alpine 基础)
│       ├── 301f53f63f5929afbde5311a7ab0b9c9653f5236ada0811da4abaa8976b7d166  (Layer 2: curl/jq)
│       ├── ced34a41d9af2b41a826a35f57a23eddd3b8e1df58eff40208c22e931dc69b58  (Layer 3: WORKDIR)
│       └── defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36  (Layer 4: start.sh)
├── index.json        (OCI 格式的索引)
├── manifest.json     (Docker 格式的 manifest)
├── oci-layout        (OCI 布局标记)
└── repositories      (仓库标签信息)

理解 Manifest

Manifest 是镜像的"目录",记录了镜像包含哪些层和配置文件。

Docker 格式的 manifest.json

1
cat manifest.json | jq .

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[
  {
    "Config": "blobs/sha256/43ca17c1102512f884b06414b71609902362c57b19e6642fc2e32ffdf7ea5426",
    "RepoTags": [
      "demo-app:1.0"
    ],
    "Layers": [
      "blobs/sha256/171a26c7bc56cc6ba67549042b24db9f5bc7cc7d4f195e8f03aaf58e956b2544",
      "blobs/sha256/301f53f63f5929afbde5311a7ab0b9c9653f5236ada0811da4abaa8976b7d166",
      "blobs/sha256/ced34a41d9af2b41a826a35f57a23eddd3b8e1df58eff40208c22e931dc69b58",
      "blobs/sha256/defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36"
    ]
  }
]

关键字段:

  • Config: 指向配置文件的路径
  • RepoTags: 镜像的标签
  • Layers: 所有文件层的路径(按顺序从底到顶)

OCI 格式的 index.json

1
cat index.json | jq .

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:fe19fc8e7d7fe23e67d7b1d6b1b66897eda70c2125e16684bfce26328a4faedc",
      "size": 854,
      "annotations": {
        "io.containerd.image.name": "docker.io/library/demo-app:1.0",
        "org.opencontainers.image.ref.name": "1.0"
      }
    }
  ]
}

OCI 格式使用 index.json 指向实际的 manifest 文件(fe19fc8e...)。

OCI Manifest 文件

1
cat blobs/sha256/fe19fc8e7d7fe23e67d7b1d6b1b66897eda70c2125e16684bfce26328a4faedc | jq .

输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:43ca17c1102512f884b06414b71609902362c57b19e6642fc2e32ffdf7ea5426",
    "size": 2666
  },
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar",
      "digest": "sha256:171a26c7bc56cc6ba67549042b24db9f5bc7cc7d4f195e8f03aaf58e956b2544",
      "size": 7956992
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar",
      "digest": "sha256:301f53f63f5929afbde5311a7ab0b9c9653f5236ada0811da4abaa8976b7d166",
      "size": 6397952
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar",
      "digest": "sha256:ced34a41d9af2b41a826a35f57a23eddd3b8e1df58eff40208c22e931dc69b58",
      "size": 1536
    },
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar",
      "digest": "sha256:defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36",
      "size": 7680
    }
  ]
}

Manifest 的核心作用:

  1. 引用 config 文件(通过 digest)
  2. 引用所有 layer(通过 digest)
  3. 记录每个文件的大小和媒体类型

理解 Config

Config 文件记录了镜像的所有配置信息和构建历史。

1
cat blobs/sha256/43ca17c1102512f884b06414b71609902362c57b19e6642fc2e32ffdf7ea5426 | jq .

输出(关键部分):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{
  "architecture": "arm64",
  "config": {
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "APP_VERSION=1.0.0",
      "APP_NAME=demo-app"
    ],
    "Cmd": [
      "/app/start.sh"
    ],
    "WorkingDir": "/app"
  },
  "created": "2026-03-28T13:43:51.519899657Z",
  "docker_version": "28.5.2",
  "history": [
    {
      "created": "2025-02-14T03:03:06Z",
      "created_by": "ADD alpine-minirootfs-3.18.12-aarch64.tar.gz / # buildkit",
      "comment": "buildkit.dockerfile.v0"
    },
    {
      "created": "2025-02-14T03:03:06Z",
      "created_by": "CMD [\"/bin/sh\"]",
      "comment": "buildkit.dockerfile.v0",
      "empty_layer": true
    },
    {
      "created": "2026-03-28T13:43:50.329678511Z",
      "created_by": "/bin/sh -c apk add --no-cache curl jq"
    },
    {
      "created": "2026-03-28T13:43:50.467841313Z",
      "created_by": "/bin/sh -c #(nop) WORKDIR /app"
    },
    {
      "created": "2026-03-28T13:43:50.862205766Z",
      "created_by": "/bin/sh -c echo '#!/bin/sh' > /app/start.sh && ..."
    },
    {
      "created": "2026-03-28T13:43:51.098903795Z",
      "created_by": "/bin/sh -c #(nop)  ENV APP_VERSION=1.0.0",
      "empty_layer": true
    },
    {
      "created": "2026-03-28T13:43:51.384462712Z",
      "created_by": "/bin/sh -c #(nop)  ENV APP_NAME=demo-app",
      "empty_layer": true
    },
    {
      "created": "2026-03-28T13:43:51.519899657Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"/app/start.sh\"]",
      "empty_layer": true
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:171a26c7bc56cc6ba67549042b24db9f5bc7cc7d4f195e8f03aaf58e956b2544",
      "sha256:301f53f63f5929afbde5311a7ab0b9c9653f5236ada0811da4abaa8976b7d166",
      "sha256:ced34a41d9af2b41a826a35f57a23eddd3b8e1df58eff40208c22e931dc69b58",
      "sha256:defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36"
    ]
  }
}

Config 的关键内容:

  1. config 字段:运行时配置(Env, Cmd, WorkingDir 等)
  2. history 字段:构建历史,记录每一步的命令和时间
    • empty_layer: true 表示该步骤不产生文件层(如 ENV、CMD)
  3. rootfs.diff_ids:所有文件层的未压缩 SHA256
    • 注意:这是未压缩的 tar 文件的 SHA256
    • 与 manifest 中的 digest(压缩后的 SHA256)不同

理解 Layer

Layer 是实际的文件系统层,每个 layer 是一个 tar 包,包含文件系统的变更。

查看 Layer 的类型和大小

1
ls -lh blobs/sha256/

输出:

1
2
3
4
-rw-r--r-- 1 jimyag wheel 7.6M 171a26c7bc56cc6ba67549042b24db9f5bc7cc7d4f195e8f03aaf58e956b2544  (Alpine 基础)
-rw-r--r-- 1 jimyag wheel 6.1M 301f53f63f5929afbde5311a7ab0b9c9653f5236ada0811da4abaa8976b7d166  (curl/jq)
-rw-r--r-- 1 jimyag wheel 1.5K ced34a41d9af2b41a826a35f57a23eddd3b8e1df58eff40208c22e931dc69b58  (WORKDIR)
-rw-r--r-- 1 jimyag wheel 7.5K defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36  (start.sh)

查看 Layer 的文件类型

1
file blobs/sha256/defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36

输出:

1
blobs/sha256/defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36: POSIX tar archive

可以看到 layer 是未压缩的 POSIX tar 包。

注意:这是 docker save 导出的本地镜像格式。实际推送到 Registry(如 Docker Hub)时,layer 会被压缩成 gzip 格式。这是理解 diff_ids 和 digest 关系的关键。

查看 Layer 的内容

1
tar -tvf blobs/sha256/defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36

输出:

1
2
3
4
5
6
drwxr-xr-x  0 0      0           0  3月 28 21:43 app/
-rwxr-xr-x  0 0      0          72  3月 28 21:43 app/start.sh
drwxr-xr-x  0 0      0           0  3月 28 21:43 etc/
drwxr-xr-x  0 0      0           0  2月 14  2025 etc/ssl/
drwxr-xr-x  0 0      0           0  3月 28 21:43 etc/ssl/certs/
-rw-r--r--  0 0      0        2739  3月 28 21:43 etc/ssl/certs/orbstack-root.crt

这个 layer 包含:

  • app/start.sh - 我们的启动脚本
  • etc/ssl/certs/orbstack-root.crt - OrbStack 添加的根证书

提取 Layer 中的文件

可以直接从 tar 包中提取单个文件:

1
tar -xOf blobs/sha256/defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36 app/start.sh

输出:

1
2
3
#!/bin/sh
echo "Hello from Docker"
echo "Container started at: $(date)"

这就是我们在 Dockerfile 中创建的启动脚本。

SHA256 计算验证

现在来验证 SHA256 是如何计算的。关键原则:文件名就是文件内容的 SHA256

验证 Config 文件的 SHA256

1
sha256sum < blobs/sha256/43ca17c1102512f884b06414b71609902362c57b19e6642fc2e32ffdf7ea5426

输出:

1
43ca17c1102512f884b06414b71609902362c57b19e6642fc2e32ffdf7ea5426  -

确实,文件名就是内容的 SHA256。

验证 OCI Manifest 的 SHA256

1
sha256sum < blobs/sha256/fe19fc8e7d7fe23e67d7b1d6b1b66897eda70c2125e16684bfce26328a4faedc

输出:

1
fe19fc8e7d7fe23e67d7b1d6b1b66897eda70c2125e16684bfce26328a4faedc  -

同样正确。

验证 Layer 的 SHA256

1
sha256sum < blobs/sha256/defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36

输出:

1
defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36  -

完美匹配!

diff_ids vs digest

这是容易混淆的概念:

  • diff_ids:在 Config 文件中,是未压缩的 tar 文件的 SHA256
  • digest:在 Manifest 中,通常是压缩后的 tar.gz 文件的 SHA256

但在我们的案例中,所有 layer 都是未压缩的 tar 包,所以 diff_ids 和 digest 完全相同:

Config 中的 diff_ids:

1
2
3
4
5
6
"diff_ids": [
  "sha256:171a26c7bc56cc6ba67549042b24db9f5bc7cc7d4f195e8f03aaf58e956b2544",
  "sha256:301f53f63f5929afbde5311a7ab0b9c9653f5236ada0811da4abaa8976b7d166",
  "sha256:ced34a41d9af2b41a826a35f57a23eddd3b8e1df58eff40208c22e931dc69b58",
  "sha256:defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36"
]

Manifest 中的 digest:

1
2
3
4
5
6
"layers": [
  {"digest": "sha256:171a26c7bc56cc6ba67549042b24db9f5bc7cc7d4f195e8f03aaf58e956b2544"},
  {"digest": "sha256:301f53f63f5929afbde5311a7ab0b9c9653f5236ada0811da4abaa8976b7d166"},
  {"digest": "sha256:ced34a41d9af2b41a826a35f57a23eddd3b8e1df58eff40208c22e931dc69b58"},
  {"digest": "sha256:defc394ea218775f58eea09fa8bc8c89b9c27ffa614710cb47982ddf4e446b36"}
]

如果 layer 被压缩(gzip),那么:

  • digest = SHA256(layer.tar.gz)
  • diff_ids = SHA256(layer.tar)
  • digest ≠ diff_ids

本地存储 vs Registry 存储

理解了 diff_ids 和 digest 的区别后,一个关键问题是:什么时候会压缩?

本地存储(Docker daemon)

本地 Docker 存储中,layer 通常不压缩

1
2
3
4
5
/var/lib/docker/overlay2/
├── <layer-id>/
   └── diff/           解压后的文件系统
└── l/
    └── <symlinks>

原因:

  • 直接挂载使用,启动容器快
  • OverlayFS 支持直接挂载目录

这就是为什么我的演示中看到的是未压缩的 tar 包,且 diff_ids 和 digest 相同。

Registry 存储(Docker Hub 等)

推送到 Registry 时,layer 会压缩成 gzip

1
2
3
4
5
6
# 推送镜像时
docker push your-registry/demo-app:1.0

# 实际上传的文件:
# layer.tar.gz(压缩后的 tar 包)
# manifest.json(引用压缩后的 digest)

此时 Manifest 中的 digest 会变化:

1
2
3
4
5
6
7
8
9
{
  "layers": [
    {
      "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:abc123...",   压缩后的 SHA256
      "size": 1234567
    }
  ]
}

而 Config 中的 diff_ids 保持不变(因为它是未压缩 tar 的 SHA256):

1
2
3
4
5
6
7
{
  "rootfs": {
    "diff_ids": [
      "sha256:def456..."   未压缩的 SHA256
    ]
  }
}

实际验证方法

可以通过 docker manifest inspect 查看远程镜像的压缩情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 查看远程镜像的 manifest
docker manifest inspect alpine:latest

# 输出示例:
{
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 2811981,
      "digest": "sha256:4f4fb700ef..."
    }
  ]
}

注意 mediaTypetar.gzip,说明 Registry 存储时是压缩的。

为什么要这样设计?

  1. 双重校验

    • Registry 用 digest 验证压缩文件的完整性
    • Docker 用 diff_ids 验证解压后文件的完整性
  2. 性能优化

    • 本地存储不压缩 → 快速挂载启动
    • Registry 存储压缩 → 节省存储和网络带宽
  3. 去重能力

    • 相同内容的 tar 包 → 相同的 diff_ids
    • 即使压缩方式不同,也能识别内容相同的层

完整的 SHA256 计算流程

总结 Docker 镜像 SHA256 的计算流程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
1. 构建每一层(RUN、COPY 等)
2. 将文件系统变更打包成 tar 包
3. 计算 tar 包的 SHA256
   - 如果未压缩:digest = SHA256(tar)
   - 如果压缩:digest = SHA256(tar.gz), diff_ids = SHA256(tar)
4. 生成 Config JSON
   - 包含所有 diff_ids
   - 包含构建历史
5. 计算 Config JSON 的 SHA256
6. 生成 Manifest JSON
   - 引用 config digest
   - 引用所有 layer digest
7. 计算 Manifest JSON 的 SHA256(用于 OCI 格式)

快速查看镜像信息的命令

日常工作中,可以使用这些命令快速查看镜像信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 查看镜像历史
docker history <image> --no-trunc

# 查看镜像详细信息
docker image inspect <image>

# 查看镜像 manifest(需要启用实验性功能)
docker manifest inspect <image>

# 导出镜像并查看结构
docker save -o image.tar <image>
tar -tf image.tar | head -20

# 使用 skopeo 查看远程镜像 manifest(需安装 skopeo)
skopeo inspect docker://alpine:latest --raw | jq .

总结

通过这个实际案例,我们验证了:

  1. 文件名即 SHA256:每个文件(config、manifest、layer)的文件名都是其内容的 SHA256
  2. Layer 是 tar 包:每个 layer 是一个 POSIX tar archive,包含文件系统的变更
  3. Manifest 是目录:Manifest 记录了镜像包含的所有文件及其 digest
  4. Config 是配置中心:Config 包含运行时配置、构建历史和 diff_ids
  5. diff_ids vs digest:未压缩时两者相同,压缩时 digest 是压缩后的 SHA256

理解这些原理后,Docker 镜像不再是黑盒,你可以:

  • 手动验证镜像的完整性
  • 排查镜像构建问题
  • 优化镜像大小(减少层数、合并层)
  • 理解镜像分发和缓存机制

下次看到镜像的 SHA256 时,你就知道它是怎么计算出来的了。

参考资料

#Docker #Container #Image #Sha256