xji
xji

Reputation: 8237

What's the difference between a Docker image's Image ID and its Digest?

This has been surprisingly confusing for me. I thought Docker's Image ID is its SHA256 hash. However, apparently the result from docker image ls --digests (listed under the column header DIGEST) is different from the IMAGE ID of that image.

For example

$ docker image ls --digests alpine
REPOSITORY  TAG     DIGEST                                                                   IMAGE ID      CREATED      SIZE
alpine      latest  sha256:769fddc7cc2f0a1c35abb2f91432e8beecf83916c421420e6a6da9f8975464b6  055936d39205  2 weeks ago  5.53MB

while

$ docker image ls --no-trunc
REPOSITORY  TAG     IMAGE ID                                                                 CREATED      SIZE
...                                                                                                       
alpine      latest  sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1  2 weeks ago  5.53MB

Clearly

I was confused by this when reading the book Docker Deep Dive, and I haven't been able to find a clear answer either in the book or online.

Upvotes: 124

Views: 39923

Answers (3)

chestack
chestack

Reputation: 116

Unrelated to this question, but just a bit of additional information about digest. Besides the mentioned ID and Digest, the image index image index in OCI spec might also be worth noting for multi-platform images.

It's very convenient to use image index to specify a base image for a multi-platform build. Such as the following Dockerfile

ARG GOLANG_IMAGE=docker.io/library/golang:1.23.2@sha256:ad5c126b5cf501a8caef751a243bb717ec204ab1aa56dc41dc11be089fafcb4f

FROM --platform=${BUILDPLATFORM} ${GOLANG_IMAGE} AS builder

sha256:ad5c126b5cf501a8caef751a243bb717ec204ab1aa56dc41dc11be089fafcb4fcould be used as sha reference to be sure it's always the correct image for all platforms built.

enter image description here

Upvotes: 1

BMitch
BMitch

Reputation: 263469

There are a lot of digests in a container image. To take a multi-platform example with a tag, here's a diagram of what that would look like:

OCI Multi-platform Image example

The tag is effectively a symbolic link to a manifest. Excluding the tag, every object in the picture is content addressable, referenced by a digest. Each of the blue objects are manifests. The Index contains a list of manifests. And the Image manifests each have an image config and a list of layers. The green objects are each blobs in the container registry, which a registry may treat as an opaque stream of bytes.

Lets walk one of these as an example with the real data. Here's the Index (or, in Docker's media types, the manifest list):

$ regctl manifest get alpine --format 'body' | sha256sum
beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d  -

$ regctl manifest get alpine --format '{{jsonPretty .}}'
{
  "manifests": [
    {
      "digest": "sha256:33735bd63cf84d7e388d9f6d297d348c523c044410f553bd878c6d7829612735",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "size": 528
    },
    {
      "digest": "sha256:9cee2b382fe2412cd77d5d437d15a93da8de373813621f2e4d406e3df0cf0e7c",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm64",
        "os": "linux",
        "variant": "v8"
      },
      "size": 528
    },
// ... additional entries removed for brevity
  ],
  "mediaType": "application\/vnd.docker.distribution.manifest.list.v2+json",
  "schemaVersion": 2
}

And then pulling the amd64 manifest from that list (runtimes do this for you automatically based on your local platform):

$ regctl manifest get alpine@sha256:33735bd63cf84d7e388d9f6d297d348c523c044410f553bd878c6d7829612735 --format '{{jsonPretty .}}'
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 1471,
    "digest": "sha256:91ef0af61f39ece4d6710e465df5ed6ca12112358344fd51ae6a3b886634148b"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 3623807,
      "digest": "sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170"
    }
  ]
}

Now to inspect the image in docker, we'll see the config digest is the image ID, while the repo digest that you would use to pin the image is the top level index digest:

$ docker inspect alpine
[
    {
        "Id": "sha256:91ef0af61f39ece4d6710e465df5ed6ca12112358344fd51ae6a3b886634148b",
        "RepoTags": [
            "alpine:latest"
        ],
        "RepoDigests": [
            "alpine@sha256:beefdbd8a1da6d2915566fde36db9db0b524eb737fc57cd1367effd16dc0d06d"
        ],
...

More details about the manifests and image config can be found in the OCI image specification.

Upvotes: 5

xji
xji

Reputation: 8237

Thanks for michalk's comment. The short answer is:

  • The "digest" is a hash of the manifest, introduced in Docker registry v2.
  • The image ID is a hash of the local image JSON configuration.

Upvotes: 86

Related Questions