15

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.

Community
  • 1
  • 1
Nathaniel Waisbrot
  • 21,093
  • 6
  • 69
  • 94
  • 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. – Nathaniel Waisbrot Sep 07 '16 at 17:38

8 Answers8

21

For newer versions of Docker, the inspect command provides the correct value:

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.

Nathaniel Waisbrot
  • 21,093
  • 6
  • 69
  • 94
Olli
  • 1,455
  • 12
  • 16
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
jfx
  • 111
  • 1
  • 3
4

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"
        }
  },
  ...
}
Arvin
  • 2,386
  • 25
  • 29
  • 2
    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 : --raw' – Arvin Feb 18 '21 at 20:31
3

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
wsams
  • 2,332
  • 7
  • 34
  • 48
  • Skopeo now also accepts a Go template to define the output format so `jq` is no longer needed. `skopeo inspect --format "{{ .Digest }}" "docker://$image"` – Chris Jun 02 '21 at 16:52
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

Alexandre Thenorio
  • 2,158
  • 2
  • 28
  • 47
  • 1
    Could you include some example code of how you do this with an ECR registry? I'm currently stuck on this myself. – djsumdog Apr 01 '19 at 17:56
  • 1
    This is an old thread, but I just had to do this. Basically, you need to use the 'batch_get_image' service: https://docs.aws.amazon.com/cli/latest/reference/ecr/batch-get-image.html 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.: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ecr.html#ECR.Client.batch_get_image – buddyroo30 Mar 29 '20 at 18:11
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; }

}
sbeskur
  • 2,120
  • 22
  • 21
0

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
-3

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.

mcw
  • 3,346
  • 29
  • 33