Search code examples
bashdocker

How to import many fsLayers into docker as one image


I have downloaded several fsLayers images for a given docker image. How can I import them as a single image in docker?

Consider the following commands, which will download all of the fsLayers for the nextcloud image from Docker Hub

curl -so "token.json" "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nextcloud:pull";
token=$(cat token.json | jq -jr ".token")

curl -o manifest.json -#H "Authorization: Bearer ${token}" https://registry-1.docker.io/v2/library/nextcloud/manifests/stable

fsLayers=$(cat manifest.json | jq -r ".fsLayers[].blobSum")
for layer in $fsLayers; do
    echo $layer;
    file="$(echo "${layer}" | cut -d: -f2).tar.gz"
    curl -o "${file}" -#LH "Authorization: Bearer ${token}" https://registry-1.docker.io/v2/library/nextcloud/blobs/${layer}
done

After executing the above commands, I now have all the layers downloaded in separate gz-compressed tarballs.

user@disp7148:~$ ls
09f376ebb190216b0459f470e71bec7b5dfa611d66bf008492b40dcc5f1d8eae.tar.gz
1693466e4cc6edc54994f2c9c9e1989f28b0df6243b3c709876e36a999aac7ee.tar.gz
29e89bc69515a29ca19a9f9d5159e84862484ce94772369f8eb8ea55471b22ae.tar.gz
35129b4567fb01bf2ed62b98beeb738072c263291f0efaf238c50af3dcbb5ee3.tar.gz
43af3fe8136ad9261342f5add4a18489a3ffc70ba50f2651ea7cc1b3d0e45875.tar.gz
501c476418a820ad744262168cc1942997c004938bca4411e76c9fd2423f78b6.tar.gz
57c8d94a4882fa141e8c25a4a5ea1f1188629cc772674bdc7dcd69c13a07bd25.tar.gz
5b819af798e29a5411463ff791375a52cc270c442dec56cb1fae2365a3033d56.tar.gz
76afcdc8655129b4b8245d674820f06020b8a54f3db6c8d9147234b985f3c923.tar.gz
8d0c9118b5f56bd64e1ff9a579351c1c0cdc78cd5480b421affe8ef4df774970.tar.gz
9ec84be954b08b2782f93426799a585ad7b33b0e7d57b3f725218d450ea5d20d.tar.gz
a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.tar.gz
a4ba0bdbbf0a7e73b6acc7dcd82c8dbc4635884963f73bbebcbac1a2f988cf99.tar.gz
b760377fd0ccbf30765bec3415200ae24b35c6da60725aa6dd49a74b737af10b.tar.gz
bdad2781572257d109331576ace9e6292ca5bab9a8d069e9f59bf185db3c1b29.tar.gz
cc438aaa3f45fe13b885e2bcef089aafa4534fb8d0b8543c216534561e81f8a4.tar.gz
ccdfd53780b4d34d5cb6ce17495dd3e5a13f585f6fd52f7a05b9aa917cd5b014.tar.gz
ceed4541c527d7a443908138f347495ec250ba7a1e70d8dd6b567464064ee115.tar.gz
dbbbc9a8833202619954978f80ebc0a2284f0e0b248972d29a930f7461853b61.tar.gz
ddef75e08a5d03695ba1b0ce20598e067646188c87a8367b98c0b1768a280b7f.tar.gz
e4d6ffd249e6677e14868de2526b46ff49ab74a9c732bc37bfdf38759a4f8d66.tar.gz
ff0e278869f981b14b52a6f43e6a0ce131ed2f3067de4314ed34393669549724.tar.gz
manifest.json
token.json
user@disp7148:~$

I can import a single one of these layers with the following command

zcat 501c476418a820ad744262168cc1942997c004938bca4411e76c9fd2423f78b6.tar.gz | docker image import - nextcloud:stable

...but how do I import all of the different fsLayers into one image in docker?


