Can I get an image digest without downloading the image?
Asked Answered
M

11

30

Similar to the question "What´s the sha256 code of a docker image?", I would like to find the digest of a Docker image. I can see the digest when I download an image:

$ docker pull waisbrot/wait:latest                                                                                                  
latest: Pulling from waisbrot/wait
Digest: sha256:6f2185daa4ab1711181c30d03f565508e8e978ebd0f263030e7de98deee5f330
Status: Image is up to date for waisbrot/wait:latest
$

Another question, What is the Docker registry v2 API endpoint to get the digest for an image has an answer suggesting the Docker-Content-Digest header.

I can see that there is a Docker-Content-Digest header when I fetch the manifest for the image:

$ curl 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:waisbrot/wait:pull' -H "Authorization: Basic ${username_password_base64}"

# store the resulting token in DT

$ curl -v https://registry-1.docker.io/v2/waisbrot/wait/manifests/latest -H "Authorization: Bearer $DT" -XHEAD
*   Trying 52.7.141.30...
* Connected to registry-1.docker.io (52.7.141.30) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate: *.docker.io
* Server certificate: RapidSSL SHA256 CA - G3
* Server certificate: GeoTrust Global CA
> GET /v2/waisbrot/wait/manifests/latest HTTP/1.1
> Host: registry-1.docker.io
> User-Agent: curl/7.43.0
> Accept: */*
> Authorization: Bearer LtVRw-etc-etc-etc
>
< HTTP/1.1 200 OK
< Content-Length: 4974
< Content-Type: application/vnd.docker.distribution.manifest.v1+prettyjws
< Docker-Content-Digest: sha256:128c6e3534b842a2eec139999b8ce8aa9a2af9907e2b9269550809d18cd832a3
< Docker-Distribution-Api-Version: registry/2.0
< Etag: "sha256:128c6e3534b842a2eec139999b8ce8aa9a2af9907e2b9269550809d18cd832a3"
< Date: Wed, 07 Sep 2016 16:37:15 GMT
< Strict-Transport-Security: max-age=31536000

However, this header isn't the same. The pull command got me 6f21 and the header shows 128c. Further, the pull command doesn't work for that digest:

$ docker pull waisbrot/wait@sha256:128c6e3534b842a2eec139999b8ce8aa9a2af9907e2b9269550809d18cd832a3                               
Error response from daemon: manifest unknown: manifest unknown

whereas things work as I want when I have the correct digest:

$ docker pull waisbrot/wait@sha256:6f2185daa4ab1711181c30d03f565508e8e978ebd0f263030e7de98deee5f330                                 12:46  waisbrot@influenza
sha256:6f2185daa4ab1711181c30d03f565508e8e978ebd0f263030e7de98deee5f330: Pulling from waisbrot/wait
Digest: sha256:6f2185daa4ab1711181c30d03f565508e8e978ebd0f263030e7de98deee5f330
Status: Image is up to date for waisbrot/wait@sha256:6f2185daa4ab1711181c30d03f565508e8e978ebd0f263030e7de98deee5f330

What I'm looking for is a way to translate the latest tag (which changes all the time) into a fixed digest that I can reliably pull. But I don't want to actually pull it down in order to do this translation.

Mcloughlin answered 7/9, 2016 at 16:48 Comment(1)
Whoah, I didn't expect a solution so easily! Yes, that appears to be the answer: when I add that header, the digest header I get back is the one that I'm looking for.Mcloughlin
U
27

edit 2022-10-04:

# INPUT
REPO=waisbrot/wait
user=my-user
password=my-password

# Get TOKEN
username_password_base64=$(echo -n $user:$password | base64)

TOKEN=$(curl -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
                -H "Authorization: Basic ${username_password_base64}" \
                'https://auth.docker.io/token?service=registry.docker.io&scope=repository:waisbrot/wait:pull' \
        | jq -r .token)

# GET Digest from v2 API
curl -s -D - -H "Authorization: Bearer $TOKEN" \
     https://registry-1.docker.io/v2/waisbrot/wait/manifests/latest 2>&1 \
  | grep docker-content-digest \
  | cut -d' ' -f2

original answer:

For newer versions of Docker, the inspect command provides the correct value (requires the image to have been pulled as Jan Hudec has pointed out in the comments):

docker inspect --format='{{index .RepoDigests 0}}' waisbrot/wait

For older versions, fetch the value from the repository following this example with the main Docker repo:

curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
     -H "Authorization: Basic ${username_password_base64}" \
     'https://auth.docker.io/token?service=registry.docker.io&scope=repository:waisbrot/wait:pull' 

Naive attempts to fetch that value fail because the default content-type being selected by the server is application/vnd.docker.distribution.manifest.v1+prettyjws (a v1 manifest) and you need to v2 manifest. Therefore, you need to set the Accept header to application/vnd.docker.distribution.manifest.v2+json.

Urfa answered 7/9, 2016 at 17:45 Comment(2)
For a more complete answer (including how to generate the ${username_password_base64} automatically), see https://mcmap.net/q/472647/-how-to-determine-the-docker-image-id-for-a-tag-via-docker-hub-apiMireillemireles
The first does NOT work without downloading the image.Matrimonial
H
23

This is how you do it today using a V2 manifest.

docker manifest inspect <REMOTE IMAGE>:<TAG> -v

Your output is JSON:

{
  ...
  "Descriptor": {
        "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
        "digest": "sha256:d13e102941a9f7bd417440f62f9cb29de35f6acb13a26cbf6a34f4c7340f0b63",
        "size": 3255,
        "platform": {
            "architecture": "amd64",
            "os": "linux"
        }
  },
  ...
}
Hunch answered 18/2, 2021 at 20:24 Comment(6)
Worth mentioning that if you do something like this, it works fine for list manifest but not for FAT manifests which is what you get when you build with just one platform. So avoid this: 'docker buildx imagetools inspect <REMOTE IMAGE>:<TAG> --raw'Hunch
To get just the digest value, you can use jq: docker manifest inspect <REMOTE IMAGE>:<TAG> -v | jq -r '.Descriptor.digest'Ressler
@Ressler I had to use [0] to get the first item of the array: docker manifest inspect <IMAGE>:<TAG> -v | jq -r '.[0].Descriptor.digest'Inequity
I had to use [0] for ghcr.io but not for docker.io.Prisca
The -v part is very important. Also note: if you are using a localhost:port registry, you want to pass --insecure, so that it works over http docker manifest inspect ${target_image} --insecure -v | jq -r ".Descriptor.digest"Diaphoretic
podman manifest inspect registry.some.site/tools/some_image:some_tag also worksLamberto
M
6

I encountered a task recently that required viewing the sha256 digest without necessarily pulling the image. The tool skopeo makes the registry API calls so you don't need to pull the image.

For example,

$ skopeo inspect --creds "username:password" docker://waisbrot/wait:latest

You could then pipe this to jq if you want to get just the digest value.

$ skopeo inspect --creds "username:password" \
  docker://waisbrot/wait:latest | jq -r '.Digest'
sha256:6f2185daa4ab1711181c30d03f565508e8e978ebd0f263030e7de98deee5f330
Murdocca answered 16/7, 2020 at 23:22 Comment(1)
Skopeo now also accepts a Go template to define the output format so jq is no longer needed. skopeo inspect --format "{{ .Digest }}" "docker://$image"Oni
P
5

With 2 http requests, you can get it. The first one to get an authentication token, and the second to get the image digest list by architecture and variant:

token=$(curl --silent "https://auth.docker.io/token?scope=repository:$image:pull&service=registry.docker.io"  | jq -r '.token')

curl -s --header "Accept: application/vnd.docker.distribution.manifest.list.v2+json" --header "Authorization: Bearer ${token}" "https://registry-1.docker.io/v2/$image/manifests/$tag" | jq -r '.manifests|.[]| "\(.digest) \(.platform.architecture) \(.platform.variant)"'

Example with:

image=library/nginx
tag=stable-alpine
sha256:8853c7e938c2aa5d9d7439e698f0e700f058df8414a83134a09fcbb68bb0707a amd64 null
sha256:dbcd23f95b94018fe72bfdb356e40f4ae8b95063883f3456fedaed1c02204ed4 arm v6
sha256:d3670edcd50bb07cae303767426adf9bc7ba0219736148d30e6f30dd4e08695c arm v7
sha256:0bcd76faa141e4fa37e875834b3994261e0cfc94b7233ac84896381315b845ca arm64 v8
sha256:da8e62ddb3fab89ff4fa0271dbe230f849ab53402a71338503952437dcda1026 386 null
sha256:269bf99e100294b6b75fbdecf7b4ddbef8b29ea0a953e2e904452a50dbc923ab ppc64le null
sha256:103da50956034c157abeffbc869e2e38a4fabbf913bed8ae6ae7c59e646b28a1 s390x null
Preparedness answered 11/7, 2020 at 13:16 Comment(1)
This doesn't work for me, the digest is not available in the API responseGaylagayle
S
4

As mentioned in other answers, this digest did not match because you attempted to curl without an Accept header, and so the registry triggered a fallback to an older v1 image manifest:

< Content-Type: application/vnd.docker.distribution.manifest.v1+prettyjws
< Docker-Content-Digest: sha256:128c6e3534b842a2eec139999b8ce8aa9a2af9907e2b9269550809d18cd832a3

You can query Hub with curl using a script like:

#!/bin/sh

ref="${1:-library/ubuntu:latest}"
sha="${ref#*@}"
if [ "$sha" = "$ref" ]; then
  sha=""
fi
wosha="${ref%%@*}"
repo="${wosha%:*}"
tag="${wosha##*:}"
if [ "$tag" = "$wosha" ]; then
  tag="latest"
fi
cto="application/vnd.oci.image.index.v1+json"
ctol="application/vnd.oci.image.manifest.v1+json"
ctd="application/vnd.docker.distribution.manifest.v2+json"
ctdl="application/vnd.docker.distribution.manifest.list.v2+json"
token=$(curl -sL "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" \
        | jq -r '.token')
curl -H "Accept: ${cto}" -H "Accept: ${ctol}" -H "Accept: ${ctd}" -H "Accept: ${ctdl}" \
     -H "Authorization: Bearer $token" \
     -I -sL "https://registry-1.docker.io/v2/${repo}/manifests/${sha:-$tag}" 

However, that's limited since it's specific to Hub, and you still need to parse the headers. From the skopeo output, it's still pulling the entire manifest rather than a HEAD request, which will count against Hub rate limits.

Instead my two preferred tools for this are go-containerregistry/crane and regclient/regctl (I'm the author of the latter). Each has a digest command which automatically handles auth to different registries, includes the needed Accept headers, and parses the output to just the digest which is useful for scripting:

$ regctl image digest busybox
sha256:3b3128d9df6bbbcc92e2358e596c9fbd722a437a62bafbc51607970e9e3b8869

$ crane digest busybox
sha256:3b3128d9df6bbbcc92e2358e596c9fbd722a437a62bafbc51607970e9e3b8869

To avoid installing other tools, docker now has docker buildx imagetools inspect, but similar to skopeo, this is pulling the entire manifest rather than a HEAD request:

$ docker buildx imagetools inspect busybox
Name:      docker.io/library/busybox:latest
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest:    sha256:3b3128d9df6bbbcc92e2358e596c9fbd722a437a62bafbc51607970e9e3b8869
...

$ docker buildx imagetools inspect busybox --format '{{json .}}' | jq -r .manifest.digest
sha256:3b3128d9df6bbbcc92e2358e596c9fbd722a437a62bafbc51607970e9e3b8869
Shackle answered 11/12, 2022 at 21:51 Comment(0)
C
1

I realise this issue is answered however either I am missing something or the current version of AWS ECR registry service does not work as expected.

When trying to get the digest from AWS ECR using either HEAD and also trying to switch the content-type does not return a digest value that I can use to pull an image using the registry Api.

To get this digest you have to get the manifest for the tag you are interested in and calculate the sha256 of the response Json as is, including the formatting, without the signature section

Catchings answered 4/10, 2016 at 21:57 Comment(3)
Could you include some example code of how you do this with an ECR registry? I'm currently stuck on this myself.Ragman
This is an old thread, but I just had to do this. Basically, you need to use the 'batch_get_image' service: docs.aws.amazon.com/cli/latest/reference/ecr/… Then you parse the imageManifest part of the response, and extract imageManifest.config.digest for each image. This is also available in boto3, e.g.: boto3.amazonaws.com/v1/documentation/api/latest/reference/…Renelle
fwiw it seems to work fine for me as long as I use HEAD and an appropriate Accept header to force a v2 manifestDemurral
R
1

I struggled with this also. Here is a C# (dotnet core 5.0) implementation if anyone is intersted:

    /**
       TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:waisbrot/wait:pull" | jq -r .token)
       curl -s -D - -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.docker.distribution.manifest.v2+json" https://index.docker.io/v2/waisbrot/wait/manifests/latest
    */
    private string GetRemoteImageDigest(string image, string tag) {
        using HttpClient client = new ();   
        var url = string.Format($"https://auth.docker.io/token?service=registry.docker.io&scope=repository:{image}:pull");
        //var response = client.Send(new HttpRequestMessage(HttpMethod.Get, url));
        var result = client.GetStringAsync(url);            
        var drt = JsonSerializer.Deserialize<DockerRegistryToken>(result.Result);
    
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", drt.Token);
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.docker.distribution.manifest.v2+json"));
        
        var response = client.GetAsync(string.Format($"https://index.docker.io/v2/{image}/manifests/{tag}"));                        
        var headers =  response.Result.Headers;
        IEnumerable<string> values;
        headers.TryGetValues("Docker-Content-Digest", out values);
        return values.FirstOrDefault();
    }

