Reputation: 8237
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
sha256:055936d3920576da37aa9bc460d70c5f212028bda1c08c0879aedf03d7a66ea1
(IMAGE ID) andsha256:769fddc7cc2f0a1c35abb2f91432e8beecf83916c421420e6a6da9f8975464b6
(DIGEST)
are not the same value. But why? What's the purpose of having two different sha256
hashes of the same image. How are they calculated, respectively?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
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:ad5c126b5cf501a8caef751a243bb717ec204ab1aa56dc41dc11be089fafcb4f
could be used as sha reference to be sure it's always the correct image for all platforms built.
Upvotes: 1
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:
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
Reputation: 8237
Thanks for michalk's comment. The short answer is:
Upvotes: 86