Docker image digest using v2 registry API

Ever need to get the image digest SHA from an image stored in a docker registry.  Easy to do if the image has been pulled, just run docker image ls <image> –digests.


docker image ls bitnami/wordpress --digests
REPOSITORY          TAG                   DIGEST                                                                    IMAGE ID            CREATED             SIZE
bitnami/wordpress   5.4.1-debian-10-r35   sha256:11db66166c3d16c8251134e538b794ec08dfbe5f11bcc8066c6fe50e3282d6ed   7e0e802dc089        13 days ago         593MB

But doing it from the docker registry v2 API is a bit different.  Most solutions have you hit the https://registry-1.docker.io/v2/${org}/${img}/manifests/$tag and look for the config.digest JSON field.  The config.digest is the SHA for the manifest not the image itself.  The docker image digest SHA is there but we just looking in the wrong place. We need to look at the http headers for the when we hit that same API endpoint. Docker-Content-Digest:

Here is a full example that includes interacting with Helm Charts. It finds the docker image digest for each image the chart uses. The image digest is found by using the two curl commands in the script (auth and manifest).

 #!/bin/bash helmrepouser="" helmrepopass="" helmrepo="bitnami" helmrepourl="https://charts.bitnami.com/bitnami" dockeruser="DOCKERHUB USERID" dockerpass='DOCKERHUB PASSWORD' chartorg="bitnami" chartname="wordpress" chartversion="9.3.8" helmexe="helm" if [ "$dockeruser" == "" ]; then   dockeruser=$helmrepouser fi if [ "$dockerpass" == "" ]; then   dockerpass=$helmrepopass fi if [ "$helmrepouser" != "" ]; then   helmlogin="--username $helmrepouser" fi if [ "$helmrepouser" != "" ]; then   helmlogin="$helmlogin --password $helmrepopass" fi if [ "$chartorg" == "" ]; then   chartorg=`echo $chartname | cut -d / -f 1`   chartname=`echo $chartname | cut -d / -f 2` fi # Add Helm Repo $helmexe repo add $helmlogin $helmrepo $helmrepourl 2>&1 1>/dev/null # Get latest Helm Charts $helmexe repo update 2>&1 1>/dev/null # Fetch the Helm Chart as .tgz $helmexe fetch $chartorg/$chartname --version $chartversion 2>&1 1>/dev/null # Calc digest of the Helm Chart digest=`sha256sum $chartname*.tgz | cut -f1 -d " "` # Print out the starting JSON echo "{" echo "\"digest\": \"$digest\"," echo "\"images\": [" tar -xf *.tgz IFS=$'\n' # Find the image: fields in the expanded Helm Chart and loop over each image line let cnt=0 for line in $($helmexe template $chartname | grep "image:" | cut -f 2- -d ":" | tr -d '"' | tr -d " ") do if [ "$cnt" -gt "0" ]; then   echo "," fi let cnt=$cnt+1 # find the number of slashes since registry name and org are not required for images in docker.io slashes=$(echo $line | awk -F"/" '{print NF-1}') if [ "$slashes" == "2" ]; then    reg=$(echo $line | cut -d / -f 1)    org=$(echo $line | cut -d / -f 2)    img=$(echo $line | cut -d / -f 3- | cut -d ":" -f 1)    tag=$(echo $line | cut -d / -f 3- | cut -d ":" -f 2) elif [ "$slashes" == "1" ]; then    reg="docker.io"    org=$(echo $line | cut -d / -f 1)    img=$(echo $line | cut -d / -f 2- | cut -d ":" -f 1)    tag=$(echo $line | cut -d / -f 2- | cut -d ":" -f 2) else    reg="docker.io"    org="library"    img=$(echo $line | cut -d ":" -f 1)    tag=$(echo $line | cut -d ":" -f 2) fi # docker.io v2 API endpoint lives in a different location than the registry url if [ "$reg" == "docker.io" ]; then    apidomain="registry-1.docker.io" else    apidomain=$reg fi # Find the docker authentication relm authdomsvc=`curl --head "https://${apidomain}/v2/" 2>&1 | grep realm | cut -f2- -d "=" | tr "," "?" | tr -d '"' | tr -d "\r"` # Set the scope to look for our image authscope="repository:${org}/${img}:pull" # Login to docker v2 API endpoint and save token TOKEN=$(curl -s -X GET -u ${dockeruser}:${dockerpass} "${authdomsvc}&scope=${authscope}&offline_token=1&client_id=shell" | jq -r '.token') # Grab the image digest from the Docker-Content-Digest: that is in the http header # Use "Accept: application/vnd.docker.distribution.manifest.v2+json" in order to get the version 2 schema digest=$(curl --head -s -H "Authorization: Bearer ${TOKEN}" -H "Accept: application/vnd.docker.distribution.manifest.v2+json" https://${apidomain}/v2/${org}/${img}/manifests/$tag | grep "Docker-Content-Digest:" | cut -f 2 -d " " | tr -d '"' | tr -d "\r") # Print out the details for each image cat <<- EOF         {          "registry": "$reg",          "organization": "$org",          "image_name": "$img",          "image_tag": "$tag",          "image_digest": "$digest"         } EOF done # Print closing JSON echo "]" echo "}" # Cleanup rm $chartname*.tgz 

Results of from the script:

 { "digest": "8e1774518cba1f58bde35342e0f243a4e2d416bad78b4d5e001c5eedb0e86b58", "images": [         {          "registry": "docker.io",          "organization": "bitnami",          "image_name": "wordpress",          "image_tag": "5.4.1-debian-10-r35",          "image_digest": "sha256:11db66166c3d16c8251134e538b794ec08dfbe5f11bcc8066c6fe50e3282d6ed"         },         {          "registry": "docker.io",          "organization": "bitnami",          "image_name": "mariadb",          "image_tag": "10.3.23-debian-10-r21",          "image_digest": "sha256:93746d7b2a1ba8edbb892a3659d2118e792de9211de25957504cbd7b9d8d8fc0"         },         {          "registry": "docker.io",          "organization": "bitnami",          "image_name": "wordpress",          "image_tag": "5.4.1-debian-10-r35",          "image_digest": "sha256:11db66166c3d16c8251134e538b794ec08dfbe5f11bcc8066c6fe50e3282d6ed"         },         {          "registry": "docker.io",          "organization": "dduportal",          "image_name": "bats",          "image_tag": "0.4.0",          "image_digest": "sha256:b2d533b27109f7c9ea1e270e23f212c47906346f9cffaa4da6da48ed9d8031da"         },         {          "registry": "docker.io",          "organization": "bitnami",          "image_name": "mariadb",          "image_tag": "10.3.23-debian-10-r21",          "image_digest": "sha256:93746d7b2a1ba8edbb892a3659d2118e792de9211de25957504cbd7b9d8d8fc0"         }       ] }` 

Leave a Reply