DockerRegistryToken is defined as:

public class DockerRegistryToken{
    [JsonPropertyName("token")]
    public string Token { get; set; }

    /// always null
    [JsonPropertyName("access_token")]
    public string AccessToken  {get; set; }

    [JsonPropertyName("expires_in")]
    public int ExpiresInSeconds { get; set; }

    [JsonPropertyName("issued_at")]
    public DateTime IssuedAt { get; set; }

}
Roomer answered 27/4, 2021 at 23:32 Comment(0)
J
1

example for reg which requires redirect (curl follow 302)

REGISTRY_ADDRESS='registry.access.redhat.com'
image='ubi8/openjdk-17-runtime'

curl --silent -L \
    --header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
    "https://$REGISTRY_ADDRESS/v2/$image/manifests/latest" |
    jq -r '.config.digest'
Judenberg answered 8/2, 2023 at 8:53 Comment(0)
A
0

An alternative answer only for Amazon Elastic Container Registry (ECR) using describe-images API.

aws ecr describe-images --repository-name REPOSITORY_NAME --image-ids imageTag=TAG --query imageDetails[0].imageDigest --output text
Absorptivity answered 4/8, 2023 at 6:51 Comment(0)
C
-1

Following up on ByteFlinger's suggestion, which did not have an example, I tried this, and this is how to calculate it:

