bigywater.tech
Docker · 19

OCI 镜像规范:manifest、config 与 layer 的 JSON 结构

镜像到底是什么文件?这篇不看文档的描述,直接把一个镜像 docker save 成 tar,打开 index.json、manifest、config 和每一层,逐个重新计算 sha256,看 OCI 镜像规范里的各个字段在真实镜像里长什么样。

5 个 blob
1 个 manifest、1 个 config、3 个层;文件名等于内容的 sha256,重算全部一致
9 条历史 / 3 层
config 的 history 里 6 条是 empty_layer,ENV、LABEL、EXPOSE、ENTRYPOINT、CMD 不产生层
digest ≠ diff_id
压缩数据的 sha256 和解压 tar 的 sha256,3 层都是两个不同的值

tar 里是什么

实测 · 本机执行输出
  顶层条目:['blobs', 'index.json', 'manifest.json', 'oci-layout']
  blobs/sha256/ 下共 5 个文件,文件名就是内容的 sha256
  oci-layout 的内容:{'imageLayoutVersion': '1.0.0'}
  逐个重新计算 sha256 与文件名对不上的 blob 数:0

用 docker save 导出的是 OCI 镜像布局:blobs/sha256/ 放所有内容,index.json 是入口,oci-layout 标版本,另外还有一个 Docker 自己加的 manifest.json。5 个 blob 的文件名都等于自己内容的 sha256(我逐个重算过)——这就是“内容寻址”。

manifest

实测 · 本机执行输出
  index.json:schemaVersion=2,1 个条目;第 1 个 mediaType=application/vnd.oci.image.manifest.v1+json size=857
  manifest 的键:['schemaVersion', 'mediaType', 'config', 'layers'];mediaType=application/vnd.oci.image.manifest.v1+json
  config:mediaType=application/vnd.oci.image.config.v1+json size=1864 digest=84689109decd…
  layers[0]:mediaType=application/vnd.oci.image.layer.v1.tar+gzip size=4092319 digest=3f26bc2dec0b…
  layers[1]:mediaType=application/vnd.oci.image.layer.v1.tar+gzip size=108 digest=891034b893e2…
  layers[2]:mediaType=application/vnd.oci.image.layer.v1.tar+gzip size=112 digest=b1de4487bdf8…

从 index.json 指向的 manifest(OCI image manifest,857 字节)里,只有 config 和 layers 两样:config 是一个 JSON,layers 是 3 个 tar+gzip,每一项都带 mediaType、size、digest。这个镜像是用 --provenance=false --sbom=false 构建的,所以没有附加证明(见 c11、c13)。

config

实测 · 本机执行输出
  config 的顶层键:['architecture', 'config', 'created', 'history', 'os', 'rootfs']
  config.Env=['PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', 'APP_MODE=demo']
  config.Entrypoint=['/bin/sh', '-c'] Cmd=['cat /hello.txt'] ExposedPorts={'8080/tcp': {}} Labels={'bwlab': '1', 'purpose': 'c14'}
  architecture=arm64 os=linux created=2026-10-10T08:25:59
  rootfs.type=layers;diff_ids 共 3 个

config 记录运行时的信息:环境变量、Entrypoint、Cmd、暴露的端口、标签,以及 architecture / os 和 rootfs 的 diff_ids。docker run 的默认行为就来自这里。

history 与层

实测 · 本机执行输出
  history 共 9 条,其中 empty_layer=true 的有 6 条;layers 有 3 层
    (有层) ADD alpine-minirootfs-3.20.10-aarch64.tar.gz / # buildkit
    (无层) CMD ["/bin/sh"]
    (无层) ENV APP_MODE=demo
    (无层) LABEL purpose=c14
    (有层) RUN /bin/sh -c echo hello > /hello.txt # buildkit
    (有层) COPY data.txt /data.txt # buildkit
    (无层) EXPOSE [8080/tcp]
    (无层) ENTRYPOINT ["/bin/sh" "-c"]
    (无层) CMD ["cat /hello.txt"]

9 条 history 里只有 3 条产生了层(ADD rootfs、RUN、COPY),其余 6 条 empty_layer 只改 config。基础镜像自带的 CMD ["/bin/sh"] 也在其中。想估算镜像大小,看的是层,不是指令条数。

digest 与 diff_id

实测 · 本机执行输出
  layers[0]:压缩后 4092319 字节,解压后 9107968 字节;压缩数据的 sha256 与 manifest 里的 digest 一致:True;解压数据的 sha256 与 diff_ids[0] 一致:True;digest 和 diff_id 相同吗?False
  layers[1]:压缩后 108 字节,解压后 2048 字节;压缩数据的 sha256 与 manifest 里的 digest 一致:True;解压数据的 sha256 与 diff_ids[1] 一致:True;digest 和 diff_id 相同吗?False
  layers[2]:压缩后 112 字节,解压后 2560 字节;压缩数据的 sha256 与 manifest 里的 digest 一致:True;解压数据的 sha256 与 diff_ids[2] 一致:True;digest 和 diff_id 相同吗?False

manifest 里的 layer digest 是压缩后数据的 sha256,config 里的 diff_id 是解压后 tar 的 sha256。三层都验证了两者各自成立、彼此不同。我的理解是前者用来校验传输/存储,后者用来描述文件系统内容,与压缩方式无关;这是从规范的字段含义和上面的验证推断的,没有做换压缩算法的实验。

层的内容

实测 · 本机执行输出
  最后一层(COPY data.txt)的文件:data.txt(1001 字节)
  倒数第二层(RUN echo hello)的文件:hello.txt(6 字节)

最后一层只含 data.txt,倒数第二层只含 hello.txt:每层只带这一步新增或改动的文件(删除用 whiteout 标记,见 c3)。

分步演示

OCI 镜像规范
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。镜像在本机用 docker build --provenance=false --sbom=false 构建(基础 alpine:3.20 用 dpull.py 下载后 docker load),docker save 后用 Python 的 tarfile / hashlib / gzip 读取;所有 sha256 都在 tar 里重新计算;实验后已删除镜像
    • 没有验证:docker pull 从 registry 拿到的 manifest 形式(见后面的 registry 文章)、zstd 压缩的层、多平台索引的 platform 字段、annotations 字段、docker save 的 manifest.json 的含义、换压缩算法后 digest 与 diff_id 的变化
    京ICP备2026067502号-1京公网安备11010502063487号