Notes on Docker image manifests

A Docker image contains a file system. This file system is broken down into different layers. Each layer is a tar-based blob which contains the changes to do on the previous layer. By stacking the layers on top of each other the desired final file system state is achieved. The other big image building block is the config, which collects things such as: application arguments, environment variables, etc.

The layers are stored in the docker registry as blobs, the config values and the order of the layers are stored as artifact also in the docker registry. These artifacts often take the shape of json files, and the Open Container Initiative (OCI) provides a spec of what information shall go into these artifacts.

Image index

This file acts as an index for the image manifests of all supported platforms, a platform is defined by os and architecture. This file is not always provided, some publishers might skip it if the image is only available for a single platform. For multi architecture images this file must be given, and it is the file which docker will read when pulling the image. From this file docker will find the location of the appropriate manifests for the desired platform.

For each image manifest entry the the mediaType, platform info and digest are collected.

Get the image index manifest with:

oras manifest fetch "docker.io/library/nginx:stable-trixie-perl" | jq . > image-index.nginx.json

Get the file descriptor, where the image index digest appears:

oras manifest fetch --descriptor "docker.io/library/nginx:stable-trixie-perl" | jq .
{
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "digest": "sha256:9e81d98c45ae75e3e8e6340c60d092182f50d125705398f2456002dda6f07755",
  "size": 10245
}

This digest, the image index digest, will be shown when pulling the image, will appear in the next section.

Image manifest

The image manifest acts as an index for the digests of the different layers and the config file for the given platform. The layers list is organized in the order in which each layer should be applied. Layer at index 0 is the base layer. Each of the layers will have at least the following fields: mediaType, size and digest. These digests values match those shown when pulling the image.

To pull the image manifest:

oras manifest fetch --platform 'linux/amd64' "docker.io/library/nginx:stable-trixie-perl" | jq . > image-manifest.nginx.linux.amd64.json

This file also contains the reference to the image config digest:

# Get config manifest digest
jq '.config.digest' image-manifest.nginx.linux.amd64.json
# "sha256:8e422626d779878992f367b67a9acdbdde8393573c2566e060ff0cc24957a6cd"
# Get layers digest
jq '.layers[].digest' image-manifest.nginx.linux.amd64.json
# "sha256:ecc510c1e359bdc007b39802570bb7e4dec7d6ebccf357c3201a74299113e05e"
# "sha256:f4616bb1be1c93ca4fa8e2cbb3879ec0aeb379c4cdf1fbebf3d073c64aea8092"
# "sha256:f1a6a629b8241bf06bf6ec3b5eff70b5b115c7c54330ebca802f34ab1647c9b7"
# "sha256:3d774efa34e339b8815f8d39b1a3a311b2ec04772f08b55d93ac8b53b5e1bc70"
# "sha256:cb44c3fe542bf1395586c7a2fefd3c4f5b5eea167e83c7328126350313c1ab8d"
# "sha256:121907e00c1c4b9285a7fee885efa2bebbea4dd519e0139b7255b2bbe7b0d29f"
# "sha256:220a6ac3636821f49ea8f5b599bb0e21509352760644211a522ac7b3abf55b2e"
# "sha256:620d2192fbbcb573b8b59493d5b800ff1a6f16145e1db3acc5d134512026829b"

See how the layers digest from above and the manifest digest from the previous section are seen when pulling the image:

docker pull nginx:stable-trixie-perl
# stable-trixie-perl: Pulling from library/nginx
# ecc510c1e359: Already exists
# f4616bb1be1c: Pull complete
# f1a6a629b824: Pull complete
# 3d774efa34e3: Pull complete
# cb44c3fe542b: Pull complete
# 121907e00c1c: Pull complete
# 220a6ac36368: Pull complete
# 620d2192fbbc: Pull complete
# Digest: sha256:9e81d98c45ae75e3e8e6340c60d092182f50d125705398f2456002dda6f07755
# Status: Downloaded newer image for nginx:stable-trixie-perl
# docker.io/library/nginx:stable-trixie-perl

Note that when I pulled the image, I did not provide the platform value, so docker searched for the image which platform characteristics matched my machine, linux/amd64. Thats why the digest values match. If you are using a different platform, pull the manifest which matches your machine. Also if you read this in the future, nginx might have a newer stable-trixie image and the digests you see can differ from what is shown here.

