You can use docker save
to output a tar file of an image, potentially making it workable for analyzing the filesystem contents ala anti-virus scans and such:
$ docker pull microsoft/dotnet
$ docker save microsoft/dotnet --output dotnet.tar
So here we pull the official dotnet image from the docker store, then use docker save
to push it to a tar file. After that taking a look shows:
$ ls -1p
4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc/
58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/
7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3/
9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f/
9d32878ab9fe85ddbd2e18230e7fa9fe23a4d9a1231a7eaca6c668ad9ffd4244.json
a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6/
ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e/
ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8/
manifest.json
repositories
So there are a bunch of weirdly named directories plus two JSON files and a repositories file. Repositories seems interesting:
{"microsoft/dotnet":{"latest":"58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635"}}
Given that latest
points to one of the directories my guess is that it's the diff tail for that named tag. Out of further curiousity I decided to see what happend if I pulled down a tagged image then did a docker save
on the image without specifying a tag:
$ docker pull microsoft/dotnet:2.1.1-runtime-stretch-slim
$ docker save microsoft/dotnet --output dotnet_tagged.tar
Now the end result directory comes out like this:
0bb0001e31bac78432d38bac6d4e8512c9dec172bd1be54fc8be2303884ba6c3/
26b54366ad86b0948ddf01aca72e1ac498b3412ea9c187263bda42b2ec7153c5.json
4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc/
5169dbebd5a2f17f7e45a6fbedcd507de5504c045d448414c2ab8b8802dee058/
58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/
7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3/
83bc496b3e6ec08b26041758650a33eee75ace3b0da7313445b26e879afced06/
9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f/
9d32878ab9fe85ddbd2e18230e7fa9fe23a4d9a1231a7eaca6c668ad9ffd4244.json
a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6/
cc0c2ba935e6ef0726e63108d2c744f8ee4abeba00e2e25f7570b07c8366f94f/
ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e/
ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8/
manifest.json
repositories
The number of folders and toplevel .json
files has increased, and the repositories
file?
{"microsoft/dotnet":{"2.1.1-runtime-stretch-slim":"cc0c2ba935e6ef0726e63108d2c744f8ee4abeba00e2e25f7570b07c8366f94f","latest":"58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635"}}
So because we don't specify a tag and we had both the latest
and 2.1.1-runtime-stretch-slim
tagged of the image we ended up with two entries. If you were to do:
docker save microsoft/dotnet:2.1.1-runtime-stretch-slim --output dotnet_tagged.tar
instead then the repositories file would be the same as before just that you'd have the tag instead of latest. As with before the 2.1.1-runtime-stretch-slim
tagged entry also leads to a folder indicating it's probably the point in the list.
So you're probably wondering what the heck is going on. What's with all these directories? Well docker works with incremental builds. That way when you make a slight change to update a single package you don't have to upload the entire OS (assuming you've uploaded base images to the appropriate registry before). Layers are essentially just that with each having one of the following formats:
$ ls -1p 4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc/
VERSION
json
layer.tar
VERSION
here is the version of the JSON schema presented in json
. Basically if you're writing tools around these folders you'll want to pin them to VERSION
so you're not screwed trying to parse JSON you can't deal with. Now to look at the json
file:
{
"id": "4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc",
"parent": "a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6",
"created": "1970-01-01T00:00:00Z",
"container_config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": null,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
}
}
I've beautified it here for readability but the actual file is mostly minified JSON. There's not really a lot to work with here. Mostly you'll be interested in the "id" and "parent" values. The "id" is mostly a self identifier while the "parent" tells you another layer is required to make this work. Remember what I was talking about the repositories
showing the tail value? Well that's the last layer that gets applied from the base to make it work. So let's start there and work our way up:
$ grep -o '"parent":"[^"]*"' 58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/json
"parent":"7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3"
$ grep -o '"parent":"[^"]*"' 7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3/json
"parent":"4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc"
$ grep -o '"parent":"[^"]*"' 4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc/json
"parent":"a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6"
$ grep -o '"parent":"[^"]*"' a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6/json
"parent":"ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8"
$ grep -o '"parent":"[^"]*"' ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8/json
"parent":"ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e"
$ grep -o '"parent":"[^"]*"' ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e/json
"parent":"9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f"
$ grep -o '"parent":"[^"]*"' 9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f/json
$
So as shown here you pretty much get:
9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f
ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e
ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8
a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6
4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc
7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3
58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635
As the order you need to apply your layers in order to get to the final image. In fact if you look at docker history
output:
$ docker history microsoft/dotnet
IMAGE CREATED CREATED BY SIZE COMMENT
9d32878ab9fe 4 days ago /bin/sh -c dotnet help 1.08GB
<missing> 4 days ago /bin/sh -c #(nop) ENV ASPNETCORE_URLS=http:… 0B
<missing> 4 days ago /bin/sh -c curl -SL --output dotnet.tar.gz h… 337MB
<missing> 4 days ago /bin/sh -c #(nop) ENV DOTNET_SDK_VERSION=2.… 0B
<missing> 4 days ago /bin/sh -c apt-get update && apt-get ins… 32.5MB
<missing> 5 days ago /bin/sh -c apt-get update && apt-get install… 142MB
<missing> 5 days ago /bin/sh -c set -ex; if ! command -v gpg > /… 7.8MB
<missing> 5 days ago /bin/sh -c apt-get update && apt-get install… 23.2MB
<missing> 5 days ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 5 days ago /bin/sh -c #(nop) ADD file:f21d7c14104d5d9fa… 101MB
Then compare it with the filesize of layer.tar
in each of the layer folders:
$ docker history --human="false" --format "{{.ID}} | {{.CreatedSince}} | {{.CreatedBy}} | {{.Size}}" microsoft/dotnet
9d32878ab9fe | 2018-06-27T11:18:56-05:00 | /bin/sh -c dotnet help | 1081967095
<missing> | 2018-06-27T11:18:05-05:00 | /bin/sh -c #(nop) ENV ASPNETCORE_URLS=http:… | 0
<missing> | 2018-06-27T11:18:04-05:00 | /bin/sh -c curl -SL --output dotnet.tar.gz h… | 336706535
<missing> | 2018-06-27T11:17:08-05:00 | /bin/sh -c #(nop) ENV DOTNET_SDK_VERSION=2.… | 0
<missing> | 2018-06-27T11:17:08-05:00 | /bin/sh -c apt-get update && apt-get ins… | 32498826
<missing> | 2018-06-26T17:17:29-05:00 | /bin/sh -c apt-get update && apt-get install… | 141752094
<missing> | 2018-06-26T17:16:48-05:00 | /bin/sh -c set -ex; if ! command -v gpg > /… | 7802802
<missing> | 2018-06-26T17:16:40-05:00 | /bin/sh -c apt-get update && apt-get install… | 23221329
<missing> | 2018-06-26T16:24:58-05:00 | /bin/sh -c #(nop) CMD ["bash"] | 0
<missing> | 2018-06-26T16:24:58-05:00 | /bin/sh -c #(nop) ADD file:f21d7c14104d5d9fa… | 100561066
$ stat -f "%N %z" */layer.tar
4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc/layer.tar 32578048
58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/layer.tar 1102282240
7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3/layer.tar 337792000
9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f/layer.tar 105512960
a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6/layer.tar 146386432
ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e/layer.tar 24097280
ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8/layer.tar 7995904
You'll notice that the amount of layer folders doesn't quite match the number of entries in history. Turns out you really don't need those unless you're actually modifying the filesystem. The 0 byte updates end up being metadata changes which:
$ grep -Rl 'ASPNETCORE_URLS' *
58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/json
9d32878ab9fe85ddbd2e18230e7fa9fe23a4d9a1231a7eaca6c668ad9ffd4244.json
You can see hit a toplevel JSON file and the JSON file of the latest tag ID pointer. Now if we do this with tags:
$ docker history --human="false" --format "{{.ID}} | {{.CreatedSince}} | {{.CreatedBy}} | {{.Size}}" microsoft/dotnet:2.1.1-runtime-stretch-slim
26b54366ad86 | 2018-06-27T11:16:42-05:00 | /bin/sh -c curl -SL --output dotnet.tar.gz h… | 74211917
<missing> | 2018-06-27T11:16:36-05:00 | /bin/sh -c #(nop) ENV DOTNET_VERSION=2.1.1 | 0
<missing> | 2018-06-27T11:16:36-05:00 | /bin/sh -c apt-get update && apt-get ins… | 7020304
<missing> | 2018-06-27T11:16:18-05:00 | /bin/sh -c #(nop) ENV ASPNETCORE_URLS=http:… | 0
<missing> | 2018-06-27T11:16:17-05:00 | /bin/sh -c apt-get update && apt-get ins… | 43773214
<missing> | 2018-06-26T16:25:25-05:00 | /bin/sh -c #(nop) CMD ["bash"] | 0
<missing> | 2018-06-26T16:25:25-05:00 | /bin/sh -c #(nop) ADD file:28fbc9fd012eef727… | 55292915
You'll notice that it's not the same set. What about if we check those variables in an unpacked image with multiple tags?
$ grep -Rl 'ASPNETCORE_URLS' *
26b54366ad86b0948ddf01aca72e1ac498b3412ea9c187263bda42b2ec7153c5.json
58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/json
9d32878ab9fe85ddbd2e18230e7fa9fe23a4d9a1231a7eaca6c668ad9ffd4244.json
cc0c2ba935e6ef0726e63108d2c744f8ee4abeba00e2e25f7570b07c8366f94f/json
So it hits not one, but two top level JSON files as well as JSON files in the tail layer ID folder. This essentially means that non-file related nops will filter up metadata related changes to the tail layer's JSON file. If you're wondering what they top level JSON files are about, they are metadata for an image:
$ docker images --no-trunc
REPOSITORY TAG IMAGE ID CREATED SIZE
microsoft/dotnet latest sha256:9d32878ab9fe85ddbd2e18230e7fa9fe23a4d9a1231a7eaca6c668ad9ffd4244 4 days ago 1.72GB
microsoft/dotnet 2.1.1-runtime-stretch-slim sha256:26b54366ad86b0948ddf01aca72e1ac498b3412ea9c187263bda42b2ec7153c5 4 days ago 180MB
The sha's given match the filename identifiers minus the json extension. Looking at the latest tag's JSON entries you'll find some interesting information:
{
"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",
"DOTNET_SDK_VERSION=2.1.301",
"ASPNETCORE_URLS=http://+:80",
"DOTNET_RUNNING_IN_CONTAINER=true",
"DOTNET_USE_POLLING_FILE_WATCHER=true",
"NUGET_XMLDOC_MODE=skip"
],
"Cmd": [
"bash"
],
"ArgsEscaped": true,
"Image": "sha256:dbf7c9ea5c79113a57eac7c7cfd1348c377f2378274db7964df4e3296743e44b",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": [],
"Labels": null
},
"container": "9d266f53b8a701301650a6fbccf72df45b1774b40b15aa92891fda2ea42b87f8",
"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",
"DOTNET_SDK_VERSION=2.1.301",
"ASPNETCORE_URLS=http://+:80",
"DOTNET_RUNNING_IN_CONTAINER=true",
"DOTNET_USE_POLLING_FILE_WATCHER=true",
"NUGET_XMLDOC_MODE=skip"
],
"Cmd": [
"/bin/sh",
"-c",
"dotnet help"
],
"ArgsEscaped": true,
"Image": "sha256:dbf7c9ea5c79113a57eac7c7cfd1348c377f2378274db7964df4e3296743e44b",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": [],
"Labels": null
},
"created": "2018-06-27T16:18:56.2008874Z",
"docker_version": "18.03.1-ce",
"history": [
{
"created": "2018-06-26T21:24:58.552395569Z",
"created_by": "/bin/sh -c #(nop) ADD file:f21d7c14104d5d9fa99f271177e765a3472f5a69398bb78f34f7401e9b2df837 in / "
},
{
"created": "2018-06-26T21:24:58.974831097Z",
"created_by": "/bin/sh -c #(nop) CMD [\"bash\"]",
"empty_layer": true
},
{
"created": "2018-06-26T22:16:40.735261597Z",
"created_by": "/bin/sh -c apt-get update && apt-get install -y --no-install-recommends \t\tca-certificates \t\tcurl \t\tnetbase \t\twget \t&& rm -rf /var/lib/apt/lists/*"
},
{
"created": "2018-06-26T22:16:48.022107092Z",
"created_by": "/bin/sh -c set -ex; \tif ! command -v gpg > /dev/null; then \t\tapt-get update; \t\tapt-get install -y --no-install-recommends \t\t\tgnupg \t\t\tdirmngr \t\t; \t\trm -rf /var/lib/apt/lists/*; \tfi"
},
{
"created": "2018-06-26T22:17:29.585929208Z",
"created_by": "/bin/sh -c apt-get update && apt-get install -y --no-install-recommends \t\tbzr \t\tgit \t\tmercurial \t\topenssh-client \t\tsubversion \t\t\t\tprocps \t&& rm -rf /var/lib/apt/lists/*"
},
{
"created": "2018-06-27T16:17:08.200176Z",
"created_by": "/bin/sh -c apt-get update && apt-get install -y --no-install-recommends libc6 libgcc1 libgssapi-krb5-2 libicu57 liblttng-ust0 libssl1.0.2 libstdc++6 zlib1g && rm -rf /var/lib/apt/lists/*"
},
{
"created": "2018-06-27T16:17:08.8406262Z",
"created_by": "/bin/sh -c #(nop) ENV DOTNET_SDK_VERSION=2.1.301",
"empty_layer": true
},
{
"created": "2018-06-27T16:18:04.4654999Z",
"created_by": "/bin/sh -c curl -SL --output dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$DOTNET_SDK_VERSION/dotnet-sdk-$DOTNET_SDK_VERSION-linux-x64.tar.gz && dotnet_sha512='2101df5b1ca8a4a67f239c65080112a69fb2b48c1a121f293bfb18be9928f7cfbf2d38ed720cbf39c9c04734f505c360bb2835fa5f6200e4d763bd77b47027da' && sha512sum dotnet.tar.gz && echo \"$dotnet_sha512 dotnet.tar.gz\" | sha512sum -c - && mkdir -p /usr/share/dotnet && tar -zxf dotnet.tar.gz -C /usr/share/dotnet && rm dotnet.tar.gz && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet"
},
{
"created": "2018-06-27T16:18:05.0279261Z",
"created_by": "/bin/sh -c #(nop) ENV ASPNETCORE_URLS=http://+:80 DOTNET_RUNNING_IN_CONTAINER=true DOTNET_USE_POLLING_FILE_WATCHER=true NUGET_XMLDOC_MODE=skip",
"empty_layer": true
},
{
"created": "2018-06-27T16:18:56.2008874Z",
"created_by": "/bin/sh -c dotnet help"
}
],
"os": "linux",
"rootfs": {
"type": "layers",
"diff_ids": [
"sha256:a2e66f6c6f5f248f2f8dcf31a3b569626ef61c6371e157fb0db857152983fc1d",
"sha256:e14378b596fba6a0e3130a89610fa340c62b76f93fefb394b9243cd34b9449d2",
"sha256:43701cc7035143dad7edea420be1cc475f38506bf8bfebdfb350916ed4b18c7a",
"sha256:c30dae2762bdd89451c076c0d6c2f37292d8e27a1de2a47bb747921942312cfd",
"sha256:f4850b2b67700478bc0f0f3e979cf941f86cefa891a8aa21eacf988b9b0e9c01",
"sha256:b3188a29c28ba577c17b239633432a159a28859e6fd4631014c8405119c92045",
"sha256:30b8545cf971222d47806cc702699f5fa66bcbb2959bfff6ad92cb9e907af1e6"
]
}
}
One interesting piece of information is that this JSON contains much of the output that was in docker history
. Of particular interest is the empty_layer
attribute which shows areas where a layer.tar
was not created and simply metadata updates occurred.
diff_ids
is also interesting from a validation perspective. If you look at the manifest.json
file you'll notice a few things:
[
{
"Config": "9d32878ab9fe85ddbd2e18230e7fa9fe23a4d9a1231a7eaca6c668ad9ffd4244.json",
"RepoTags": [
"microsoft/dotnet:latest"
],
"Layers": [
"9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f/layer.tar",
"ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e/layer.tar",
"ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8/layer.tar",
"a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6/layer.tar",
"4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc/layer.tar",
"7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3/layer.tar",
"58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/layer.tar"
]
}
]
Layers are directly associated with the layer.tar
files in the layer ID folders. Now if you run a sha256 on each of these files:
$ shasum -a 256 "9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f/layer.tar" "ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e/layer.tar" "ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8/layer.tar" "a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6/layer.tar" "4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc/layer.tar" "7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3/layer.tar" "58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/layer.tar"
a2e66f6c6f5f248f2f8dcf31a3b569626ef61c6371e157fb0db857152983fc1d 9b8967520f4a8a8111390d20d14c6d14d17483fbcfe94810e37e4691ed1c866f/layer.tar
e14378b596fba6a0e3130a89610fa340c62b76f93fefb394b9243cd34b9449d2 ccccb3b1c4d09fcd2532e9528d55a8e7fb9551112e2d7b7222dabdedfd90e20e/layer.tar
43701cc7035143dad7edea420be1cc475f38506bf8bfebdfb350916ed4b18c7a ff817ba161df73af9764e49ba69a02f5410f1dda2af7b67310987c7445bde8d8/layer.tar
c30dae2762bdd89451c076c0d6c2f37292d8e27a1de2a47bb747921942312cfd a07df3daa7d55ad115e0fedeca55b7cef92b436219cc5b0e9e86d854eeb001b6/layer.tar
f4850b2b67700478bc0f0f3e979cf941f86cefa891a8aa21eacf988b9b0e9c01 4fca0fc1feef92e7a68dfbdfa41435dee495a2a3fb22db2bfb1123ef741c06fc/layer.tar
b3188a29c28ba577c17b239633432a159a28859e6fd4631014c8405119c92045 7ed7310248364f5e57cd1f115fe416edade44c43e972d7b0aa2861f388e91ad3/layer.tar
30b8545cf971222d47806cc702699f5fa66bcbb2959bfff6ad92cb9e907af1e6 58198826d5338fff62079565c54177e1516da34be73e5ecd674c20f0f290c635/layer.tar
You'll notice that the sha's calculated are in the same order of the diff_ids
array. This is a nice way to validate that your layer tars at least match sha-checksums for integrity (which you still assume the manifest.json
and layer ID JSON are valid at that point).