<p>thanks for motivation.
I made a powershell version of it.
Check it out...
With it you can move in dockerhub containers to a restricted docker networks with a windows desktop and an ssh-scp tool to docker machine without root or administrator rights</p>

<p><a href="https://gitlab.com/Jancsoj78/dockerless_docker_downloader" rel="nofollow noreferrer">https://gitlab.com/Jancsoj78/dockerless_docker_downloader</a>
a new hacker tool :)</p>

<pre><code>$image = "ubuntu"
$tag = "latest"
$imageuri = "https://auth.docker.io/token?service=registry.docker.io&amp;scope=repository:library/"+$image+":pull"
$taguri = "https://registry-1.docker.io/v2/library/"+$image+"/manifests/"+$tag
$bloburi = "https://registry-1.docker.io/v2/library/"+$image+"/blobs/sha256:"

#token request
$token = Invoke-WebRequest -Uri $imageuri | ConvertFrom-Json | Select -expand token

#pull image manifest
$blobs = $($(Invoke-Webrequest -Headers @{Authorization="Bearer $token"} -Method GET -Uri $taguri | ConvertFrom-Json | Select -expand fsLayers ) -replace "sha256:" -replace "@{blobSum=" -replace "}")

#download blobs
for ($i=0; $i -lt $blobs.length; $i++) {
    $blobelement =$blobs[$i]

    Invoke-Webrequest -Headers @{Authorization="Bearer $token"} -Method GET -Uri $bloburi$blobelement -OutFile blobtmp

    $source = "blobtmp"
    $newfile = "$blobelement.gz"

#overwrite
Copy-Item $source $newfile -Force -Recurse
#source blobs
ls *.gz
}
#postprocess
echo "copy these .gz to your docker machine"
echo "docker import .gz backward one by one"
echo "lastone with ubuntu:latest"
echo "after docker export and reimport to make a simple layer image"
</code></pre>

CC BY-SA 4.0
2019-03-20T16:37:48.093Z
2019-03-22T19:46:14.327Z
2019-03-22T19:46:14.327Z
Jancsó József
<p>Here is an adapted python script, thus having an OS independant solution: <a href="https://github.com/NotGlop/docker-drag" rel="nofollow noreferrer">docker-drag</a></p>

<p>Use it like that, and it will create a TAR archive that you will be able to import using docker load :</p>

<pre><code>python docker_pull.py hello-world
python docker_pull.py alpine:3.9
python docker_pull.py kalilinux/kali-linux-docker
</code></pre>

CC BY-SA 4.0
2019-04-09T13:16:06.527Z
If you close your github account there's nothing left, if you're ok with sharing it, keep the link but paste the script here also please. You can [edit] your answer, paste your code and then select it and type ctrl+K or the `{}` (code) button in the editor top bar to format it.
Tensibai
CC BY-SA 4.0
2019-04-09T13:19:30.570Z
I would love to paste the code here but it is 100 lines long and I don't think it will be readable. Nevertheless, you can fork the code to save your own copy of the script.
Dutfaz
CC BY-SA 4.0
2019-04-09T13:39:44.230Z
That's not for me, that to have an answer which is self sustaining, if the link break, do you really think this may help someone reading this answer in a few months ? (btw the maximum size for an answer is 30k characters)
Tensibai
CC BY-SA 4.0
2021-04-18T18:24:34.437Z
You could put this in pip. I find it very helpful.
Anoyz
CC BY-SA 4.0
2019-04-09T13:13:10.790Z
2019-04-09T13:17:13.340Z
2019-04-09T13:17:13.340Z
Dutfaz
<p>There is a tool called <a href="https://github.com/containers/skopeo" rel="noreferrer">Skopeo</a> which can retrieve Docker images from a repository and save them in several formats.</p>

<p>For example:</p>

<ol>
<li><p>Download the image and save the layers as a tarball:
<code>skopeo copy docker://ubuntu docker-archive:/tmp/ubuntu.tar:ubuntu</code></p></li>
<li><p>Transfer <code>/tmp/ubuntu.tar</code> to another machine if desired.</p></li>
<li><p>Load the image on a Docker instance which does not have internet connection:
<code>docker load --input /tmp/ubuntu.tar</code></p></li>
</ol>

<p>It is available in CentOS 7 repo with the package name <code>skopeo</code>. There are no Debian or Ubuntu packages at this time (but it is easy to compile).</p>

CC BY-SA 4.0
2020-09-18T23:05:32.007Z
FWIW there are deb packages now: https://github.com/containers/skopeo/blob/master/install.md
Jeremy Davis
CC BY-SA 4.0
2018-11-20T17:20:03.050Z
2018-11-20T17:20:03.050Z
snap
<p>To me it is not completely clear what you are trying to achieve and why the attempts are not a solution for the problem. If I would need to solve this issue I would like @Tensibai and other Q&amp;As indicated, do a docker pull first on a system with internet connectivity, save the docker image, copy it to the machine without internet connectivity, load the image and run it.</p>

<p><strong>Demonstration</strong></p>

<p>There are no images on system A:</p>

<pre><code>userA@systemA ~ $ docker images
REPOSITORY        TAG               IMAGE ID          CREATED             SIZE
userA@systemA ~ $
</code></pre>

<p>Pull an image from dockerhub:</p>

<pre><code>userA@systemA ~ $
docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
bc95e04b23c0: Pull complete 
f3186e650f4e: Pull complete 
9ac7d6621708: Pull complete 
Digest: sha256:b81f317384d7388708a498555c28a7cce778a8f291d90021208b3eba3fe74887
Status: Downloaded newer image for nginx:latest
userA@systemA ~ $ docker images
REPOSITORY        TAG               IMAGE ID            CREATED             SIZE
nginx             latest            9e7424e5dbae        10 days ago         108MB
</code></pre>

<p>Save docker image:</p>

<pre><code>userA@systemA ~ $ docker save nginx -o nginx.tar
</code></pre>

<p>Copy docker image to systemB and load it.</p>

<pre><code>userB@systemB ~ $ docker load -i nginx.tar
cec7521cdf36: Loading layer  58.44MB/58.44MB
350d50e58b6c: Loading layer  53.76MB/53.76MB
63c39cd4a775: Loading layer  3.584kB/3.584kB
Loaded image: nginx:latest
userB@systemB ~ $ docker images
REPOSITORY        TAG               IMAGE ID            CREATED             SIZE
nginx             latest            9e7424e5dbae        10 days ago         108MB
</code></pre>

CC BY-SA 3.0
2017-12-03T15:03:17.830Z
The machine with internet connectivity does not and cannot have Docker installed. The question is asking for a way to download an image *without* using the Docker client. See [my solution](https://devops.stackexchange.com/a/2772/3070).
igal
CC BY-SA 3.0
2017-12-03T13:26:18.993Z
2017-12-03T13:35:59.387Z
2017-12-03T13:35:59.387Z
030
<p>I didn't really understand <a href="https://devops.stackexchange.com/a/6666/28888">Jancsó's</a> way of postprocessing, so I've spent some time to modify his script and here's what I came with:</p>
<p><a href="https://github.com/meetyourturik/dockerless-docker-downloader" rel="nofollow noreferrer">https://github.com/meetyourturik/dockerless-docker-downloader</a></p>
<p>upd: apparently, 'a link only answer...' is something bad, so here's a whole script:</p>
<pre><code># Workaround for SelfSigned Cert an force TLS 1.2
add-type @&quot;
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
            return true;
        }
    }