Solution

  • You can import multiple layers into docker using docker image load if you organize the layers (and metadata about the layers) in a directory following the Docker Image Specification v1.x.x.

    The Specification

    Released in January 2015, the Docker Image Specification v1.0.0 defines exactly what you want: how to arrange the different layer tarballs on-disk so that they can be fed into docker to import it as one image.

    The spec provides an example tree of the files:

    For example, here's what the full archive of library/busybox is (displayed in tree format):

    .
    ├── 5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e
    │   ├── VERSION
    │   ├── json
    │   └── layer.tar
    ├── a7b8b41220991bfc754d7ad445ad27b7f272ab8b4a2c175b9512b97471d02a8a
    │   ├── VERSION
    │   ├── json
    │   └── layer.tar
    ├── a936027c5ca8bf8f517923169a233e391cbb38469a75de8383b5228dc2d26ceb
    │   ├── VERSION
    │   ├── json
    │   └── layer.tar
    ├── f60c56784b832dd990022afc120b8136ab3da9528094752ae13fe63a2d28dc8c
    │   ├── VERSION
    │   ├── json
    │   └── layer.tar
    └── repositories
    

    There are one or more directories named with the ID for each layer in a full image. Each of these directories contains 3 files:

    * `VERSION` - The schema version of the `json` file
    * `json` - The JSON metadata for an image layer
    * `layer.tar` - The Tar archive of the filesystem changeset for an image
      layer.
    

    The content of the VERSION files is simply the semantic version of the JSON metadata schema:

    1.0
    

    And the repositories file is another JSON file which describes names/tags:

    {  
        "busybox":{  
            "latest":"5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e"
        }
    }
    

    Every key in this object is the name of a repository, and maps to a collection of tag suffixes. Each tag maps to the ID of the image represented by that tag.

    The code

    In your case, for the nextcloud:stable image, you can create these files with the following steps.

    Layer Download

    First, let's download the layers. But we'll put each layer in its own directory, and put each of these directories in a directory named layers.

    Note that the image manifest.json file contains two parallel arrays of the same length:

    1. fsLayers[]
    2. history[]

    Consider this truncated snippet of the first few elements of these arrays

    {
       "schemaVersion": 1,
       "name": "library/nextcloud",
       "tag": "stable",
       "architecture": "amd64",
       "fsLayers": [
          {
             "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
          },
          {
             "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
          },
          {
             "blobSum": "sha256:e4d6ffd249e6677e14868de2526b46ff49ab74a9c732bc37bfdf38759a4f8d66"
          },
    ...
          {
             "blobSum": "sha256:09f376ebb190216b0459f470e71bec7b5dfa611d66bf008492b40dcc5f1d8eae"
          }
       ],
       "history": [
          {
             "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"80/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"PHPIZE_DEPS=autoconf \\t\\tdpkg-dev \\t\\tfile \\t\\tg++ \\t\\tgcc \\t\\tlibc-dev \\t\\tmake \\t\\tpkg-config \\t\\tre2c\",\"PHP_INI_DIR=/usr/local/etc/php\",\"APACHE_CONFDIR=/etc/apache2\",\"APACHE_ENVVARS=/etc/apache2/envvars\",\"PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64\",\"PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64\",\"PHP_LDFLAGS=-Wl,-O1 -pie\",\"GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC\",\"PHP_VERSION=8.2.19\",\"PHP_URL=https://www.php.net/distributions/php-8.2.19.tar.xz\",\"PHP_ASC_URL=https://www.php.net/distributions/php-8.2.19.tar.xz.asc\",\"PHP_SHA256=aecd63f3ebea6768997f5c4fccd98acbf897762ed5fc25300e846197a9485c13\",\"PHP_MEMORY_LIMIT=512M\",\"PHP_UPLOAD_LIMIT=512M\",\"APACHE_BODY_LIMIT=1073741824\",\"NEXTCLOUD_VERSION=28.0.5\"],\"Cmd\":[\"apache2-foreground\"],\"Image\":\"sha256:e78a60ffed800c04d7ea2dbdf729079eaf30202a4a91b56164e96393894ad631\",\"Volumes\":{\"/var/www/html\":{}},\"WorkingDir\":\"/var/www/html\",\"Entrypoint\":[\"/entrypoint.sh\"],\"OnBuild\":null,\"Labels\":null,\"StopSignal\":\"SIGWINCH\"},\"container\":\"44399bfd05f1624c6fe5296facf61ba01990cf6e27a920218967cb96918ea138\",\"container_config\":{\"Hostname\":\"44399bfd05f1\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"80/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"PHPIZE_DEPS=autoconf \\t\\tdpkg-dev \\t\\tfile \\t\\tg++ \\t\\tgcc \\t\\tlibc-dev \\t\\tmake \\t\\tpkg-config \\t\\tre2c\",\"PHP_INI_DIR=/usr/local/etc/php\",\"APACHE_CONFDIR=/etc/apache2\",\"APACHE_ENVVARS=/etc/apache2/envvars\",\"PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64\",\"PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64\",\"PHP_LDFLAGS=-Wl,-O1 -pie\",\"GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC\",\"PHP_VERSION=8.2.19\",\"PHP_URL=https://www.php.net/distributions/php-8.2.19.tar.xz\",\"PHP_ASC_URL=https://www.php.net/distributions/php-8.2.19.tar.xz.asc\",\"PHP_SHA256=aecd63f3ebea6768997f5c4fccd98acbf897762ed5fc25300e846197a9485c13\",\"PHP_MEMORY_LIMIT=512M\",\"PHP_UPLOAD_LIMIT=512M\",\"APACHE_BODY_LIMIT=1073741824\",\"NEXTCLOUD_VERSION=28.0.5\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"apache2-foreground\\\"]\"],\"Image\":\"sha256:e78a60ffed800c04d7ea2dbdf729079eaf30202a4a91b56164e96393894ad631\",\"Volumes\":{\"/var/www/html\":{}},\"WorkingDir\":\"/var/www/html\",\"Entrypoint\":[\"/entrypoint.sh\"],\"OnBuild\":null,\"Labels\":{},\"StopSignal\":\"SIGWINCH\"},\"created\":\"2024-05-14T19:35:01.826109632Z\",\"docker_version\":\"20.10.23\",\"id\":\"8bb71f757b3f57b39727d791946f1f50f3eeb9e83bcd2a41a2c0d975c05d7913\",\"os\":\"linux\",\"parent\":\"a627048f860d478fc492fd2a44341afc2a61b3c120e086161414bc2cd35e6356\",\"throwaway\":true}"
          },
          {
             "v1Compatibility": "{\"id\":\"a627048f860d478fc492fd2a44341afc2a61b3c120e086161414bc2cd35e6356\",\"parent\":\"ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435\",\"created\":\"2024-05-14T19:35:01.725747724Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  ENTRYPOINT [\\\"/entrypoint.sh\\\"]\"]},\"throwaway\":true}"
          },
          {
             "v1Compatibility": "{\"id\":\"ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435\",\"parent\":\"6741399104ab522484ce1ca58960e6997e6b85d1ce8e99b8584acb03f1591ff6\",\"created\":\"2024-05-14T19:35:01.628255621Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) COPY multi:325eebb30e6912e4cdbb030d4d22b6dd14fe18ca007f50df3afe27a136e1e383 in /usr/src/nextcloud/config/ \"]}}"
          },
    ...
          {
             "v1Compatibility": "{\"id\":\"a3fd99121603fe47c7afef0f8f9721974c3cece6793251d8e01440727647010b\",\"created\":\"2024-05-14T01:28:03.648179024Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:5aaace706aa00ff97d243daa2c29f5de88f124e1b97c570634f16eef90783286 in / \"]}}"
          }
       ],
    ...
    }
    

    The sha256sum used to download the blob of the first layer is found at fsLayers[0]. The metadata about this layer is found at history[0].

    The sha256sum used to download the blob of the second layer is found at fsLayers[1]. The metadata about this layer is found at history[1].

    Now let's iterate through each layer and [a] create a dir for each layer, [b] download each layer, and [c] create a json file with the metadata for each layer, as required by the spec (see above)

    curl -so "token.json" "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/nextcloud:pull";
    token=$(cat token.json | jq -jr ".token")
    
    curl -o manifest.json -#H "Authorization: Bearer ${token}" https://registry-1.docker.io/v2/library/nextcloud/manifests/stable
    
    num_layers=$(cat manifest.json | jq -r ".history | length")
    for ((i = 0 ; i < $num_layers ; i++)); do
        layer_blobSum=$(cat manifest.json | jq -r ".fsLayers[$i].blobSum")
        layer_metadata=$(cat manifest.json | jq -r ".history[$i].v1Compatibility")
        layer_id=$(echo $layer_metadata | jq -r ".id")
    
        echo $layer_id
        echo $layer_blobSum
    
        mkdir -p "layers/$layer_id"
        echo $layer_metadata > "layers/$layer_id/json"
        curl -o "layers/$layer_id/layer.tar" -#LH "Authorization: Bearer ${token}" "https://registry-1.docker.io/v2/library/nextcloud/blobs/${layer_blobSum}"
    done
    

    Here's an example execution

    ############################################################### 100.0%
    8bb71f757b3f57b39727d791946f1f50f3eeb9e83bcd2a41a2c0d975c05d7913
    sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
    ############################################################### 100.0%
    a627048f860d478fc492fd2a44341afc2a61b3c120e086161414bc2cd35e6356
    sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
    ############################################################### 100.0%
    ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435
    sha256:e4d6ffd249e6677e14868de2526b46ff49ab74a9c732bc37bfdf38759a4f8d66
    ############################################################### 100.0%
    ...
    a3fd99121603fe47c7afef0f8f9721974c3cece6793251d8e01440727647010b
    sha256:09f376ebb190216b0459f470e71bec7b5dfa611d66bf008492b40dcc5f1d8eae
    ############################################################### 100.0%
    

    Note that, in addition to downloading the layers/<id>/layer.tar file, we also created a layers/<id>/json file, which was just an element of the history[] array from manifest.json corresponding to each layer.

    The VERSION file

    To comply with the Docker Image Specification (see above), every layer dir should also have a file named VERSION that simply has the contents 1.0.

    Execute this to create these VERSION files in each layer's directory

    dirs=$(find layers/ -mindepth 1 -type d)
    
    for dir in ${dirs}; do
        echo '1.0' > "${dir}/VERSION";
    done
    

    The repositories file

    To comply with the Docker Image Specification (see above), we also must add a file named repositories at the same height as the layer dirs, which states the name & tag of the image, and it points to the first layer of the image.

    Note the 0th item in the history[] array is the first layer of the image.

    Execute this to create the repository file

    start_image=$(cat manifest.json | jq -r ".history[0].v1Compatibility")
    start_image_id=$(echo $start_image | jq -r ".id")
    
    cat > layers/repositories <<EOF
    {
        "nextcloud": { "stable": "$start_image_id" }
    }
    EOF
    

    Final directory

    You should now have a layers dir with the following layout that complies with the Docker Image Specification and is ready to be read by docker image load

    user@disp341:~$ tree layers
    layers
    ├── 027570efa75696b1318ac9d19a0c8202c45eaaa18896d792c546773148786cb0
    │   ├── json
    │   ├── layer.tar
    │   └── VERSION
    ...
    ├── 8bb71f757b3f57b39727d791946f1f50f3eeb9e83bcd2a41a2c0d975c05d7913
    │   ├── json
    │   ├── layer.tar
    │   └── VERSION
    ...
    ├── a627048f860d478fc492fd2a44341afc2a61b3c120e086161414bc2cd35e6356
    │   ├── json
    │   ├── layer.tar
    │   └── VERSION
    ...
    ├── ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435
    │   ├── json
    │   ├── layer.tar
    │   └── VERSION
    ...
    ├── ee7c13dd7c918f94bf123c3836668d657e1b606ac2cd42a8ce36d2b82ad582d3
    │   ├── json
    │   ├── layer.tar
    │   └── VERSION
    └── repositories
    
    46 directories, 136 files
    user@disp341:~$ 
    

    And here's the contents of one of the layers, for example:

    user@disp341:~$ ls layers/ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435/
    json  layer.tar  VERSION
    user@disp341:~$ 
    
    user@disp341:~$ cat layers/ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435/VERSION 
    1.0
    user@disp341:~$
    
    user@disp341:~$ cat layers/ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435/json 
    {"id":"ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435","parent":"6741399104ab522484ce1ca58960e6997e6b85d1ce8e99b8584acb03f1591ff6","created":"2024-05-14T19:35:01.628255621Z","container_config":{"Cmd":["/bin/sh -c #(nop) COPY multi:325eebb30e6912e4cdbb030d4d22b6dd14fe18ca007f50df3afe27a136e1e383 in /usr/src/nextcloud/config/ "]}}
    user@disp341:~$ 
    
    user@disp341:~$  sha256sum layers/ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435/layer.tar 
    e4d6ffd249e6677e14868de2526b46ff49ab74a9c732bc37bfdf38759a4f8d66  layers/ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435/layer.tar
    user@disp341:~$ 
    
    user@disp341:~$ tar -tvf layers/ddcd091dc0c67ce58031ca76994b637c6a088d64bd498c34da51ca1a014bf435/layer.tar 
    drwxr-xr-x 0/0               0 2024-05-12 19:00 usr/
    drwxr-xr-x 0/0               0 2024-05-14 14:34 usr/src/
    drwxr-xr-x 65534/65534       0 2024-05-14 14:34 usr/src/nextcloud/
    drwxr-xr-x 65534/65534       0 2024-05-14 14:35 usr/src/nextcloud/config/
    -rw-rw-r-- 0/0              60 2024-05-14 14:33 usr/src/nextcloud/config/apache-pretty-urls.config.php
    -rw-rw-r-- 0/0              70 2024-05-14 14:33 usr/src/nextcloud/config/apcu.config.php
    -rw-rw-r-- 0/0             377 2024-05-14 14:33 usr/src/nextcloud/config/apps.config.php
    -rw-rw-r-- 0/0            2110 2024-05-14 14:33 usr/src/nextcloud/config/autoconfig.php
    -rw-rw-r-- 0/0             484 2024-05-14 14:33 usr/src/nextcloud/config/redis.config.php
    -rw-rw-r-- 0/0             798 2024-05-14 14:33 usr/src/nextcloud/config/reverse-proxy.config.php
    -rw-rw-r-- 0/0            2473 2024-05-14 14:33 usr/src/nextcloud/config/s3.config.php
    -rw-rw-r-- 0/0            1025 2024-05-14 14:33 usr/src/nextcloud/config/smtp.config.php
    -rw-rw-r-- 0/0            1103 2024-05-14 14:33 usr/src/nextcloud/config/swift.config.php
    -rw-rw-r-- 0/0              60 2024-05-14 14:33 usr/src/nextcloud/config/upgrade-disable-web.config.php
    user@disp341:~$ 
    

    Image Load

    Finally, you can load the layers as one image into docker with docker image load

    tar -cC layers . | docker image load
    

    Here's an example

    user@disp341:~$ docker image ls
    REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
    user@disp341:~$ 
    
    user@disp341:~$ tar -cC layers . | docker load
    a3fd99121603: Loading layer [==================================================>]  29.15MB/29.15MB
    b0aafcfb84e6: Loading layer [==================================================>]      32B/32B
    3715ebefa61d: Loading layer [==================================================>]     225B/225B
    05ce1790ce94: Loading layer [==================================================>]      32B/32B
    bcf4e1d78897: Loading layer [==================================================>]  104.4MB/104.4MB
    c831187d3ab8: Loading layer [==================================================>]      32B/32B
    7c6546251399: Loading layer [==================================================>]     270B/270B
    9cdf16081201: Loading layer [==================================================>]      32B/32B
    03e025ff2073: Loading layer [==================================================>]      32B/32B
    7c6b496f6c66: Loading layer [==================================================>]  20.33MB/20.33MB
    41dc0099a0d0: Loading layer [==================================================>]     474B/474B
    aa8896b06297: Loading layer [==================================================>]     510B/510B
    027570efa756: Loading layer [==================================================>]      32B/32B
    4d31e63c6ddd: Loading layer [==================================================>]      32B/32B
    c8949859a26e: Loading layer [==================================================>]      32B/32B
    0a43b51563a4: Loading layer [==================================================>]      32B/32B
    6c7afd475cec: Loading layer [==================================================>]      32B/32B
    1fc7df1b1d0d: Loading layer [==================================================>]      32B/32B
    870fb04a55cb: Loading layer [==================================================>]      32B/32B
    ee7c13dd7c91: Loading layer [==================================================>]  12.43MB/12.43MB
    e3f96f0a2c91: Loading layer [==================================================>]     491B/491B
    aca5442ab421: Loading layer [==================================================>]  11.41MB/11.41MB
    11e2dac9f90d: Loading layer [==================================================>]  2.457kB/2.457kB
    31bd12083f6b: Loading layer [==================================================>]     246B/246B
    c739cafd5b09: Loading layer [==================================================>]      32B/32B
    10dc813fad11: Loading layer [==================================================>]      32B/32B
    55bd542caed2: Loading layer [==================================================>]     893B/893B
    203a2fc2cd6f: Loading layer [==================================================>]      32B/32B
    1bbea5314a3f: Loading layer [==================================================>]      32B/32B
    6ebaa344548a: Loading layer [==================================================>]      32B/32B
    1d2b1c7c2a4b: Loading layer [==================================================>]  22.78MB/22.78MB
    cd6b223a0f0b: Loading layer [==================================================>]      32B/32B
    0e2c96d64680: Loading layer [==================================================>]      32B/32B
    8b2075f7262b: Loading layer [==================================================>]  20.93MB/20.93MB
    1d69a4bb0279: Loading layer [==================================================>]     761B/761B
    3ae48505bd71: Loading layer [==================================================>]      32B/32B
    de570e6825fa: Loading layer [==================================================>]     573B/573B
    7473b038c742: Loading layer [==================================================>]      32B/32B
    1b29b8d0bd4d: Loading layer [==================================================>]     398B/398B
    465777959114: Loading layer [==================================================>]      32B/32B
    d533ca36ace0: Loading layer [==================================================>]  207.3MB/207.3MB
    6741399104ab: Loading layer [==================================================>]  3.613kB/3.613kB
    ddcd091dc0c6: Loading layer [==================================================>]  2.391kB/2.391kB
    a627048f860d: Loading layer [==================================================>]      32B/32B
    8bb71f757b3f: Loading layer [==================================================>]      32B/32B
    user@disp341:~$
    
    user@disp341:~$ docker image ls
    REPOSITORY   TAG       IMAGE ID       CREATED      SIZE
    nextcloud    stable    c926d7c29f4b   4 days ago   1.24GB
    user@disp341:~$ 
    
    

    References

    1. Guide on Manually Downloading Container Images (Docker, Github Packages)
    2. Documentation of OCI Image Media Types
    3. Documentation of Image Manifest Version 2, Schema 1 (application/vnd.docker.distribution.manifest.v1+json)
    4. Documentation of Docker Image Specification v1.0.0