The image manifest descriptor can be pulled from the registry with:

oras manifest fetch --platform 'linux/amd64' --descriptor "docker.io/library/nginx:stable-trixie-perl"

From this document the image manifest digest can be read:

# Get image manifest digest
oras manifest fetch --platform 'linux/amd64' --descriptor "docker.io/library/nginx:stable-trixie-perl" | jq '.digest'
# "sha256:d159237b851e93bc88e030f53c6576bfff15980d3526db924e2017505c7594cb"

Image config

This file provides the config values, the image history and the layer digests, also ordered by steps.

Pull image config with:

oras manifest fetch-config --platform 'linux/amd64' "docker.io/library/nginx:stable-trixie-perl" | jq . > image-config.linux.amd64.json

Some familiar entries in the config object:

  • Env: a list of environmental variables with its values
  • Entrypoint: default command to run when the container starts
  • Cmd: default arguments for the entrypoint
  • WorkingDir: working directory for the entrypoint

The history section contains an entry for each of the commands inside the Dockerfile. For each of the history entries we can see:

  • created: date time when the layer was created
  • created_by: the command which created the layer
  • empty_layer: not always given, when set to true means that the command which created the entry does not create a layer

The layer references are collected in the rootfs object, under diff_ids, the entry at the first position is the layer to be applied first. For each layer there is a hash value, which differs from the digest in the image manifest.

The DiffIDs is the digest derived from the layer uncompressed tar blob. These digests are not the same as the layer digests found in the manifest, which are derived from the compressed layer and are used for transfer.

See how the values differ:

jq '.rootfs.diff_ids' image-config.linux.amd64.json
# [
#   "sha256:15f1c8eb1ab18eae260c7fb6296434cbe1ff0e1e30fd814ae92a80dd54005e62",
#   "sha256:4c6067168705d3222beb8ea0f2cc724ba610bcfbea433c5005da5b2a3625a0f4",
#   "sha256:8d7e1cf3e75958a6330b8a409a94d6a0d6c83137a02d683b5b73d7465adf503a",
#   "sha256:87ff180a73c88dc3a2ab160a7ffee4de98515be1996854b97d454505789b4b53",
#   "sha256:593fd9fafe92c8a1baaff33b2a50cdfe5cd0f8f4e616a53f17d4eaed95a80596",
#   "sha256:75462831c7c00fb10347cfe80e74b24db03649f18ca97b4748ad57ab73dba504",
#   "sha256:76d0b7c84e9d77ee650d33578b2a89aaa7c38808e798bd727f0f67f7c154db8d",
#   "sha256:d27e5bd61a0501947b644c359fc0ff149a0af14785fae146d051f9d260bd17ad"
# ]

The file image ID is generated by calculating the digest of this file, the image config manifest.

The config descriptor can be pulled with:

oras manifest fetch-config --descriptor --platform 'linux/amd64' "docker.io/library/nginx:stable-trixie-perl" | jq .
{
  "mediaType": "application/vnd.oci.image.config.v1+json",
  "digest": "sha256:8e422626d779878992f367b67a9acdbdde8393573c2566e060ff0cc24957a6cd",
  "size": 13094
}

Obtaining info with docker cli

Docker cli will also provide some of this information.

Images command

The short version of the image config digest can be obtained with:

docker image ls nginx
# IMAGE                      ID             DISK USAGE   CONTENT SIZE   EXTRA
# nginx:stable-trixie-perl   8e422626d779        213MB             0B

The index digest can be obtained with:

docker images --digests nginx
# REPOSITORY   TAG                  DIGEST                                                                    IMAGE ID       CREATED        SIZE
# nginx        stable-trixie-perl   sha256:9e81d98c45ae75e3e8e6340c60d092182f50d125705398f2456002dda6f07755   8e422626d779   13 days ago    213MB

Image inspect command

docker image inspect nginx:stable-trixie-perl

Provides:

  • Id: Image id, this is the image config digest
  • RepoDigests: List of image index digests
  • RooFS.Layers: List of DiffIDs

Image history command

docker image history nginx:stable-trixie-perl

Provides a table with a summary of the image config history. The summary includes: layer creation date, command which created the layer, layer size and layer comment. Notice that the image column only provides an id for the image, the value of the the layers is always missing. This is for historical reasons, before Docker 1.10 each layer was treated as a layer therefore each layer had an image id.