Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to view Docker image layers on Docker Hub?

I know that I can use this command $ docker images --tree docker history to view the layers of a Docker image, but how do I do that for images on Docker Hub without pulling it? This is so that I know what is on an image before I download it.

E.g., for the Tomcat repo, https://registry.hub.docker.com/_/tomcat/, the webpage doesn't seem to show what is on the image. I have to look at the Dockerfile on Github to find out.

Update I see this repo https://registry.hub.docker.com/u/tutum/tomcat/ has more tabs. The "Dockerfile" tab shows how it is created, but only seems to show the latest version. Is there no choice to view the file for other tags?

like image 962
stackoverflower Avatar asked Nov 29 '14 23:11

stackoverflower


1 Answers

You can get this for any registry by using the registry API. Within the image manifest is a pointer to the image config, and that config is a json blob that includes the history steps. Note that this history is not authoritative, and depending on the build tooling, could report different commands than what were actually used to build the image layers.

A shell script version of this using the auth for Docker Hub looks like:

#!/bin/sh

ref="${1:-library/ubuntu:latest}"
repo="${ref%:*}"
tag="${ref##*:}"
token=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repo}:pull" \
        | jq -r '.token')
digest=$(curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
              -H "Authorization: Bearer $token" \
              -s "https://registry-1.docker.io/v2/${repo}/manifests/${tag}" \
         | jq -r .config.digest)
curl -H "Accept: application/vnd.docker.container.image.v1+json" \
     -H "Authorization: Bearer $token" \
     -s -L "https://registry-1.docker.io/v2/${repo}/blobs/${digest}" | jq .history

Behind the scenes, what's happening is the registry first resolves any manifest list to a single manifest for the default architecture, but you could pull any architecture manually:

$ regctl image manifest --list localhost:5000/library/alpine:latest --format '{{jsonPretty .}}'
{
  "manifests": [
    {
      "digest": "sha256:69704ef328d05a9f806b6b8502915e6a0a4faa4d72018dc42343f511490daf8a",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      },
      "size": 528
    },
    {
      "digest": "sha256:18c29393a090ba5cde8a5f00926e9e419f47cfcfd206cc3f7f590e91b19adfe9",
      "mediaType": "application\/vnd.docker.distribution.manifest.v2+json",
      "platform": {
        "architecture": "arm",
        "os": "linux",
        "variant": "v6"
      },
      "size": 528
    },
...

Then that platform specific manifest includes the config and layers:

$ regctl image manifest localhost:5000/library/alpine@sha256:69704ef328d05a9f806b6b8502915e6a0a4faa4d72018dc42343f511490daf8a --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:14119a10abf4669e8cdbdff324a9f9605d99697215a0d21c360fe8dfa8471bab"
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 2814446,
      "digest": "sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e"
    }
  ]
}

And pulling the config blob shows all of the contents you may recognize from the history and inspect commands:

$ regctl blob get localhost:5000/library/alpine sha256:14119a10abf4669e8cdbdff324a9f9605d99697215a0d21c360fe8dfa8471bab | jq .
{
  "architecture": "amd64",
  "config": {
    "Hostname": "",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "/bin/sh"
    ],
    "Image": "sha256:d3e0b6258ec2f725c19668f11ae5323c3b0245e197ec478424ec6a87935690eb",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": null
  },
  "container": "330289c649db86f5fb1ae5bfef18501012b550adb0638b9193d4a3a4b65a2f9b",
  "container_config": {
    "Hostname": "330289c649db",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "Cmd": [
      "/bin/sh",
      "-c",
      "#(nop) ",
      "CMD [\"/bin/sh\"]"
    ],
    "Image": "sha256:d3e0b6258ec2f725c19668f11ae5323c3b0245e197ec478424ec6a87935690eb",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": {}
  },
  "created": "2021-08-27T17:19:45.758611523Z",
  "docker_version": "20.10.7",
  "history": [
    {
      "created": "2021-08-27T17:19:45.553092363Z",
      "created_by": "/bin/sh -c #(nop) ADD file:aad4290d27580cc1a094ffaf98c3ca2fc5d699fe695dfb8e6e9fac20f1129450 in / "
    },
    {
      "created": "2021-08-27T17:19:45.758611523Z",
      "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/sh\"]",
      "empty_layer": true
    }
  ],
  "os": "linux",
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:e2eb06d8af8218cfec8210147357a68b7e13f7c485b991c288c2d01dc228bb68"
    ]
  }
}

The regctl commands above come from my regclient project, but I believe you can do similar commands with skopeo and crane. The advantage of these tools over curl commands is handling the authentication that varies between registries (the bearer token from Hub won't work for other registries).

Lastly, the one-line answer using regctl is a formatted image config command, which performs all the above steps without needing jq:

$ regctl image config localhost:5000/library/alpine:latest --format '{{jsonPretty .History}}'
[
  {
    "created": "2021-08-27T17:19:45.553092363Z",
    "created_by": "/bin/sh -c #(nop) ADD file:aad4290d27580cc1a094ffaf98c3ca2fc5d699fe695dfb8e6e9fac20f1129450 in / "
  },
  {
    "created": "2021-08-27T17:19:45.758611523Z",
    "created_by": "/bin/sh -c #(nop)  CMD [\"/bin/sh\"]",
    "empty_layer": true
  }
]
like image 118
BMitch Avatar answered Oct 27 '22 22:10

BMitch