$ docker-ls tag -registry https://myregistry.net:5000 
spicysomtam/zookeeper:latest
requesting manifest . done
repository: spicysomtam/zookeeper
tagName: latest
digest: sha256:bd5dd80253171e4dffccbea7c639c90a63d5424aa2d7fe655aea766405c83036

$ curl -ns -H "Accept: 
application/vnd.docker.distribution.manifest.v2+json" -X GET  
https://myregistry.net:5000/v2/spicysomtam/zookeeper/manifests/latest|sha256sum
bd5dd80253171e4dffccbea7c639c90a63d5424aa2d7fe655aea766405c83036  -

$ docker images --digests |grep zookeeper
myregistry.net:5000/spicysomtam/zookeeper           latest                                     sha256:bd5dd80253171e4dffccbea7c639c90a63d5424aa2d7fe655aea766405c83036   a983e71ca22d        29 hours ago        584MB
Colligan answered 13/10, 2017 at 15:44 Comment(2)
I would make this much more clear on the steps taken to fix rather than showing a logTelepathist
What is the docker-ls? That is not part of standard docker distribution.Matrimonial
W
-5

You can get this using docker inspect:

docker inspect --format='{{index .RepoDigests 0}}' ${IMAGE_NAME}

Docs: https://docs.docker.com/engine/reference/commandline/inspect/

This has been in place since at least v1.9.

Webster answered 28/1, 2019 at 22:28 Comment(2)
How does that find the image digest WITHOUT downloading it??Matrimonial
Yeah, you'll need to pull the image before running docker inspect.Murdocca

© 2022 - 2024 — McMap. All rights reserved.