For my first blog post we’re going to take a look at image digests, with a focus on how they’re listed on DockerHub, and how we can use them to identify different tags that point to the same image.
I’m currently writing a small application that will replicate this
list. It will let you embed the list by including a
<script>
block in your Markdown file, similar to embedding Gists.
Here we can see two tags that refer to the same image. The image also
targets two platforms, linux/amd64
and linux/arm64/v8
.
Using the Docker Registry V2 API, how do we get this information? We will want to use these endpoints:
We’re using Heroku’s docker-registry-client. It hasn’t been updated in a couple of years, but it exposes those two endpoints and doesn’t have the complexity of some other libraries.
Lets start by creating a Client and retrieving the list of tags.
registryClient, err = client.New(url, username, password)
if err != nil {
log.Fatal("Could not create the registry client", err)
}
tags, err := registryClient.Tags("library/amazoncorretto")
Now we have a list of all the tags for the repository.
We can also get the digest of the manifest using a reference.
In this case a tag.
Using client.ManifestDigest("library/amazoncorretto", "11")
we get:
sha256:38455efd51e100ce0cdcc481036749395363671ed71406b21ad78b69b22b2c37
This doesn’t match with the what we’ve seen on DockerHub. Lets use cURL to replicate the request and see what we get back. Stack Overflow has a great walkthrough on how to make the requests.
curl -vH "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/library/amazoncorretto/manifests/11
> Host: registry-1.docker.io
> User-Agent: curl/7.47.0
> Accept: */*
> Authorization: Bearer <redacted>
>
< HTTP/1.1 200 OK
< Content-Length: 5560
< Content-Type: application/vnd.docker.distribution.manifest.v1+prettyjws
< Docker-Content-Digest: sha256:38455efd51e100ce0cdcc481036749395363671ed71406b21ad78b69b22b2c37
< Docker-Distribution-Api-Version: registry/2.0
< Etag: "sha256:38455efd51e100ce0cdcc481036749395363671ed71406b21ad78b69b22b2c37"
...
Great, we can see that the same, incorrect digest is returned in the Docker-Content-Digest
header.
The Content-Type header is interesting. We’re getting a v1
manifest back.
Docker has documented the different Media Types that are available.
Lets start by specifying Accept: application/vnd.docker.distribution.manifest.v2+json
.
curl -vH "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.docker.distribution.manifest.v2+json" https://registry-1.docker.io/v2/library/amazoncorretto/manifests/11
> Host: registry-1.docker.io
> User-Agent: curl/7.47.0
> Accept: application/vnd.docker.distribution.manifest.v2+json
> Authorization: Bearer <redacted>
>
< HTTP/1.1 200 OK
< Content-Type: application/vnd.docker.distribution.manifest.v2+json
< Docker-Content-Digest: sha256:717ba3e944ebbbbc0f1e0a37a1099cd2c6154549398ac96f9a1bc5c47ee18d7a
< Docker-Distribution-Api-Version: registry/2.0
< Etag: "sha256:717ba3e944ebbbbc0f1e0a37a1099cd2c6154549398ac96f9a1bc5c47ee18d7a"
Perfect. Manifest V2 returns the digest we see on DockerHub!
We can also pipe the raw (ugly) JSON directly into sha256sum
and get the same digest.
Earlier we saw that a tag could refer to multiple images targetting a single platform.
This was exactly what application/vnd.docker.distribution.manifest.list.v2+json
was
made for. This gives us a
Manifest List.
The list references each platform-specific image by the digest.
{
"manifests": [
{
"digest": "sha256:717ba3e944ebbbbc0f1e0a37a1099cd2c6154549398ac96f9a1bc5c47ee18d7a",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "amd64",
"os": "linux"
},
"size": 742
},
{
"digest": "sha256:0b480d28d84b4492c9f6eace74094a8295035572f4202f15b10ce1c144d67a13",
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
},
"size": 742
}
],
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"schemaVersion": 2
}
This can be used when you need a different, or multiple, platforms.
Note that this means Manifest V2 defaults to linux/amd64
. Perhaps
because this is the first in the Manifest List.
The Heroku library supported getting V2 manifests. However, the
client.ManifestDigest(repo, ref)
function, which uses a HEAD method
request, doesn’t specify the Accept
header.
I’ve forked the repository and
added a ManifestDigestV2
function.
docker image
IDs are different?Running docker images
we get the name of the image, the tag and an IMAGE ID
.
REPOSITORY TAG IMAGE ID CREATED SIZE
amazoncorretto 11 e5a017eba449 10 days ago 445MB
Previously we’ve been looking at the digest for the manifest; the document that contains information on how to get the image.
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 2990,
"digest": "sha256:e5a017eba449ce2eb19d142ad9d92b997006af8f761162d189ed384b4e6f4a63"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 61996576,
"digest": "sha256:62350c28fdb7b7cbd0e199dd893555ed129ed85da482d882b1eeb574988ea7d6"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 146517618,
"digest": "sha256:383b03c8da6a748602d2579040e3d1b907c0c4c0b6d59b586e8e41c342acace6"
}
]
}
Here we can see the IMAGE ID
is the digest for the image config.