Why is a parent container image SHA not listed in the layers of the child?

888 views Asked by At

Consider a container image, lets call it BaseContainerImage. I build this container image based off a container image on docker hub (the .Net Core 3.1 runtime if it matters). By "based off" I mean that the FROM references that docker hub image.

When I build it, it gets a SHA of: sha256:9ec7b7481feee3eb141f7321be1df353b1ab8b6bdf0d871717b6f7e90e6ed0f6. (Found by checking config.digest of the container image's manifest.)

Then I go make a new container image, lets call it ApplicationContainerImage. It is based off the BaseContainerImage, using a tag that refers to the above SHA. After I build it, I look at the container image's manifest.

I expected the layers section to contain the SHA of the "parent" container image. But it does not.

When I compare the layers of both, all the layers of the BaseContainerImage are in the ApplicationContainerImage. So I know that the FROM working. But I just don't understand why the SHA of the BaseContainerImage is left out of the layers of the ApplicationContainerImage.

Why is the SHA of the BaseContainerImage not listed in layers of the ApplicationContainerImage?

Later Notes:
When I went and downloaded the BaseContainerImage from a remote repository, it tells me (as part of the PULL command ouput that the Digest is Digest: sha256:a1dd2dfdfc51e7abba1d2db319ba457e7b72f7258f5cefca0ee6ec6845f564b6 Which clearly does not match the above digest. But when I run docker manifest inspect the the exact same image, the config.digest is sha256:9ec7b7481feee3eb141f7321be1df353b1ab8b6bdf0d871717b6f7e90e6ed0f6, matching what I got earlier.

Why are there two different SHA values? Is one just for the pull action somehow?

1

There are 1 answers

3
BMitch On BEST ANSWER

You're mixing up digests for different objects. The image in a registry consist of:

  • Manifest: this is the top level, has it's own digest, but is commonly referred to by tag
  • Config: the manifest points to this, and it includes the default settings you see when you inspect a docker image
  • Layers: each layer has it's own digest, these are typically tar+gzip on the registry, and tar (uncompressed) when pulled locally

The manifest digest is the most commonly used digest, it's used to pin an image for pulling. Note that you can have a manifest list that points to multiple platform specific manifests, and each of those have their own digest.

The config digest shouldn't be compared to anything locally, it's needed to pull the config blob from the registry, but it isn't directly associated with layer digests and isn't the manifest digest.

The layer digests are sometimes confused because they change when you go from compressed on the registry to uncompressed locally.

What is a digest? It's just the sha256sum on the content. That file is pushed to the registry as a blob or manifest. Because of how the manifest includes digests of the other files, you end up with a directed acyclic graph (DAG).

To see the layer reuse, look at the actual layers within the image manifest. Or you can look at the layers section of the config blob (these digests will be different because the layer digests in the config are on the uncompressed layer).


Here's an example of layer reuse looking at two images on docker hub:

$ regctl image manifest alpine:3.13
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 1472,
    "digest": "sha256:6dbb9cc54074106d46d4ccb330f2a40a682d49dda5f4844962b7dce9fe44aaec"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 2811969,
      "digest": "sha256:540db60ca9383eac9e418f78490994d0af424aab7bf6d0e47ac8ed4e2e9bcbba"
    }
  ]
}

$ regctl image manifest redis:alpine
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 6390,
    "digest": "sha256:1690b63e207f6651429bebd716ace700be29d0110a0cfefff5038bb2a7fb6fc7"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 2811969,
      "digest": "sha256:540db60ca9383eac9e418f78490994d0af424aab7bf6d0e47ac8ed4e2e9bcbba"
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 1258,
      "digest": "sha256:29712d301e8c43bcd4a36da8a8297d5ff7f68c3d4c3f7113244ff03675fa5e9c"
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 384200,
      "digest": "sha256:8173c12df40f1578a7b2dfbbc0034a4fbc8ec7c870fd32b9236c2e5e1936616a"
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 7692532,
      "digest": "sha256:8cc52074f78e0a2fd174bdd470029cf287b7366bf1b8d3c1f92e2aa8789b92ae"
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 135,
      "digest": "sha256:aa7854465cce07929842cb49fc92f659de8a559cf521fc7ea8e1b781606b85cd"
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 412,
      "digest": "sha256:6ab1d05b49730290d3c287ccd34640610423d198e84552a4c2a4e98a46680cfd"
    }
  ]
}

From that you can see the config blobs are completely different (as expected, these aren't the same image), but the layer from the alpine image is the same as the first layer of the redis:alpine image.

The regctl tool shown here is available from github. Disclaimer, I'm the author.