&quot;@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[System.Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# use 'library/' prefix for 'official' images like postgres 
$image = &quot;atlassian/jira-software&quot; 
$tag = &quot;8.13.2&quot; 

$imageuri = &quot;https://auth.docker.io/token?service=registry.docker.io&amp;scope=repository:${image}:pull&quot; 
$taguri = &quot;https://registry-1.docker.io/v2/${image}/manifests/${tag}&quot;
$bloburi = &quot;https://registry-1.docker.io/v2/${image}/blobs/&quot; 

# generating folder to save image files 
$path = &quot;$image$tag&quot; -replace '[\\/&quot;:*?&lt;&gt;|]'
if (!(test-path $path)) { 
    New-Item -ItemType Directory -Force -Path $path 
} 

# token request 
$token = Invoke-WebRequest -Uri $imageuri | ConvertFrom-Json | Select -expand token 

# getting image manifest 
$headers = @{} 
$headers.add(&quot;Authorization&quot;, &quot;Bearer $token&quot;) 
# this header is needed to get manifest in correct format: https://docs.docker.com/registry/spec/manifest-v2-2/ 
$headers.add(&quot;Accept&quot;, &quot;application/vnd.docker.distribution.manifest.v2+json&quot;) 
$manifest = Invoke-Webrequest -Headers $headers -Method GET -Uri $taguri | ConvertFrom-Json 

# downloading config json 
$configSha = $manifest | Select -expand config | Select -expand digest 
$config = &quot;.\$path\config.json&quot; 
Invoke-Webrequest -Headers @{Authorization=&quot;Bearer $token&quot;} -Method GET -Uri $bloburi$configSha -OutFile $config 

# generating manifest.json 
$manifestJson = @{} 
$manifestJson.add(&quot;Config&quot;, &quot;config.json&quot;) 
$manifestJson.add(&quot;RepoTags&quot;,@(&quot;${image}:${tag}&quot;)) 

# downloading layers 
$layers = $manifest | Select -expand layers | Select -expand digest 
$blobtmp = &quot;.\$path\blobtmp&quot; 

#downloading blobs 
$layersJson = @() 
foreach ($blobelement in $layers) { 
    # making so name doesnt start with 'sha256:' 
    $fileName = &quot;$blobelement.gz&quot; -replace 'sha256:' 
    $newfile = &quot;.\$path\$fileName&quot; 
    $layersJson += @($fileName) 

    # token expired after 5 minutes, so requesting new one for every blob just in case 
    $token = Invoke-WebRequest -Uri $imageuri | ConvertFrom-Json | Select -expand token 
    
    Invoke-Webrequest -Headers @{Authorization=&quot;Bearer $token&quot;} -Method GET -Uri $bloburi$blobelement -OutFile $blobtmp 
    
    Copy-Item $blobtmp $newfile -Force -Recurse 
} 

# removing temporary blob 
Remove-Item $blobtmp 

# saving manifest.json 
$manifestJson.add(&quot;Layers&quot;, $layersJson) 
ConvertTo-Json -Depth 5 -InputObject @($manifestJson) | Out-File -Encoding ascii &quot;.\$path\manifest.json&quot; 

# postprocessing
echo &quot;copy generated folder to your docker machine&quot; 
echo &quot;tar -cvf imagename.tar *&quot; 
echo &quot;docker load &lt; imagename.tar&quot;
</code></pre>
<p>after sctipt downloads blobs and generates config and manifest jsons download it to docker machine and execute two following commands:</p>
<pre><code>tar -cvf imagename.tar *
docker load &lt; imagename.tar
</code></pre>
<p>first creates an archive, 2nd uploads image archive to docker</p>

CC BY-SA 4.0
2021-07-22T19:23:31.987Z
2021-07-24T09:44:43.050Z
2021-07-24T09:44:43.050Z
Ilia Turov
<p>Another tool that could be used to download docker images is <a href="https://github.com/030/p2iwd" rel="nofollow noreferrer">P2IWD</a>.</p>

CC BY-SA 4.0
2022-03-06T13:24:03.963Z
There doesn’t appear to be any code in that repository.
igal
CC BY-SA 4.0
2022-03-06T22:11:42.567Z
@igal version 0.1.0 has been released. It is capable to download docker images from a Nexus3 server and to upload them to another one.
030
CC BY-SA 4.0
2022-03-06T13:11:34.640Z
2022-03-06T13:11:34.640Z
030
<p>You can manually download a docker image with curl by:</p>
<ol>
<li>getting a free auth token</li>
<li>download the manifest</li>
<li>download the layers (blob hashes specified in the manifest)</li>
</ol>
<h1>Example</h1>
<p>For example, I'll show how you can download <a href="https://hub.docker.com/_/hitch" rel="nofollow noreferrer">the <code>hitch</code> package</a> from hub.docker.com.</p>
<h2>Get an Auth Token</h2>
<p>Execute the following command to get an <a href="https://distribution.github.io/distribution/spec/auth/oauth/" rel="nofollow noreferrer">authentication token</a> from Docker Hub within the scope of the '<code>hitch</code>' package's namespace.</p>
<pre><code># get a JSON with an anonymous token
curl -so &quot;token.json&quot; &quot;https://auth.docker.io/token?service=registry.docker.io&amp;scope=&lt;resourcetype&gt;:&lt;component&gt;/&lt;component&gt;:&lt;action&gt;&quot;;

# extract token from JSON
token=$(cat token.json | jq -jr &quot;.token&quot;)
</code></pre>
<blockquote>
<p>ⓘ INFO: If you're like me and wondering where the heck is the OCI spec that defines this <code>/token</code> endpoint for authentication, know that <a href="https://github.com/opencontainers/distribution-spec/issues/240" rel="nofollow noreferrer">it doesn't exist</a> </p>
<p>To learn more about the syntax of this URL and '<code>scope</code>' GET variable, see docker's <a href="https://distribution.github.io/distribution/spec/auth/scope/" rel="nofollow noreferrer">Token Scope Documentation</a>.</p>
</blockquote>
<p>The above commands will get a free/temporary token that you can use in subsequent API calls. If all went well, there will be no output from these commands. Here's an example execution</p>
<pre><code>user@disp7456:~$ curl -so &quot;token.json&quot; &quot;https://auth.docker.io/token?service=registry.docker.io&amp;scope=repository:library/hitch:pull&quot;;
user@disp7456:~$ 

user@disp7456:~$ token=$(cat token.json | jq -jr &quot;.token&quot;)
user@disp7456:~$ 
</code></pre>
<h2>Download the Manifest</h2>
<p>We can download the manifest for the '<code>1.8.0-1</code>' tag of the '<code>hitch</code>' package with the '<code>GET /v2/&lt;name&gt;/manifests/&lt;reference&gt;</code>' API endpoint.</p>
<pre><code>curl -o manifest.json -s -H &quot;Authorization: Bearer ${token}&quot; https://registry-1.docker.io/v2/library/&lt;package_name&gt;/manifests/&lt;tag&gt;
</code></pre>
<p>And here's an example execution that downloads the manifest for the '<code>1.8.0-1</code>' tag of the '<code>hitch</code>' package</p>
<pre><code>
user@disp7456:~$ curl -o manifest.json -s -H &quot;Authorization: Bearer ${token}&quot; https://registry-1.docker.io/v2/library/hitch/manifests/1.8.0-1
user@disp7456:~$ 

user@disp7456:~$ ls
manifest.json
user@disp7456:~$ 
</code></pre>
<h2>Parse the Manifest</h2>
<p>The previous step downloaded a file named '<code>manifest.json</code>'. This '<code>manifest.json</code>' file lists all of the &quot;layers&quot; that make up the image of the '<code>hitch</code>' package.</p>
<p>Each &quot;layer&quot; consists of a tarball and some metadata about the layer in json format. The information that we need to download the layer's tar file is located in the '<code>manifest.json</code>' file. And the metadata about each layer is also in the '<code>manifest.json</code>' file.</p>
<p>The format of the '<code>manifest.json</code>' file is '<code>vnd.docker.distribution.manifest.v1+json</code>', which is defined in <a href="https://github.com/distribution/distribution/blob/bf9f80eaffb0eabc768762bc4ff03ded277ea594/docs/spec/manifest-v2-1.md" rel="nofollow noreferrer">Image Manifest Version 2, Schema 1</a>.</p>
<p>Most importantly, the '<code>manifest.json</code>' file contains two <a href="https://en.wikipedia.org/wiki/Parallel_array" rel="nofollow noreferrer">parallel arrays</a> of the same length:</p>
<ol>
<li>fsLayers[]</li>
<li>history[]</li>
</ol>
<p>Consider this truncated snippet of the manifest for the '<code>hitch</code>' package's '<code>1.8.0-1</code>' tag:</p>
<pre><code>{
   &quot;schemaVersion&quot;: 1,
   &quot;name&quot;: &quot;library/hitch&quot;,
   &quot;tag&quot;: &quot;1.8.0-1&quot;,
   &quot;architecture&quot;: &quot;amd64&quot;,
   &quot;fsLayers&quot;: [
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:3148f4af0a813bcff0a3ed2562aabfb1b596b52ef36eb5eb4d82ce836350b73a&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a0e9543db8c1238572466cf00b55436bc7b7e849f7cb305128f391a94b75c2fc&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4&quot;
      },
      {
         &quot;blobSum&quot;: &quot;sha256:728328ac3bde9b85225b1f0d60f5c149f5635a191f5d8eaeeb00e095d36ef9fd&quot;
      }
   ],
   &quot;history&quot;: [
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;architecture\&quot;:\&quot;amd64\&quot;,\&quot;config\&quot;:{\&quot;Hostname\&quot;:\&quot;\&quot;,\&quot;Domainname\&quot;:\&quot;\&quot;,\&quot;User\&quot;:\&quot;\&quot;,\&quot;AttachStdin\&quot;:false,\&quot;AttachStdout\&quot;:false,\&quot;AttachStderr\&quot;:false,\&quot;ExposedPorts\&quot;:{\&quot;443/tcp\&quot;:{}},\&quot;Tty\&quot;:false,\&quot;OpenStdin\&quot;:false,\&quot;StdinOnce\&quot;:false,\&quot;Env\&quot;:[\&quot;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\&quot;],\&quot;Cmd\&quot;:[],\&quot;Image\&quot;:\&quot;sha256:996009c7d7eb032c9ea750e5decc1f8aedbf4530b892cf4ebc7716e1458f36d9\&quot;,\&quot;Volumes\&quot;:null,\&quot;WorkingDir\&quot;:\&quot;/etc/hitch\&quot;,\&quot;Entrypoint\&quot;:[\&quot;docker-hitch-entrypoint\&quot;],\&quot;OnBuild\&quot;:null,\&quot;Labels\&quot;:null},\&quot;container\&quot;:\&quot;0ff54ee96c4bbfe77da3b2124720ef95c6154d3bc1d3e40a168920dd818367c4\&quot;,\&quot;container_config\&quot;:{\&quot;Hostname\&quot;:\&quot;0ff54ee96c4b\&quot;,\&quot;Domainname\&quot;:\&quot;\&quot;,\&quot;User\&quot;:\&quot;\&quot;,\&quot;AttachStdin\&quot;:false,\&quot;AttachStdout\&quot;:false,\&quot;AttachStderr\&quot;:false,\&quot;ExposedPorts\&quot;:{\&quot;443/tcp\&quot;:{}},\&quot;Tty\&quot;:false,\&quot;OpenStdin\&quot;:false,\&quot;StdinOnce\&quot;:false,\&quot;Env\&quot;:[\&quot;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\&quot;],\&quot;Cmd\&quot;:[\&quot;/bin/sh\&quot;,\&quot;-c\&quot;,\&quot;#(nop) \&quot;,\&quot;CMD []\&quot;],\&quot;Image\&quot;:\&quot;sha256:996009c7d7eb032c9ea750e5decc1f8aedbf4530b892cf4ebc7716e1458f36d9\&quot;,\&quot;Volumes\&quot;:null,\&quot;WorkingDir\&quot;:\&quot;/etc/hitch\&quot;,\&quot;Entrypoint\&quot;:[\&quot;docker-hitch-entrypoint\&quot;],\&quot;OnBuild\&quot;:null,\&quot;Labels\&quot;:{}},\&quot;created\&quot;:\&quot;2024-05-14T05:23:11.666992342Z\&quot;,\&quot;docker_version\&quot;:\&quot;20.10.23\&quot;,\&quot;id\&quot;:\&quot;6703605aae83084affcafb4abcc7c556f0e436c4992ae224f1f58e88242328cb\&quot;,\&quot;os\&quot;:\&quot;linux\&quot;,\&quot;parent\&quot;:\&quot;c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188\&quot;,\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188\&quot;,\&quot;parent\&quot;:\&quot;1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:23:11.581588417Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop)  EXPOSE 443\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240\&quot;,\&quot;parent\&quot;:\&quot;8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:23:11.489285564Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop)  ENTRYPOINT [\\\&quot;docker-hitch-entrypoint\\\&quot;]\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad\&quot;,\&quot;parent\&quot;:\&quot;a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:23:11.403178706Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop) COPY file:1abf3c94dce5dc9f6617dc8d36a6fe6f4f7236189d4819f16cefb54288e80e0d in /usr/local/bin/ \&quot;]}}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5\&quot;,\&quot;parent\&quot;:\&quot;5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:23:11.304477182Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop) WORKDIR /etc/hitch\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f\&quot;,\&quot;parent\&quot;:\&quot;5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:23:11.160227264Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;|5 DISTVER=bullseye PKGCOMMIT=f12ab7958bc4885f3f00311cbca5103d9e6ba794 PKGVER=1 SHASUM=62b3554d668c9d17382415db10898bf661ee76343e4ee364f904457efda6cb1eeee7cb81d7a3897734024812b64b1c0e2dc305605706d81a0c1f6030508bf7e2 SRCVER=1.8.0 /bin/sh -c set -ex;     BASE_PKGS=\\\&quot;apt-utils curl dirmngr dpkg-dev debhelper devscripts equivs fakeroot git gnupg pkg-config\\\&quot;;     export DEBIAN_FRONTEND=noninteractive;     export DEBCONF_NONINTERACTIVE_SEEN=true;     tmpdir=\\\&quot;$(mktemp -d)\\\&quot;;     cd \\\&quot;$tmpdir\\\&quot;;     apt-get update;     apt-get install -y --no-install-recommends $BASE_PKGS;     git clone https://github.com/varnish/pkg-hitch.git;     cd pkg-hitch;     git checkout ${PKGCOMMIT};     rm -rf .git;     curl -Lf https://hitch-tls.org/source/hitch-${SRCVER}.tar.gz -o $tmpdir/orig.tgz;     echo \\\&quot;${SHASUM}  $tmpdir/orig.tgz\\\&quot; | sha512sum -c -;     tar xavf $tmpdir/orig.tgz --strip 1;     sed -i         -e \\\&quot;s/@SRCVER@/${SRCVER}/g\\\&quot;         -e \\\&quot;s/@PKGVER@/${PKGVER:-1}/g\\\&quot;         -e \\\&quot;s/@DISTVER@/$DISTVER/g\\\&quot; debian/changelog;     mk-build-deps --install --tool=\\\&quot;apt-get -o Debug::pkgProblemResolver=yes --yes\\\&quot; debian/control;     sed -i '' debian/hitch*;     dpkg-buildpackage -us -uc -j\\\&quot;$(nproc)\\\&quot;;     apt-get -y purge --auto-remove hitch-build-deps $BASE_PKGS;     apt-get -y --no-install-recommends install ../*.deb;     sed -i 's/daemon = on/daemon = off/' /etc/hitch/hitch.conf;     rm -rf /var/lib/apt/lists/* \\\&quot;$tmpdir\\\&quot;\&quot;]}}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8\&quot;,\&quot;parent\&quot;:\&quot;c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:21:33.061082853Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop)  ARG SHASUM=62b3554d668c9d17382415db10898bf661ee76343e4ee364f904457efda6cb1eeee7cb81d7a3897734024812b64b1c0e2dc305605706d81a0c1f6030508bf7e2\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be\&quot;,\&quot;parent\&quot;:\&quot;24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:21:32.967727298Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop)  ARG PKGCOMMIT=f12ab7958bc4885f3f00311cbca5103d9e6ba794\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d\&quot;,\&quot;parent\&quot;:\&quot;f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:21:32.875807605Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop)  ARG DISTVER=bullseye\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34\&quot;,\&quot;parent\&quot;:\&quot;65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:21:32.781941821Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop)  ARG PKGVER=1\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8\&quot;,\&quot;parent\&quot;:\&quot;863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T05:21:32.682503634Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop)  ARG SRCVER=1.8.0\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270\&quot;,\&quot;parent\&quot;:\&quot;e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T01:28:27.043980081Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop)  CMD [\\\&quot;bash\\\&quot;]\&quot;]},\&quot;throwaway\&quot;:true}&quot;
      },
      {
         &quot;v1Compatibility&quot;: &quot;{\&quot;id\&quot;:\&quot;e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd\&quot;,\&quot;created\&quot;:\&quot;2024-05-14T01:28:26.699066026Z\&quot;,\&quot;container_config\&quot;:{\&quot;Cmd\&quot;:[\&quot;/bin/sh -c #(nop) ADD file:9b38b383dd93169a663eed88edf3f2285b837257ead69dc40ab5ed1fb3f52c35 in / \&quot;]}}&quot;
      }
   ],
   ...
   ]
}
</code></pre>
<p>The sha256sum used to download the blob of the <strong>first layer</strong> is found at the first element of the <code>fsLayers[]</code> array (<strong><code>fsLayers[0]</code></strong>). The metadata about this first layer is found at the first element of the <code>history[]</code> array (<strong><code>history[0]</code></strong>).</p>
<p>The sha256sum used to download the blob of the <strong>second layer</strong> is found at second element of the <code>fsLayers[]</code> array (<strong><code>fsLayers[1]</code></strong>). The metadata about this second layer is found at the second element of the <code>history[]</code> array (<strong><code>history[1]</code></strong>).</p>
<p>Et cetera...</p>
<h2>Download the Layers</h2>
<p>So how do we download each of these layers separately, yet organize them such that we can later import them as a single image into docker? The answer to that lies in the <a href="https://github.com/moby/moby/blob/79910625f0a7aa76590e4362817dda22f28343aa/image/spec/v1.md" rel="nofollow noreferrer">Docker Image Specification v1.0.0</a>.</p>
<p>The above spec provides an example tree of the files:</p>
<blockquote>
<p>For example, here's what the full archive of <code>library/busybox</code> is (displayed in <code>tree</code> format):</p>
<p><br />
<code>.</code><br />
<code>├── 5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e</code><br />
<code>│ ├── VERSION</code><br />
<code>│ ├── json</code><br />
<code>│ └── layer.tar</code><br />
<code>├── a7b8b41220991bfc754d7ad445ad27b7f272ab8b4a2c175b9512b97471d02a8a</code><br />
<code>│ ├── VERSION</code><br />
<code>│ ├── json</code><br />
<code>│ └── layer.tar</code><br />
<code>├── a936027c5ca8bf8f517923169a233e391cbb38469a75de8383b5228dc2d26ceb</code><br />
<code>│ ├── VERSION</code><br />
<code>│ ├── json</code><br />
<code>│ └── layer.tar</code><br />
<code>├── f60c56784b832dd990022afc120b8136ab3da9528094752ae13fe63a2d28dc8c</code><br />
<code>│ ├── VERSION</code><br />
<code>│ ├── json</code><br />
<code>│ └── layer.tar</code><br />
<code>└── repositories</code>\</p>
<p>There are one or more directories named with the ID for each layer in a full image. Each of these directories contains 3 files:</p>
<ul>
<li>`VERSION` - The schema version of the `json` file</li>
<li>`json` - The JSON metadata for an image layer</li>
<li>`layer.tar` - The Tar archive of the filesystem changeset for an image<br />
layer.</li>
</ul>
<p>The content of the <code>VERSION</code> files is simply the semantic version of the JSON metadata schema:<br />
<br />
<code>1.0</code>\</p>
<p>And the <code>repositories</code> file is another JSON file which describes names/tags:</p>
<pre><code>{  
    &quot;busybox&quot;:{  
        &quot;latest&quot;:&quot;5785b62b697b99a5af6cd5d0aabc804d5748abbb6d3d07da5d1d3795f2dcc83e&quot;
    }
}
</code></pre>
<p>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.</p>
</blockquote>
<p>So, as shown in the quote from the spec above, what we need to do is to create a set of directories -- one for each layer -- named after the layer's ID. Each of these layer-specific directories must contain 3 files:</p>
<ol>
<li>The actual tarball of the layer (named '<code>layer.tar</code>'),</li>
<li>The metadata of the layer (in a file literally named '<code>json</code>' -- with no file extension)</li>
<li>A file named '<code>VERSION</code>' whose contents is literally just '<code>1.0</code>'</li>
</ol>
<p>All of the information that we need is in the '<code>manifest.json</code>' file that we already downloaded. Let's just loop through each layer that it defines, download the <code>layer.tar</code> tarball, create the '<code>json</code>' metadata file, and hard-code the '<code>VERSION</code>' file with the following BASH snippet</p>
<pre><code>num_layers=$(cat manifest.json | jq -r &quot;.history | length&quot;)

for ((i = 0 ; i &lt; $num_layers ; i++)); do
    layer_blobSum=$(cat manifest.json | jq -r &quot;.fsLayers[$i].blobSum&quot;)
    layer_metadata=$(cat manifest.json | jq -r &quot;.history[$i].v1Compatibility&quot;)
    layer_id=$(echo $layer_metadata | jq -r &quot;.id&quot;)

    echo $layer_id
    echo $layer_blobSum

    mkdir -p &quot;layers/$layer_id&quot;
    echo &quot;1.0&quot; &gt; &quot;layers/$layer_id/VERSION&quot;
    echo $layer_metadata &gt; &quot;layers/$layer_id/json&quot;
    curl -o &quot;layers/$layer_id/layer.tar&quot; -#LH &quot;Authorization: Bearer ${token}&quot; &quot;https://registry-1.docker.io/v2/library/&lt;package_name&gt;/blobs/${layer_blobSum}&quot;
done
</code></pre>
<p>And here's an example execution that executes the above snippet to download all of the layers onto disk in a set of directories as defined by the <a href="https://github.com/moby/moby/blob/79910625f0a7aa76590e4362817dda22f28343aa/image/spec/v1.md" rel="nofollow noreferrer">Docker Image Specification v1.0.0</a>.</p>
<pre><code>user@disp7456:~$ num_layers=$(cat manifest.json | jq -r &quot;.history | length&quot;)
user@disp7456:~$ 

user@disp7456:~$ for ((i = 0 ; i &lt; $num_layers ; i++)); do
    layer_blobSum=$(cat manifest.json | jq -r &quot;.fsLayers[$i].blobSum&quot;)
    layer_metadata=$(cat manifest.json | jq -r &quot;.history[$i].v1Compatibility&quot;)
    layer_id=$(echo $layer_metadata | jq -r &quot;.id&quot;)

    echo $layer_id
    echo $layer_blobSum

    mkdir -p &quot;layers/$layer_id&quot;
    echo &quot;1.0&quot; &gt; &quot;layers/$layer_id/VERSION&quot;
    echo $layer_metadata &gt; &quot;layers/$layer_id/json&quot;
    curl -o &quot;layers/$layer_id/layer.tar&quot; -#LH &quot;Authorization: Bearer ${token}&quot; &quot;https://registry-1.docker.io/v2/library/hitch/blobs/${layer_blobSum}&quot;
done
6703605aae83084affcafb4abcc7c556f0e436c4992ae224f1f58e88242328cb
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad
sha256:3148f4af0a813bcff0a3ed2562aabfb1b596b52ef36eb5eb4d82ce836350b73a
################################################################ 100.0%
a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f
sha256:a0e9543db8c1238572466cf00b55436bc7b7e849f7cb305128f391a94b75c2fc
################################################################ 100.0%
5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd
sha256:728328ac3bde9b85225b1f0d60f5c149f5635a191f5d8eaeeb00e095d36ef9fd
################################################################ 100.0%
user@disp7456:~$ 

user@disp7456:~$ tree layers/
layers/
├── 1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 6703605aae83084affcafb4abcc7c556f0e436c4992ae224f1f58e88242328cb
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd
│   ├── json
│   ├── layer.tar
│   └── VERSION
└── f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34
    ├── json
    ├── layer.tar
    └── VERSION

14 directories, 39 files
user@disp7456:~$ 
</code></pre>
<p>Finally, besides these layer-specific dirs, we need one additional file (named simply '<code>repository</code>', with no file extension) at the same height as these dirs. As defined by the <a href="https://github.com/moby/moby/blob/79910625f0a7aa76590e4362817dda22f28343aa/image/spec/v1.md" rel="nofollow noreferrer">Docker Image Specification v1.0.0</a>, this file should state the name &amp; tag of the image, and it points to the first layer of the image.</p>
<p>Note the <code>0th</code> item in the <code>history[]</code> array is the first layer of the image, so we can create this file with the following command</p>
<pre><code>start_image=$(cat manifest.json | jq -r &quot;.history[0].v1Compatibility&quot;)
start_image_id=$(echo $start_image | jq -r &quot;.id&quot;)

cat &gt; layers/repositories &lt;&lt;EOF
{
    &quot;&lt;image_name&gt;&quot;: { &quot;stable&quot;: &quot;$start_image_id&quot; }
}
EOF
</code></pre>
<p>And here's an example execution to create our '<code>repository</code>' file for the '<code>hitch</code>' package.</p>
<pre><code>user@disp7456:~$ start_image=$(cat manifest.json | jq -r &quot;.history[0].v1Compatibility&quot;)
user@disp7456:~$ 

user@disp7456:~$ start_image_id=$(echo $start_image | jq -r &quot;.id&quot;)
user@disp7456:~$ 

user@disp7456:~$ cat &gt; layers/repositories &lt;&lt;EOF
{
    &quot;hitch&quot;: { &quot;stable&quot;: &quot;$start_image_id&quot; }
}
EOF
user@disp7456:~$

user@disp7456:~$ ls layers
1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240
24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d
5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8
5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f
65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8
6703605aae83084affcafb4abcc7c556f0e436c4992ae224f1f58e88242328cb
863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270
8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad
a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5
c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be
c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188
e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd
f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34
repositories
user@disp7456:~$

user@disp7456:~$ cat layers/repositories
{
    &quot;hitch&quot;: { &quot;stable&quot;: &quot;6703605aae83084affcafb4abcc7c556f0e436c4992ae224f1f58e88242328cb&quot; }
}
user@disp7456:~$ 
</code></pre>
<p>Your '<code>layers/</code>' directory should now be prepared-to-spec for importing the entire image into docker.</p>
<p>For reference, here's the contents of just one of the layers:</p>
<pre><code>user@disp7456:~$ ls layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/
json  layer.tar  VERSION
user@disp7456:~$ 

user@disp7456:~$ cat layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/VERSION 
1.0
user@disp7456:~$ 

user@disp7456:~$ cat layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/json 
{&quot;id&quot;:&quot;8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad&quot;,&quot;parent&quot;:&quot;a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5&quot;,&quot;created&quot;:&quot;2024-05-14T05:23:11.403178706Z&quot;,&quot;container_config&quot;:{&quot;Cmd&quot;:[&quot;/bin/sh -c #(nop) COPY file:1abf3c94dce5dc9f6617dc8d36a6fe6f4f7236189d4819f16cefb54288e80e0d in /usr/local/bin/ &quot;]}}
user@disp7456:~$ 

user@disp7456:~$ sha256sum layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/layer.tar 
3148f4af0a813bcff0a3ed2562aabfb1b596b52ef36eb5eb4d82ce836350b73a  layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/layer.tar
user@disp7456:~$ 

user@disp7456:~$ tar -tvf layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/layer.tar 
drwxr-xr-x 0/0               0 2024-05-12 19:00 usr/
drwxr-xr-x 0/0               0 2024-05-12 19:00 usr/local/
drwxr-xr-x 0/0               0 2024-05-14 00:23 usr/local/bin/
-rwxrwxr-x 0/0             319 2024-05-14 00:21 usr/local/bin/docker-hitch-entrypoint
user@disp7456:~$ 
</code></pre>
<h2>Load the Image</h2>
<p>Finally, you can copy the whole <code>layers</code> directory into your restricted environment and then load the layers as one image into docker with <code>docker image load</code></p>
<pre><code>tar -cC layers . | docker image load
</code></pre>
<p>Here's an example execution</p>
<pre><code>user@disp7456:~$ docker image ls
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE
user@disp7456:~$ 

user@disp7456:~$ tar -cC layers . | docker load
e00e363f3a25: Loading layer [==================================================&gt;]  31.43MB/31.43MB
863a608d086b: Loading layer [==================================================&gt;]      32B/32B
65c7b6d17437: Loading layer [==================================================&gt;]      32B/32B
f0d07a99d7d1: Loading layer [==================================================&gt;]      32B/32B
24e7aee556d6: Loading layer [==================================================&gt;]      32B/32B
c03ad9230005: Loading layer [==================================================&gt;]      32B/32B
5a12a2c67ff9: Loading layer [==================================================&gt;]      32B/32B
5a78b0e89bba: Loading layer [==================================================&gt;]  1.573MB/1.573MB
a8d831445814: Loading layer [==================================================&gt;]      32B/32B
8f914c821cbe: Loading layer [==================================================&gt;]     415B/415B
1d2af5a156bb: Loading layer [==================================================&gt;]      32B/32B
c48ca3d95161: Loading layer [==================================================&gt;]      32B/32B
6703605aae83: Loading layer [==================================================&gt;]      32B/32B
user@disp7456:~$ 

user@disp7456:~$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
hitch        stable    f07eadb841be   3 weeks ago   85.1MB
user@disp7456:~$ 
</code></pre>
<h1>Attribution</h1>
<p>The example above was copied from <a href="https://tech.michaelaltfield.net/2024/09/03/container-download-curl-wget/" rel="nofollow noreferrer">Manually Downloading Container Images (Docker, Github Packages)</a></p>

CC BY-SA 4.0
2024-09-03T15:30:44.203Z
2024-09-03T15:30:44.203Z
Michael Altfield
<p>If your company has nexus or a similar repository, they may already have set up nexus to pull in what you need. You could try adding your repo's host/ip in front of the relative docker hub path.</p>

<p>Examples:</p>

<pre><code>docker pull nexus.example.com:18443/nginx
docker pull nexus.example.com:18443/selenium/node-chrome
docker pull nexus.example.com:18443/postgres:9.4
</code></pre>

CC BY-SA 4.0
2020-02-10T20:56:11.603Z
2020-02-10T20:56:11.603Z
ScrappyDev
<p>It turned out that the <a href="https://mobyproject.org/" rel="noreferrer">Moby Project</a> has a shell script on the <a href="https://github.com/moby/moby/" rel="noreferrer">Moby Github</a> which can download images from <a href="https://hub.docker.com/" rel="noreferrer">Docker Hub</a> in a format that can be imported into Docker:</p>
<ul>
<li><a href="https://raw.githubusercontent.com/moby/moby/master/contrib/download-frozen-image-v2.sh" rel="noreferrer">download-frozen-image-v2.sh</a></li>
</ul>
<p>The usage syntax for the script is given by the following:</p>
<pre class="lang-bash prettyprint-override"><code>download-frozen-image-v2.sh target_dir image[:tag][@digest] ...
</code></pre>
<p>The image can then be imported with <code>tar</code> and <code>docker load</code>:</p>
<pre class="lang-bash prettyprint-override"><code>tar -cC 'target_dir' . | docker load
</code></pre>
<p>To verify that the script works as expected, I downloaded an Ubuntu image from <a href="https://hub.docker.com/" rel="noreferrer">Docker Hub</a> and loaded it into Docker:</p>
<pre class="lang-bash prettyprint-override"><code>user@host:~$ bash download-frozen-image-v2.sh ubuntu ubuntu:latest
user@host:~$ tar -cC 'ubuntu' . | docker load
user@host:~$ docker run --rm -ti ubuntu bash
root@1dd5e62113b9:/#
</code></pre>
<p>In practice I would have to first copy the data from the internet client (which does <em>not</em> have Docker installed) to the target/destination machine (which <em>does</em> have Docker installed):</p>
<pre class="lang-bash prettyprint-override"><code>user@nodocker:~$ bash download-frozen-image-v2.sh ubuntu ubuntu:latest
user@nodocker:~$ tar -C 'ubuntu' -cf 'ubuntu.tar' .
user@nodocker:~$ scp ubuntu.tar user@hasdocker:~
</code></pre>
<p>and then load and use the image on the target host:</p>
<pre class="lang-bash prettyprint-override"><code>user@hasdocker:~ docker load ubuntu.tar
user@hasdocker:~ docker run --rm -ti ubuntu bash
root@1dd5e62113b9:/#
</code></pre>

CC BY-SA 3.0
2017-12-03T15:11:37.627Z
`The machine with internet connectivity does not and cannot have Docker installed`. but you apply `docker load`
030
CC BY-SA 3.0
2017-12-03T15:13:05.310Z
@030 Just to test/demonstrate that the script works and that the download image data can be imported into Docker. In practice I would first have to copy the data to a machine with Docker installed.
igal
CC BY-SA 3.0
2017-12-03T15:13:41.220Z
Perhaps you could add that part for clarification
030
CC BY-SA 3.0
2017-12-03T15:20:27.787Z
@030 I added an example session illustrating what the workflow would look like in practice.
igal
CC BY-SA 4.0
2017-12-03T14:56:17.250Z
2022-03-08T10:47:11.057Z
2022-03-08T10:47:11.057Z
igal
<p>I want to manually download a Docker Image from <a href="https://hub.docker.com/" rel="noreferrer">Docker Hub</a>. More specifically, I want to download a Docker Image from Docker Hub on a machine in a restricted environment which does not (and cannot) have the Docker client software installed. I would have thought that this would be possible using the <a href="https://docs.docker.com/v1.7/docker/reference/api/docker-io_api/" rel="noreferrer">official API</a>, but this does not appear to be the case - see the following discussion:</p>

<ul>
<li><a href="https://github.com/docker/distribution/issues/1016" rel="noreferrer">Fetch docker images without docker command. e.g. with wget</a></li>
</ul>

<p>Is it really the case that the API doesn't support downloading images? Is there a way to work around this?</p>

<hr>

<p><strong>UPDATE 1:</strong></p>

<p>I came across the following ServerFault post:</p>

<ul>
<li><a href="https://serverfault.com/questions/701248/downloading-docker-image-for-transfer-to-non-internet-connected-machine">Downloading docker image for transfer to non-internet-connected machine</a></li>
</ul>

<p>The <a href="https://serverfault.com/a/718470/264818">accepted solution</a> uses the <code>docker save</code> command, which doesn't help in my situation. But <a href="https://serverfault.com/a/823571/264818">another solution posted there</a> cites the following StackOverflow post:</p>

<ul>
<li><a href="https://stackoverflow.com/questions/22381442/pulling-docker-images/25072118">Pulling docker images</a></li>
</ul>

<p><a href="https://stackoverflow.com/a/25072118/4451345">One of the solutions there</a> refers to a command-line tool called <a href="https://github.com/samalba/docker-registry-debug" rel="noreferrer">docker-registry-debug</a> which, among other things, can generate a <code>curl</code> command for downloading an image. Here is what I got:</p>

<pre class="lang-none prettyprint-override"><code>user@host:~$ docker-registry-debug curlme docker ubuntu

# Reading user/passwd from env var "USER_CREDS"
# No password provided, disabling auth
# Getting token from https://index.docker.io
# Got registry endpoint from the server: https://registry-1.docker.io
# Got token: signature=1234567890abcde1234567890abcde1234567890,repository="library/docker",access=read
curl -i --location-trusted -I -X GET -H "Authorization: Token signature=1234567890abcde1234567890abcde1234567890,repository="library/docker",access=read" https://registry-1.docker.io/v1/images/ubuntu/layer

user@host:~$ curl \
-i --location-trusted -I -X GET \
-H "Authorization: Token signature=1234567890abcde1234567890abcde1234567890,repository="library/docker",access=read" 

https://registry-1.docker.io/v1/images/ubuntu/layer
HTTP/1.1 404 NOT FOUND
Server: gunicorn/18.0
Date: Wed, 29 Nov 2017 01:00:00 GMT
Expires: -1
Content-Type: application/json
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 29
X-Docker-Registry-Version: 0.8.15
X-Docker-Registry-Config: common
Strict-Transport-Security: max-age=31536000
</code></pre>

<p>So unfortunately it looks like the <code>curl</code> command generated does not work.</p>

<hr>

<p><strong>UPDATE 2:</strong></p>

<p>It looks like I'm able to download layer blobs from Docker Hub. Here is how I'm currently going about it.</p>

<p>Get an authorization token:</p>

<pre><code>user@host:~$ export TOKEN=\
"$(curl \
--silent \
--header 'GET' \
"https://auth.docker.io/token?service=registry.docker.io&amp;scope=repository:library/ubuntu:pull" \
| jq -r '.token' \
)"
</code></pre>

<p>Pull an image manifest:</p>

<pre><code>user@host:~$ curl \
--silent \
--request 'GET' \
--header "Authorization: Bearer ${TOKEN}" \
'https://registry-1.docker.io/v2/library/ubuntu/manifests/latest' \
| jq '.'
</code></pre>

<p>Pull an image manifest and extract the blob sums:</p>

<pre><code>user@host:~$ curl \
--silent \
--request 'GET' \
--header "Authorization: Bearer ${TOKEN}" \
'https://registry-1.docker.io/v2/library/ubuntu/manifests/latest' \
| jq -r '.fsLayers[].blobSum'

sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:be588e74bd348ce48bb7161350f4b9d783c331f37a853a80b0b4abc0a33c569e
sha256:e4ce6c3651b3a090bb43688f512f687ea6e3e533132bcbc4a83fb97e7046cea3
sha256:421e436b5f80d876128b74139531693be9b4e59e4f1081c9a3c379c95094e375
sha256:4c7380416e7816a5ab1f840482c9c3ca8de58c6f3ee7f95e55ad299abbfe599f
sha256:660c48dd555dcbfdfe19c80a30f557ac57a15f595250e67bfad1e5663c1725bb
</code></pre>

<p>Download a single layer blob and write it to a file:</p>

<pre><code>user@host:~$ BLOBSUM=\
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"

user@host:~$ curl \
--silent \
--location \
--request GET \
--header "Authorization: Bearer ${TOKEN}" \
"https://registry-1.docker.io/v2/library/ubuntu/blobs/${BLOBSUM}" \
&gt; "${BLOBSUM/*:/}.gz"
</code></pre>

<p>Write all of the blob sums to a file:</p>

<pre><code>user@host:~$ curl \
--silent \
--request 'GET' \
--header "Authorization: Bearer ${TOKEN}" \
'https://registry-1.docker.io/v2/library/ubuntu/manifests/latest' \
| jq -r '.fsLayers[].blobSum' &gt; ubuntu-blobsums.txt
</code></pre>

<p>Download all of the layer blobs from the manifest:</p>

<pre><code>user@host:~$ while read BLOBSUM; do
curl \
--silent \
--location \
--request 'GET' \
--header "Authorization: Bearer ${TOKEN}" \
"https://registry-1.docker.io/v2/library/ubuntu/blobs/${BLOBSUM}" \
&gt; "${BLOBSUM/*:/}.gz"; \
done &lt; blobsums.txt
</code></pre>

<p>Now I have a bunch of layer blobs and I need to recombine them into an image - I think.</p>

<hr>

<p>Related Links:</p>

<ul>
<li><p><a href="https://forums.docker.com/t/docker-hub-api-retrieve-images/23046" rel="noreferrer">Docker Community Forums: <strong>Docker Hub API retrieve images</strong></a></p></li>
<li><p><a href="https://forums.docker.com/t/manual-download-of-docker-hub-images/26073" rel="noreferrer">Docker Community Forums: <strong>Manual download of Docker Hub images</strong></a></p></li>
<li><p><a href="https://github.com/docker/distribution/issues/1016" rel="noreferrer">Docker Issue #1016: <strong>Fetch docker images without docker command. e.g. with wget</strong></a></p></li>
<li><p><a href="https://serverfault.com/questions/701248/downloading-docker-image-for-transfer-to-non-internet-connected-machine">ServerFault: <strong>Downloading docker image for transfer to non-internet-connected machine</strong></a></p></li>
<li><p><a href="https://stackoverflow.com/questions/22381442/pulling-docker-images/">StackOverflow: <strong>Downloading docker image for transfer to non-internet-connected machine</strong></a></p></li>
<li><p><a href="https://stackoverflow.com/questions/37905763/how-to-download-docker-images-without-using-pull-command">StackOverflow: <strong>How to download docker images without using pull command?</strong></a></p></li>
<li><p><a href="https://stackoverflow.com/questions/40402513/is-there-a-way-to-download-docker-hub-images-whitout-docker-pull-for-a-machine">StackOverflow: <strong>Is there a way to download docker hub images without “docker pull” for a machine with out Internet access?</strong></a></p></li>
<li><p><a href="https://stackoverflow.com/questions/34198392/docker-official-registry-docker-hub-url">StackOverflow: <strong>Docker official registry (Docker Hub) URL</strong></a></p></li>
</ul>

CC BY-SA 3.0
2017-11-30T11:06:03.160Z
"More specifically, I want to download a Docker Image from Docker Hub on a machine in a restricted environment which does not (and cannot) have the Docker client software installed." => What's the point to have the images on this machine then ? (easier workaround is using a pivot host, one where you acn docker pull from dockerhub and docker save/ docker push to an internal registry after)
Tensibai
CC BY-SA 3.0
2017-11-30T13:46:04.853Z
@Tensibai To copy it to another machine that *does* have Docker but *doesn't* have internet access.
igal
CC BY-SA 3.0
2017-11-30T19:04:00.370Z
Did you had a look at the docker pull code? It sounds the way to go to build something like this from basic http calls
Tensibai
CC BY-SA 3.0
2017-11-30T20:57:39.843Z
@Tensibai I think I figured it out. I also think I got a solution from the Docker community. I'll come back and post the solution later today.
igal
CC BY-SA 3.0
2017-12-03T15:04:26.180Z
@Tensibai I posted [a solution](https://devops.stackexchange.com/a/2772/3070) with a shell script that solves the problem.
igal
CC BY-SA 4.0
2019-03-23T12:43:17.253Z
Perhaps it would be easier to give the docker host Internet access by using the other as a proxy
Marged
CC BY-SA 4.0
2019-03-23T13:00:10.437Z
@Marged That wasn't an option. The server was configured in such a way that it could not easily be used as a proxy.
igal
<docker><dockerhub>
Downloading Docker Images from Docker Hub without using Docker
igal
# Downloading Docker Images from Docker Hub without using Docker

(Asked by: username_0 on 2017-11-29)

I want to manually download a Docker Image from Docker Hub. More specifically, I want to download a Docker Image from Docker Hub on a machine in a restricted environment which does not (and cannot) have the Docker client software installed. I would have thought that this would be possible using the official API, but this does not appear to be the case - see the following discussion:

Fetch docker images without docker command. e.g. with wget

Is it really the case that the API doesn't support downloading images? Is there a way to work around this?

UPDATE 1:
I came across the following ServerFault post:

Downloading docker image for transfer to non-internet-connected machine

The accepted solution uses the docker save command, which doesn't help in my situation. But another solution posted there cites the following StackOverflow post:

Pulling docker images

One of the solutions there refers to a command-line tool called docker-registry-debug which, among other things, can generate a curl command for downloading an image. Here is what I got:
user@host:~$ docker-registry-debug curlme docker ubuntu

# Reading user/passwd from env var "USER_CREDS"
# No password provided, disabling auth
# Getting token from https://index.docker.io
# Got registry endpoint from the server: https://registry-1.docker.io
# Got token: signature=1234567890abcde1234567890abcde1234567890,repository="library/docker",access=read
curl -i --location-trusted -I -X GET -H "Authorization: Token signature=1234567890abcde1234567890abcde1234567890,repository="library/docker",access=read" https://registry-1.docker.io/v1/images/ubuntu/layer

user@host:~$ curl \
-i --location-trusted -I -X GET \
-H "Authorization: Token signature=1234567890abcde1234567890abcde1234567890,repository="library/docker",access=read" 

https://registry-1.docker.io/v1/images/ubuntu/layer
HTTP/1.1 404 NOT FOUND
Server: gunicorn/18.0
Date: Wed, 29 Nov 2017 01:00:00 GMT
Expires: -1
Content-Type: application/json
Pragma: no-cache
Cache-Control: no-cache
Content-Length: 29
X-Docker-Registry-Version: 0.8.15
X-Docker-Registry-Config: common
Strict-Transport-Security: max-age=31536000

So unfortunately it looks like the curl command generated does not work.

UPDATE 2:
It looks like I'm able to download layer blobs from Docker Hub. Here is how I'm currently going about it.
Get an authorization token:
user@host:~$ export TOKEN=\
"$(curl \
--silent \
--header 'GET' \
"https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/ubuntu:pull" \
| jq -r '.token' \
)"

Pull an image manifest:
user@host:~$ curl \
--silent \
--request 'GET' \
--header "Authorization: Bearer ${TOKEN}" \
'https://registry-1.docker.io/v2/library/ubuntu/manifests/latest' \
| jq '.'

Pull an image manifest and extract the blob sums:
user@host:~$ curl \
--silent \
--request 'GET' \
--header "Authorization: Bearer ${TOKEN}" \
'https://registry-1.docker.io/v2/library/ubuntu/manifests/latest' \
| jq -r '.fsLayers[].blobSum'

sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
sha256:be588e74bd348ce48bb7161350f4b9d783c331f37a853a80b0b4abc0a33c569e
sha256:e4ce6c3651b3a090bb43688f512f687ea6e3e533132bcbc4a83fb97e7046cea3
sha256:421e436b5f80d876128b74139531693be9b4e59e4f1081c9a3c379c95094e375
sha256:4c7380416e7816a5ab1f840482c9c3ca8de58c6f3ee7f95e55ad299abbfe599f
sha256:660c48dd555dcbfdfe19c80a30f557ac57a15f595250e67bfad1e5663c1725bb

Download a single layer blob and write it to a file:
user@host:~$ BLOBSUM=\
"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"

user@host:~$ curl \
--silent \
--location \
--request GET \
--header "Authorization: Bearer ${TOKEN}" \
"https://registry-1.docker.io/v2/library/ubuntu/blobs/${BLOBSUM}" \
> "${BLOBSUM/*:/}.gz"

Write all of the blob sums to a file:
user@host:~$ curl \
--silent \
--request 'GET' \
--header "Authorization: Bearer ${TOKEN}" \
'https://registry-1.docker.io/v2/library/ubuntu/manifests/latest' \
| jq -r '.fsLayers[].blobSum' > ubuntu-blobsums.txt

Download all of the layer blobs from the manifest:
user@host:~$ while read BLOBSUM; do
curl \
--silent \
--location \
--request 'GET' \
--header "Authorization: Bearer ${TOKEN}" \
"https://registry-1.docker.io/v2/library/ubuntu/blobs/${BLOBSUM}" \
> "${BLOBSUM/*:/}.gz"; \
done < blobsums.txt

Now I have a bunch of layer blobs and I need to recombine them into an image - I think.

Related Links:

Docker Community Forums: Docker Hub API retrieve images
Docker Community Forums: Manual download of Docker Hub images
Docker Issue #1016: Fetch docker images without docker command. e.g. with wget
ServerFault: Downloading docker image for transfer to non-internet-connected machine
StackOverflow: Downloading docker image for transfer to non-internet-connected machine
StackOverflow: How to download docker images without using pull command?
StackOverflow: Is there a way to download docker hub images without “docker pull” for a machine with out Internet access?
StackOverflow: Docker official registry (Docker Hub) URL

**Tags:** docker, dockerhub

> **Comments:**
> **username_1**: "More specifically, I want to download a Docker Image from Docker Hub on a machine in a restricted environment which does not (and cannot) have the Docker client software installed." => What's the point to have the images on this machine then ? (easier workaround is using a pivot host, one where you acn docker pull from dockerhub and docker save/ docker push to an internal registry after)  
> **username_0**: @username_1 To copy it to another machine that *does* have Docker but *doesn't* have internet access.  
> **username_1**: Did you had a look at the docker pull code? It sounds the way to go to build something like this from basic http calls  
> **username_0**: @username_1 I think I figured it out. I also think I got a solution from the Docker community. I'll come back and post the solution later today.  
> **username_0**: @username_1 I posted [a solution](https://devops.stackexchange.com/a/2772/3070) with a shell script that solves the problem.  
> **username_2**: Perhaps it would be easier to give the docker host Internet access by using the other as a proxy  
> **username_0**: @username_2 That wasn't an option. The server was configured in such a way that it could not easily be used as a proxy.  

---

## 9 answers

### Answer by username_0 on 2017-12-03. Score: 56 | Accepted answer

It turned out that the Moby Project has a shell script on the Moby Github which can download images from Docker Hub in a format that can be imported into Docker:

download-frozen-image-v2.sh

The usage syntax for the script is given by the following:
download-frozen-image-v2.sh target_dir image[:tag][@digest] ...

The image can then be imported with tar and docker load:
tar -cC 'target_dir' . | docker load

To verify that the script works as expected, I downloaded an Ubuntu image from Docker Hub and loaded it into Docker:
user@host:~$ bash download-frozen-image-v2.sh ubuntu ubuntu:latest
user@host:~$ tar -cC 'ubuntu' . | docker load
user@host:~$ docker run --rm -ti ubuntu bash
root@1dd5e62113b9:/#

In practice I would have to first copy the data from the internet client (which does not have Docker installed) to the target/destination machine (which does have Docker installed):
user@nodocker:~$ bash download-frozen-image-v2.sh ubuntu ubuntu:latest
user@nodocker:~$ tar -C 'ubuntu' -cf 'ubuntu.tar' .
user@nodocker:~$ scp ubuntu.tar user@hasdocker:~

and then load and use the image on the target host:
user@hasdocker:~ docker load ubuntu.tar
user@hasdocker:~ docker run --rm -ti ubuntu bash
root@1dd5e62113b9:/#

> **Comments:**
> **username_8**: `The machine with internet connectivity does not and cannot have Docker installed`. but you apply `docker load`  
> **username_0**: @username_8 Just to test/demonstrate that the script works and that the download image data can be imported into Docker. In practice I would first have to copy the data to a machine with Docker installed.  
> **username_8**: Perhaps you could add that part for clarification  
> **username_0**: @username_8 I added an example session illustrating what the workflow would look like in practice.  

---

### Answer by username_6 on 2018-11-20. Score: 20

There is a tool called [Skopeo](https://github.com/containers/skopeo) which can retrieve Docker images from a repository and save them in several formats.

For example:

1. Download the image and save the layers as a tarball:
   `skopeo copy docker://ubuntu docker-archive:/tmp/ubuntu.tar:ubuntu`
2. Transfer `/tmp/ubuntu.tar` to another machine if desired.
3. Load the image on a Docker instance which does not have internet connection:
   `docker load --input /tmp/ubuntu.tar`

It is available in CentOS 7 repo with the package name `skopeo`. There are no Debian or Ubuntu packages at this time (but it is easy to compile).

> **Comments:**
> **username_7**: FWIW there are deb packages now: https://github.com/containers/skopeo/blob/master/install.md  

---

### Answer by username_3 on 2019-03-20. Score: 5

thanks for motivation.
I made a powershell version of it.
Check it out...
With it you can move in dockerhub containers to a restricted docker networks with a windows desktop and an ssh-scp tool to docker machine without root or administrator rights
https://gitlab.com/Jancsoj78/dockerless_docker_downloader
a new hacker tool :)
$image = "ubuntu"
$tag = "latest"
$imageuri = "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/"+$image+":pull"
$taguri = "https://registry-1.docker.io/v2/library/"+$image+"/manifests/"+$tag
$bloburi = "https://registry-1.docker.io/v2/library/"+$image+"/blobs/sha256:"

#token request
$token = Invoke-WebRequest -Uri $imageuri | ConvertFrom-Json | Select -expand token

#pull image manifest
$blobs = $($(Invoke-Webrequest -Headers @{Authorization="Bearer $token"} -Method GET -Uri $taguri | ConvertFrom-Json | Select -expand fsLayers ) -replace "sha256:" -replace "@{blobSum=" -replace "}")

#download blobs
for ($i=0; $i -lt $blobs.length; $i++) {
    $blobelement =$blobs[$i]

    Invoke-Webrequest -Headers @{Authorization="Bearer $token"} -Method GET -Uri $bloburi$blobelement -OutFile blobtmp

    $source = "blobtmp"
    $newfile = "$blobelement.gz"

#overwrite
Copy-Item $source $newfile -Force -Recurse
#source blobs
ls *.gz
}
#postprocess
echo "copy these .gz to your docker machine"
echo "docker import .gz backward one by one"
echo "lastone with ubuntu:latest"
echo "after docker export and reimport to make a simple layer image"


---

### Answer by username_4 on 2019-04-09. Score: 3

Here is an adapted python script, thus having an OS independant solution: docker-drag
Use it like that, and it will create a TAR archive that you will be able to import using docker load :
python docker_pull.py hello-world
python docker_pull.py alpine:3.9
python docker_pull.py kalilinux/kali-linux-docker

> **Comments:**
> **username_1**: If you close your github account there's nothing left, if you're ok with sharing it, keep the link but paste the script here also please. You can [edit] your answer, paste your code and then select it and type ctrl+K or the `{}` (code) button in the editor top bar to format it.  
> **username_4**: I would love to paste the code here but it is 100 lines long and I don't think it will be readable. Nevertheless, you can fork the code to save your own copy of the script.  
> **username_1**: That's not for me, that to have an answer which is self sustaining, if the link break, do you really think this may help someone reading this answer in a few months ? (btw the maximum size for an answer is 30k characters)  
> **username_5**: You could put this in pip. I find it very helpful.  

---

### Answer by username_8 on 2017-12-03. Score: 1

To me it is not completely clear what you are trying to achieve and why the attempts are not a solution for the problem. If I would need to solve this issue I would like @username_1 and other Q&As indicated, do a docker pull first on a system with internet connectivity, save the docker image, copy it to the machine without internet connectivity, load the image and run it.
Demonstration
There are no images on system A:
userA@systemA ~ $ docker images
REPOSITORY        TAG               IMAGE ID          CREATED             SIZE
userA@systemA ~ $

Pull an image from dockerhub:
userA@systemA ~ $
docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
bc95e04b23c0: Pull complete 
f3186e650f4e: Pull complete 
9ac7d6621708: Pull complete 
Digest: sha256:b81f317384d7388708a498555c28a7cce778a8f291d90021208b3eba3fe74887
Status: Downloaded newer image for nginx:latest
userA@systemA ~ $ docker images
REPOSITORY        TAG               IMAGE ID            CREATED             SIZE
nginx             latest            9e7424e5dbae        10 days ago         108MB

Save docker image:
userA@systemA ~ $ docker save nginx -o nginx.tar

Copy docker image to systemB and load it.
userB@systemB ~ $ docker load -i nginx.tar
cec7521cdf36: Loading layer  58.44MB/58.44MB
350d50e58b6c: Loading layer  53.76MB/53.76MB
63c39cd4a775: Loading layer  3.584kB/3.584kB
Loaded image: nginx:latest
userB@systemB ~ $ docker images
REPOSITORY        TAG               IMAGE ID            CREATED             SIZE
nginx             latest            9e7424e5dbae        10 days ago         108MB

> **Comments:**
> **username_0**: The machine with internet connectivity does not and cannot have Docker installed. The question is asking for a way to download an image *without* using the Docker client. See [my solution](https://devops.stackexchange.com/a/2772/3070).  

---

### Answer by username_9 on 2021-07-22. Score: 1

I didn't really understand Jancsó's way of postprocessing, so I've spent some time to modify his script and here's what I came with:
https://github.com/meetyourturik/dockerless-docker-downloader
upd: apparently, 'a link only answer...' is something bad, so here's a whole script:
# Workaround for SelfSigned Cert an force TLS 1.2
add-type @"
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    public class TrustAllCertsPolicy : ICertificatePolicy {
        public bool CheckValidationResult(
        ServicePoint srvPoint, X509Certificate certificate,
        WebRequest request, int certificateProblem) {
            return true;
        }
    }
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
[System.Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# use 'library/' prefix for 'official' images like postgres 
$image = "atlassian/jira-software" 
$tag = "8.13.2" 

$imageuri = "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${image}:pull" 
$taguri = "https://registry-1.docker.io/v2/${image}/manifests/${tag}"
$bloburi = "https://registry-1.docker.io/v2/${image}/blobs/" 

# generating folder to save image files 
$path = "$image$tag" -replace '[\\/":*?<>|]'
if (!(test-path $path)) { 
    New-Item -ItemType Directory -Force -Path $path 
} 

# token request 
$token = Invoke-WebRequest -Uri $imageuri | ConvertFrom-Json | Select -expand token 

# getting image manifest 
$headers = @{} 
$headers.add("Authorization", "Bearer $token") 
# this header is needed to get manifest in correct format: https://docs.docker.com/registry/spec/manifest-v2-2/ 
$headers.add("Accept", "application/vnd.docker.distribution.manifest.v2+json") 
$manifest = Invoke-Webrequest -Headers $headers -Method GET -Uri $taguri | ConvertFrom-Json 

# downloading config json 
$configSha = $manifest | Select -expand config | Select -expand digest 
$config = ".\$path\config.json" 
Invoke-Webrequest -Headers @{Authorization="Bearer $token"} -Method GET -Uri $bloburi$configSha -OutFile $config 

# generating manifest.json 
$manifestJson = @{} 
$manifestJson.add("Config", "config.json") 
$manifestJson.add("RepoTags",@("${image}:${tag}")) 

# downloading layers 
$layers = $manifest | Select -expand layers | Select -expand digest 
$blobtmp = ".\$path\blobtmp" 

#downloading blobs 
$layersJson = @() 
foreach ($blobelement in $layers) { 
    # making so name doesnt start with 'sha256:' 
    $fileName = "$blobelement.gz" -replace 'sha256:' 
    $newfile = ".\$path\$fileName" 
    $layersJson += @($fileName) 

    # token expired after 5 minutes, so requesting new one for every blob just in case 
    $token = Invoke-WebRequest -Uri $imageuri | ConvertFrom-Json | Select -expand token 
    
    Invoke-Webrequest -Headers @{Authorization="Bearer $token"} -Method GET -Uri $bloburi$blobelement -OutFile $blobtmp 
    
    Copy-Item $blobtmp $newfile -Force -Recurse 
} 

# removing temporary blob 
Remove-Item $blobtmp 

# saving manifest.json 
$manifestJson.add("Layers", $layersJson) 
ConvertTo-Json -Depth 5 -InputObject @($manifestJson) | Out-File -Encoding ascii ".\$path\manifest.json" 

# postprocessing
echo "copy generated folder to your docker machine" 
echo "tar -cvf imagename.tar *" 
echo "docker load < imagename.tar"

after sctipt downloads blobs and generates config and manifest jsons download it to docker machine and execute two following commands:
tar -cvf imagename.tar *
docker load < imagename.tar

first creates an archive, 2nd uploads image archive to docker


---

### Answer by username_8 on 2022-03-06. Score: 0

Another tool that could be used to download docker images is [P2IWD](https://github.com/username_8/p2iwd).

> **Comments:**
> **username_0**: There doesn’t appear to be any code in that repository.  
> **username_8**: @username_0 version 0.1.0 has been released. It is capable to download docker images from a Nexus3 server and to upload them to another one.  

---

### Answer by username_10 on 2024-09-03. Score: 0

You can manually download a docker image with curl by:

getting a free auth token
download the manifest
download the layers (blob hashes specified in the manifest)

Example
For example, I'll show how you can download the hitch package from hub.docker.com.
Get an Auth Token
Execute the following command to get an authentication token from Docker Hub within the scope of the 'hitch' package's namespace.
# get a JSON with an anonymous token
curl -so "token.json" "https://auth.docker.io/token?service=registry.docker.io&scope=:/:";

# extract token from JSON
token=$(cat token.json | jq -jr ".token")


ⓘ INFO: If you're like me and wondering where the heck is the OCI spec that defines this /token endpoint for authentication, know that it doesn't exist 
To learn more about the syntax of this URL and 'scope' GET variable, see docker's Token Scope Documentation.

The above commands will get a free/temporary token that you can use in subsequent API calls. If all went well, there will be no output from these commands. Here's an example execution
user@disp7456:~$ curl -so "token.json" "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/hitch:pull";
user@disp7456:~$ 

user@disp7456:~$ token=$(cat token.json | jq -jr ".token")
user@disp7456:~$ 

Download the Manifest
We can download the manifest for the '1.8.0-1' tag of the 'hitch' package with the 'GET /v2//manifests/' API endpoint.
curl -o manifest.json -s -H "Authorization: Bearer ${token}" https://registry-1.docker.io/v2/library//manifests/

And here's an example execution that downloads the manifest for the '1.8.0-1' tag of the 'hitch' package

user@disp7456:~$ curl -o manifest.json -s -H "Authorization: Bearer ${token}" https://registry-1.docker.io/v2/library/hitch/manifests/1.8.0-1
user@disp7456:~$ 

user@disp7456:~$ ls
manifest.json
user@disp7456:~$ 

Parse the Manifest
The previous step downloaded a file named 'manifest.json'. This 'manifest.json' file lists all of the "layers" that make up the image of the 'hitch' package.
Each "layer" consists of a tarball and some metadata about the layer in json format. The information that we need to download the layer's tar file is located in the 'manifest.json' file. And the metadata about each layer is also in the 'manifest.json' file.
The format of the 'manifest.json' file is 'vnd.docker.distribution.manifest.v1+json', which is defined in Image Manifest Version 2, Schema 1.
Most importantly, the 'manifest.json' file contains two parallel arrays of the same length:

fsLayers[]
history[]

Consider this truncated snippet of the manifest for the 'hitch' package's '1.8.0-1' tag:
{
   "schemaVersion": 1,
   "name": "library/hitch",
   "tag": "1.8.0-1",
   "architecture": "amd64",
   "fsLayers": [
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:3148f4af0a813bcff0a3ed2562aabfb1b596b52ef36eb5eb4d82ce836350b73a"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:a0e9543db8c1238572466cf00b55436bc7b7e849f7cb305128f391a94b75c2fc"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:728328ac3bde9b85225b1f0d60f5c149f5635a191f5d8eaeeb00e095d36ef9fd"
      }
   ],
   "history": [
      {
         "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"443/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"],\"Cmd\":[],\"Image\":\"sha256:996009c7d7eb032c9ea750e5decc1f8aedbf4530b892cf4ebc7716e1458f36d9\",\"Volumes\":null,\"WorkingDir\":\"/etc/hitch\",\"Entrypoint\":[\"docker-hitch-entrypoint\"],\"OnBuild\":null,\"Labels\":null},\"container\":\"0ff54ee96c4bbfe77da3b2124720ef95c6154d3bc1d3e40a168920dd818367c4\",\"container_config\":{\"Hostname\":\"0ff54ee96c4b\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"443/tcp\":{}},\"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 []\"],\"Image\":\"sha256:996009c7d7eb032c9ea750e5decc1f8aedbf4530b892cf4ebc7716e1458f36d9\",\"Volumes\":null,\"WorkingDir\":\"/etc/hitch\",\"Entrypoint\":[\"docker-hitch-entrypoint\"],\"OnBuild\":null,\"Labels\":{}},\"created\":\"2024-05-14T05:23:11.666992342Z\",\"docker_version\":\"20.10.23\",\"id\":\"6703605aae83084affcafb4abcc7c556f0e436c4992ae224f1f58e88242328cb\",\"os\":\"linux\",\"parent\":\"c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188\",\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188\",\"parent\":\"1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240\",\"created\":\"2024-05-14T05:23:11.581588417Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  EXPOSE 443\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240\",\"parent\":\"8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad\",\"created\":\"2024-05-14T05:23:11.489285564Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  ENTRYPOINT [\\\"docker-hitch-entrypoint\\\"]\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad\",\"parent\":\"a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5\",\"created\":\"2024-05-14T05:23:11.403178706Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) COPY file:1abf3c94dce5dc9f6617dc8d36a6fe6f4f7236189d4819f16cefb54288e80e0d in /usr/local/bin/ \"]}}"
      },
      {
         "v1Compatibility": "{\"id\":\"a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5\",\"parent\":\"5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f\",\"created\":\"2024-05-14T05:23:11.304477182Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) WORKDIR /etc/hitch\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f\",\"parent\":\"5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8\",\"created\":\"2024-05-14T05:23:11.160227264Z\",\"container_config\":{\"Cmd\":[\"|5 DISTVER=bullseye PKGCOMMIT=f12ab7958bc4885f3f00311cbca5103d9e6ba794 PKGVER=1 SHASUM=62b3554d668c9d17382415db10898bf661ee76343e4ee364f904457efda6cb1eeee7cb81d7a3897734024812b64b1c0e2dc305605706d81a0c1f6username_8508bf7e2 SRCVER=1.8.0 /bin/sh -c set -ex;     BASE_PKGS=\\\"apt-utils curl dirmngr dpkg-dev debhelper devscripts equivs fakeroot git gnupg pkg-config\\\";     export DEBIAN_FRONTEND=noninteractive;     export DEBCONF_NONINTERACTIVE_SEEN=true;     tmpdir=\\\"$(mktemp -d)\\\";     cd \\\"$tmpdir\\\";     apt-get update;     apt-get install -y --no-install-recommends $BASE_PKGS;     git clone https://github.com/varnish/pkg-hitch.git;     cd pkg-hitch;     git checkout ${PKGCOMMIT};     rm -rf .git;     curl -Lf https://hitch-tls.org/source/hitch-${SRCVER}.tar.gz -o $tmpdir/orig.tgz;     echo \\\"${SHASUM}  $tmpdir/orig.tgz\\\" | sha512sum -c -;     tar xavf $tmpdir/orig.tgz --strip 1;     sed -i         -e \\\"s/@SRCVER@/${SRCVER}/g\\\"         -e \\\"s/@PKGVER@/${PKGVER:-1}/g\\\"         -e \\\"s/@DISTVER@/$DISTVER/g\\\" debian/changelog;     mk-build-deps --install --tool=\\\"apt-get -o Debug::pkgProblemResolver=yes --yes\\\" debian/control;     sed -i '' debian/hitch*;     dpkg-buildpackage -us -uc -j\\\"$(nproc)\\\";     apt-get -y purge --auto-remove hitch-build-deps $BASE_PKGS;     apt-get -y --no-install-recommends install ../*.deb;     sed -i 's/daemon = on/daemon = off/' /etc/hitch/hitch.conf;     rm -rf /var/lib/apt/lists/* \\\"$tmpdir\\\"\"]}}"
      },
      {
         "v1Compatibility": "{\"id\":\"5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8\",\"parent\":\"c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be\",\"created\":\"2024-05-14T05:21:33.061082853Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  ARG SHASUM=62b3554d668c9d17382415db10898bf661ee76343e4ee364f904457efda6cb1eeee7cb81d7a3897734024812b64b1c0e2dc305605706d81a0c1f6username_8508bf7e2\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be\",\"parent\":\"24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d\",\"created\":\"2024-05-14T05:21:32.967727298Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  ARG PKGCOMMIT=f12ab7958bc4885f3f00311cbca5103d9e6ba794\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d\",\"parent\":\"f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34\",\"created\":\"2024-05-14T05:21:32.875807605Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  ARG DISTVER=bullseye\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34\",\"parent\":\"65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8\",\"created\":\"2024-05-14T05:21:32.781941821Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  ARG PKGVER=1\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8\",\"parent\":\"863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270\",\"created\":\"2024-05-14T05:21:32.682503634Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  ARG SRCVER=1.8.0\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270\",\"parent\":\"e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd\",\"created\":\"2024-05-14T01:28:27.043980081Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop)  CMD [\\\"bash\\\"]\"]},\"throwaway\":true}"
      },
      {
         "v1Compatibility": "{\"id\":\"e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd\",\"created\":\"2024-05-14T01:28:26.699066026Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:9b38b383dd93169a663eed88edf3f2285b837257ead69dc40ab5ed1fb3f52c35 in / \"]}}"
      }
   ],
   ...
   ]
}

The sha256sum used to download the blob of the first layer is found at the first element of the fsLayers[] array (fsLayers[0]). The metadata about this first layer is found at the first element of the history[] array (history[0]).
The sha256sum used to download the blob of the second layer is found at second element of the fsLayers[] array (fsLayers[1]). The metadata about this second layer is found at the second element of the history[] array (history[1]).
Et cetera...
Download the Layers
So how do we download each of these layers separately, yet organize them such that we can later import them as a single image into docker? The answer to that lies in the Docker Image Specification v1.0.0.
The above 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.

So, as shown in the quote from the spec above, what we need to do is to create a set of directories -- one for each layer -- named after the layer's ID. Each of these layer-specific directories must contain 3 files:

The actual tarball of the layer (named 'layer.tar'),
The metadata of the layer (in a file literally named 'json' -- with no file extension)
A file named 'VERSION' whose contents is literally just '1.0'

All of the information that we need is in the 'manifest.json' file that we already downloaded. Let's just loop through each layer that it defines, download the layer.tar tarball, create the 'json' metadata file, and hard-code the 'VERSION' file with the following BASH snippet
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 "1.0" > "layers/$layer_id/VERSION"
    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//blobs/${layer_blobSum}"
done

And here's an example execution that executes the above snippet to download all of the layers onto disk in a set of directories as defined by the Docker Image Specification v1.0.0.
user@disp7456:~$ num_layers=$(cat manifest.json | jq -r ".history | length")
user@disp7456:~$ 

user@disp7456:~$ 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 "1.0" > "layers/$layer_id/VERSION"
    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/hitch/blobs/${layer_blobSum}"
done
6703605aae83084affcafb4abcc7c556f0e436c4992ae224f1f58e88242328cb
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad
sha256:3148f4af0a813bcff0a3ed2562aabfb1b596b52ef36eb5eb4d82ce836350b73a
################################################################ 100.0%
a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f
sha256:a0e9543db8c1238572466cf00b55436bc7b7e849f7cb305128f391a94b75c2fc
################################################################ 100.0%
5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270
sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
################################################################ 100.0%
e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd
sha256:728328ac3bde9b85225b1f0d60f5c149f5635a191f5d8eaeeb00e095d36ef9fd
################################################################ 100.0%
user@disp7456:~$ 

user@disp7456:~$ tree layers/
layers/
├── 1d2af5a156bbc461d98824c2f6bfe295327d4419105c0b7f88f14cb28d0bb240
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 24e7aee556d6a38bfa2e13430db8a998c023a2920017eabc0b3bf0dd7661bf7d
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 5a12a2c67ff9b5bfad288a4ede18d08c259c301efb85403d08a40ea2ad0eb1f8
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 5a78b0e89bbae2390b83e60174ae1efc583f766eff7dffaffa747ccb67472d0f
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 65c7b6d17437bf7a3216e2fea283071e9b5c0d71c6b97472baa8807a30b5d9d8
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 6703605aae83084affcafb4abcc7c556f0e436c4992ae224f1f58e88242328cb
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 863a608d086b1bcf7f9b30ccf57260e6cb5d3d793b4e1131aa8f6041b07a7270
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── 8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── c03ad9230005f64133de4501e14a882ef25f03443da4da55ca002d5619f998be
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── c48ca3d95161bbcdfcaa2e016a675965d55f4f06147ef4445c69347c5965f188
│   ├── json
│   ├── layer.tar
│   └── VERSION
├── e00e363f3a25341591a5a5e724e20ae3e70f0396be8483a07c0b39d25d33fecd
│   ├── json
│   ├── layer.tar
│   └── VERSION
└── f0d07a99d7d1f0b849a4cbe8fc4552d374f4448c2e7f8bfd908aa43132c4ec34
    ├── json
    ├── layer.tar
    └── VERSION

14 directories, 39 files
user@disp7456:~$ 

Finally, besides these layer-specific dirs, we need one additional file (named simply 'repository', with no file extension) at the same height as these dirs. As defined by the Docker Image Specification v1.0.0, this file should state 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, so we can create this file with the following command
start_image=$(cat manifest.json | jq -r ".history[0].v1Compatibility")
start_image_id=$(echo $start_image | jq -r ".id")

cat > layers/repositories <": { "stable": "$start_image_id" }
}
EOF

And here's an example execution to create our 'repository' file for the 'hitch' package.
user@disp7456:~$ start_image=$(cat manifest.json | jq -r ".history[0].v1Compatibility")
user@disp7456:~$ 

user@disp7456:~$ start_image_id=$(echo $start_image | jq -r ".id")
user@disp7456:~$ 

user@disp7456:~$ cat > layers/repositories <
Your 'layers/' directory should now be prepared-to-spec for importing the entire image into docker.
For reference, here's the contents of just one of the layers:
user@disp7456:~$ ls layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/
json  layer.tar  VERSION
user@disp7456:~$ 

user@disp7456:~$ cat layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/VERSION 
1.0
user@disp7456:~$ 

user@disp7456:~$ cat layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/json 
{"id":"8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad","parent":"a8d8314458142ee2a4ebccb19f48b6f9c696100103c3d49cbbe7ecd2575120e5","created":"2024-05-14T05:23:11.403178706Z","container_config":{"Cmd":["/bin/sh -c #(nop) COPY file:1abf3c94dce5dc9f6617dc8d36a6fe6f4f7236189d4819f16cefb54288e80e0d in /usr/local/bin/ "]}}
user@disp7456:~$ 

user@disp7456:~$ sha256sum layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/layer.tar 
3148f4af0a813bcff0a3ed2562aabfb1b596b52ef36eb5eb4d82ce836350b73a  layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/layer.tar
user@disp7456:~$ 

user@disp7456:~$ tar -tvf layers/8f914c821cbe154cc6677bceb043669e4295ad5cfb6409efa9c2ec1beba75fad/layer.tar 
drwxr-xr-x 0/0               0 2024-05-12 19:00 usr/
drwxr-xr-x 0/0               0 2024-05-12 19:00 usr/local/
drwxr-xr-x 0/0               0 2024-05-14 00:23 usr/local/bin/
-rwxrwxr-x 0/0             319 2024-05-14 00:21 usr/local/bin/docker-hitch-entrypoint
user@disp7456:~$ 

Load the Image
Finally, you can copy the whole layers directory into your restricted environment and then load the layers as one image into docker with docker image load
tar -cC layers . | docker image load

Here's an example execution
user@disp7456:~$ docker image ls
REPOSITORY   TAG       IMAGE ID   CREATED   SIZE
user@disp7456:~$ 

user@disp7456:~$ tar -cC layers . | docker load
e00e363f3a25: Loading layer [==================================================>]  31.43MB/31.43MB
863a608d086b: Loading layer [==================================================>]      32B/32B
65c7b6d17437: Loading layer [==================================================>]      32B/32B
f0d07a99d7d1: Loading layer [==================================================>]      32B/32B
24e7aee556d6: Loading layer [==================================================>]      32B/32B
c03ad9230005: Loading layer [==================================================>]      32B/32B
5a12a2c67ff9: Loading layer [==================================================>]      32B/32B
5a78b0e89bba: Loading layer [==================================================>]  1.573MB/1.573MB
a8d831445814: Loading layer [==================================================>]      32B/32B
8f914c821cbe: Loading layer [==================================================>]     415B/415B
1d2af5a156bb: Loading layer [==================================================>]      32B/32B
c48ca3d95161: Loading layer [==================================================>]      32B/32B
6703605aae83: Loading layer [==================================================>]      32B/32B
user@disp7456:~$ 

user@disp7456:~$ docker image ls
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
hitch        stable    f07eadb841be   3 weeks ago   85.1MB
user@disp7456:~$ 

Attribution
The example above was copied from Manually Downloading Container Images (Docker, Github Packages)


---

### Answer by username_11 on 2020-02-10. Score: -1

If your company has nexus or a similar repository, they may already have set up nexus to pull in what you need. You could try adding your repo's host/ip in front of the relative docker hub path.
Examples:
docker pull nexus.example.com:18443/nginx
docker pull nexus.example.com:18443/selenium/node-chrome
docker pull nexus.example.com:18443/postgres:9.4


---

