Container registries expose a standard api -- Docker Registry HTTP API V2 -- that you can call to interact with container images. You can efficiently learn about images by downloading and processing very small image manifests (~2K; JSON format). Calling these APIs via curl
isn't an alternative to the docker
CLI, but enables additional workflows.
The following set of commands tell us that nearly 2000 tags have been published to the dotnet/runtime
repo in the mcr.microsoft.com
registry.
curl -s https://mcr.microsoft.com/v2/dotnet/runtime/tags/list | jq .tags | wc -l
1997
It's also straightforward to write general registry clients or more specific ones that perform tasks like image up-to-date checks.
The remainder of this document discussed image manifests.
An image manifest can be requested via the following URL scheme (in C# syntax):
string url = $"https://{registry}/v2/{repo}/manifests/{tag}";
Note: HTTP headers are also required, as you will see.
The following example demonstrates the basic pattern, requesting the manifest for the mcr.microsoft.com/dotnet/runtime:7.0
image.
$ curl -s -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json" https://mcr.microsoft.com/v2/dotnet/runtime/manifests/7.0
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1160,
"digest": "sha256:2dfb924c56bcd4686574471abdb42134a53e9179947b1c598639f56d97161fdd",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1160,
"digest": "sha256:213e687504ec1f6050e30609e054b975331c483cea5d15f804a6d6374f89f4ae",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1160,
"digest": "sha256:1fc87d6c1f375360d67e1f1df55703e72c2962929b082769d35d5174d9d98ae1",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1754,
"digest": "sha256:7d5b38b2360e0ec65d91d707b52321101882175969d9446b281acb53a3ffcf51",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.17763.3532"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1755,
"digest": "sha256:91204424b6c203664d428e526e3c364facfcff29726816fa45a35814b772d564",
"platform": {
"architecture": "amd64",
"os": "windows",
"os.version": "10.0.20348.1129"
}
}
]
}
The mcr.microsoft.com
registry is used since it is one of the few public registries that doesn't require authorization to pull public images. It's also the registry my team uses.
There are multiple versions and types of manifest schema. The latest version is Image Manifest Version 2, Schema 2, which is what will be used throughout this document.
There are multiple media types:
application/vnd.docker.distribution.manifest.v1+json
: schema1 (existing manifest format)application/vnd.docker.distribution.manifest.v2+json
: New image manifest format (schemaVersion = 2)application/vnd.docker.distribution.manifest.list.v2+json
: Manifest list, aka “fat manifest”application/vnd.docker.container.image.v1+json
: Container config JSONapplication/vnd.docker.image.rootfs.diff.tar.gzip
: “Layer”, as a gzipped tarapplication/vnd.docker.image.rootfs.foreign.diff.tar.gzip
: “Layer”, as a gzipped tar that should never be pushedapplication/vnd.docker.plugin.v1+json
: Plugin config JSON
The two (v2
) media types are the key ones. When you walk up to a registry and ask for a modern image, it will either be a container image (application/vnd.docker.distribution.manifest.v2+json
) or a multi-arch manifest (application/vnd.docker.distribution.manifest.list.v2+json
). Unless you know which media type you should be asking for (given the nature of the image), you should ask for both. For example, the 7.0
image above is a application/vnd.docker.distribution.manifest.list.v2+json
type image. That makes sense since my team wants the 7.0
image to be usable in lots of places.
The example above demonstrates how to request the manifest for a registry tag, w/o a priori knowledge of the image media type. In the case (like we saw) of a manifest list response, then we may want to request information about an actual referenced image. We can now ask for one of the listed application/vnd.docker.distribution.manifest.v2+json
manifests. We'll target the linux+x64 manifest. It's the same pattern, but we'll use a digest SHA instead of a tag. It's from the curl
response above.
$ curl -s -H "Accept: application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json" https://mcr.microsoft.com/v2/dotnet/runtime/manifests/sha256:2dfb924c56bcd4686574471abdb42134a53e9179947b1c598639f56d97161fdd
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 2836,
"digest": "sha256:0aeb78cec714e0e0062824f19ed6ff1b6747fececf864f0951b2b446bb4eb3fd"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 31420038,
"digest": "sha256:e9995326b091af7b3ce352fad4d76cf3a3cb62b7a0c35cc5f625e8e649d23c50"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 14967219,
"digest": "sha256:83280acc7c2c9fb3fecc49c3a89d133b323d7b79565f4b483014ed6f15e490b3"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 32435439,
"digest": "sha256:b08fd3a9929c3fadd980f0d51915837fe299657c3d62f2fc04bab3646961415b"
},
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 155,
"digest": "sha256:058f65a20fb5ccb9c513d000e1a8ef5b1eefa936edf2503afb391dd9233c3034"
}
]
}
In this image, you see layers
instead of manifests
since this we are look at an actual container image. The returned mediaType
demonstrates that.
This request was made with both the v2
media types in the Accept
header. That's not necessary. I didn't bother changing the request to just the application/vnd.docker.distribution.manifest.v2+json
media type.
You can see that each of the layers are the application/vnd.docker.image.rootfs.diff.tar.gzip
media type. You can request those in a similar way.
$ curl -L -H "Accept: application/vnd.docker.image.rootfs.diff.tar.gzip" https://mcr.microsoft.com/v2/dotnet/runtime/blobs/sha256:e9995326b091af7b3ce352fad4d76cf3a3cb62b7a0c35cc5f625e8e649d23c50
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
If you request that content, you will see that curl
warns you that the content is binary data. There is a reason for that (it's binary layer data).
However, the first digest in the manifest (before the layers
content) is special. It's not binary data, but metadata for the image. Let's take a look.
$ curl -sL -H "Accept: application/vnd.docker.image.rootfs.diff.tar.gzip" https://mcr.microsoft.com/v2/dotnet/runtime/blobs/sha256:0aeb78cec714e0e0062824f19ed6ff1b6747fececf864f0951b2b446bb4eb3fd | 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",
"ASPNETCORE_URLS=https://+:80",
"DOTNET_RUNNING_IN_CONTAINER=true",
"DOTNET_VERSION=7.0.0-rc.2.22472.3"
],
"Cmd": [
"bash"
],
"Image": "sha256:0b8d1fd16e32475264e397313f6ef8cee32621b5ed2238053542169f2ea1e70d",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"container": "99f45221908cd63d1a250ee16beed4bb4862a1107aca7d607c89e3114d02c65f",
"container_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",
"ASPNETCORE_URLS=https://+:80",
"DOTNET_RUNNING_IN_CONTAINER=true",
"DOTNET_VERSION=7.0.0-rc.2.22472.3"
],
"Cmd": [
"/bin/sh",
"-c",
"ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet"
],
"Image": "sha256:0b8d1fd16e32475264e397313f6ef8cee32621b5ed2238053542169f2ea1e70d",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"created": "2022-10-25T04:26:19.445847737Z",
"docker_version": "20.10.17",
"history": [
{
"created": "2022-10-25T01:43:53.171278246Z",
"created_by": "/bin/sh -c #(nop) ADD file:8644a8156a07a656a35c41e2b2a458befb660309f8592e3efd5b43d46156cec2 in / "
},
{
"created": "2022-10-25T01:43:53.514250664Z",
"created_by": "/bin/sh -c #(nop) CMD [\"bash\"]",
"empty_layer": true
},
{
"created": "2022-10-25T04:25:57.83090842Z",
"created_by": "/bin/sh -c apt-get update && apt-get install -y --no-install-recommends ca-certificates libc6 libgcc1 libgssapi-krb5-2 libicu67 libssl1.1 libstdc++6 zlib1g && rm -rf /var/lib/apt/lists/*"
},
{
"created": "2022-10-25T04:25:58.281747234Z",
"created_by": "/bin/sh -c #(nop) ENV ASPNETCORE_URLS=https://+:80 DOTNET_RUNNING_IN_CONTAINER=true",
"empty_layer": true
},
{
"created": "2022-10-25T04:26:16.267058345Z",
"created_by": "/bin/sh -c #(nop) ENV DOTNET_VERSION=7.0.0-rc.2.22472.3",
"empty_layer": true
},
{
"created": "2022-10-25T04:26:18.544328798Z",
"created_by": "/bin/sh -c #(nop) COPY dir:f0df7b7df9c2211bc690d2eb1e7cd5aa5ad3773275d0f2a818a43b148923e2c9 in /usr/share/dotnet "
},
{
"created": "2022-10-25T04:26:19.445847737Z",
"created_by": "/bin/sh -c ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet"
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:a12586ed027fafddcddcc63b31671f406c25e43342479fc92a330e7e30d65f2e",
"sha256:55125ebb892069bd16e8dedc05e382be6a818ed99026f2e5f8578852b7c6d1cb",
"sha256:7a10df1adcc9c0f6ec933d9502afd8bdd4c9d058559f63315cb7008e2b5c2222",
"sha256:b866408b8d940b8b100d176939c5a01cdffdb17792e310329dca7f3d6d41c7b5"
]
}
}
This looks just like docker inspect
. Yes.
Some registries require authorization, for both public and private content. These use some form of Docker Registry Token Authentication scheme.
Authorization information is provided by a Bearer
token in an Authorization
header.
These schemes are significantly different per registry: