DevOps Elastic Hayway
Document

SUBSCRIBE TO GET FULL ACCESS TO THE E-BOOKS FOR FREE 🎁SUBSCRIBE NOW

Professional Dropdown with Icon

SUBSCRIBE NOW TO GET FREE ACCESS TO EBOOKS

DockerLesson 11 / 227 min readUpdated September 11, 2026

Docker Save and Load Images: Move Images Offline (vs Export/Import and Registries)

Most of the time an image travels through a registry: docker push on one machine, docker pull on another. But sometimes there is no registry in the middle: an air-gapped server, a customer site without internet, a CI job that must hand an image to another job, or a laptop you want to restore after a rebuild. docker save writes one or more images to a tar archive and docker load reads them back, preserving tags, layers and history. This tutorial covers the full workflow, compression, multi-image bundles, the difference with docker export/import, and the equivalent commands in containerd (ctr, nerdctl) and Podman.

Prerequisites: Docker Engine 27+ (docker version), a few hundred MB of disk, and basic image knowledge from Docker Images.

save/load vs export/import at a glance

docker save / docker loaddocker export / docker import
Works onImagesContainers (a running or stopped container’s filesystem)
Preserves layersYes, all layers and their metadataNo, flattens to a single layer
Preserves tags, ENV, CMD, ENTRYPOINT, EXPOSE, historyYesNo (you can re-add some with --change)
Multiple images in one archiveYesNo
Typical useMove or back up images offlineSnapshot a container as a fresh, flat image

Step 1 – Save an image to a tar file

docker pull nginx:1.27-alpine
docker image ls nginx
# REPOSITORY   TAG            IMAGE ID       SIZE
# nginx        1.27-alpine    3f9a5f6b1c2d   48.3MB

# Save by name:tag (recommended – keeps the tag in the archive)
docker save -o nginx-1.27-alpine.tar nginx:1.27-alpine

# Equivalent with a redirect
docker save nginx:1.27-alpine > nginx-1.27-alpine.tar

ls -lh nginx-1.27-alpine.tar
# -rw------- 1 user user 49M ... nginx-1.27-alpine.tar

If you save by image ID (docker save 3f9a5f6b1c2d) the archive contains no repository or tag; after docker load the image shows up as <none>:<none> and you must docker tag it manually. Always save by name:tag.

Step 2 – Compress it

Image layers are already gzip-compressed inside a registry, but docker save writes them uncompressed. Compressing the tar typically shrinks it 2–3×. docker load detects gzip, bzip2, xz and zstd automatically.

# gzip (universal)
docker save nginx:1.27-alpine | gzip > nginx-1.27-alpine.tar.gz

# zstd (much faster, similar ratio; needs zstd on both sides)
docker save nginx:1.27-alpine | zstd -T0 -o nginx-1.27-alpine.tar.zst

ls -lh nginx-1.27-alpine.tar*
# 49M  nginx-1.27-alpine.tar
# 20M  nginx-1.27-alpine.tar.gz
# 19M  nginx-1.27-alpine.tar.zst

Step 3 – Transfer and load on the target host

# Copy over SSH (or USB key, S3, artifact store…)
scp nginx-1.27-alpine.tar.gz admin@airgapped-host:/tmp/

# On the target
ssh admin@airgapped-host
docker load -i /tmp/nginx-1.27-alpine.tar.gz
# Loaded image: nginx:1.27-alpine

docker image ls nginx
docker run -d --name web -p 8080:80 nginx:1.27-alpine
curl -s localhost:8080 | head -3

No temporary file needed? Stream directly over SSH:

docker save nginx:1.27-alpine | gzip | ssh admin@airgapped-host 'docker load'

Step 4 – Bundle several images in one archive

Shipping a whole Compose stack offline is a common need. docker save accepts many images and de-duplicates shared layers inside the archive.

# All images referenced by a compose file
cd my-stack
docker compose pull
IMAGES=$(docker compose config --images)
echo "$IMAGES"

docker save $IMAGES | zstd -T0 -o my-stack-images.tar.zst

# On the target
zstd -dc my-stack-images.tar.zst | docker load
docker compose up -d --pull never       # never try to reach a registry

Step 5 – Inspect an archive without loading it

tar -tf nginx-1.27-alpine.tar | head
# blobs/sha256/...        (layers and configs, OCI layout)
# index.json
# manifest.json
# oci-layout
# repositories

tar -xOf nginx-1.27-alpine.tar manifest.json | jq .
# [{ "Config": "blobs/sha256/...", "RepoTags": ["nginx:1.27-alpine"], "Layers": [ ... ] }]

Since Docker 25 the archive follows the OCI image layout while keeping the legacy manifest.json, so tools such as skopeo, crane, containerd and Podman can read it too.

Multi-platform images

By default docker save exports only the platform variant present in your local store (e.g. linux/amd64). To ship an image for an ARM server from an Intel laptop, pull the right variant first, or use the containerd image store which keeps all variants:

# Pull a specific platform and save it
docker pull --platform linux/arm64 nginx:1.27-alpine
docker save --platform linux/arm64 nginx:1.27-alpine -o nginx-arm64.tar   # Docker 27.5+

# Alternative without Docker: copy directly from the registry to a tar with skopeo
skopeo copy --all docker://nginx:1.27-alpine oci-archive:nginx-all-platforms.tar

docker export / import: snapshot a container

Use these when you want the current filesystem of a container as a flat image, for example to capture a manually configured legacy system before writing a proper Dockerfile.

docker export web | gzip > web-rootfs.tar.gz

# Import as a new image, restoring the metadata that export dropped
zcat web-rootfs.tar.gz | docker import 
  --change 'CMD ["nginx", "-g", "daemon off;"]' 
  --change 'EXPOSE 80' 
  - myorg/web-snapshot:2026-09-10

docker history myorg/web-snapshot:2026-09-10    # a single layer

Equivalents in other tools

ToolSaveLoad
Podmanpodman save -o img.tar --format oci-archive nginx:1.27-alpinepodman load -i img.tar
nerdctl (containerd)nerdctl save -o img.tar nginx:1.27-alpinenerdctl load -i img.tar
ctr (containerd, Kubernetes nodes)ctr -n k8s.io images export img.tar docker.io/library/nginx:1.27-alpinectr -n k8s.io images import img.tar
skopeo (no daemon)skopeo copy docker://nginx:1.27-alpine docker-archive:img.tarskopeo copy docker-archive:img.tar docker-daemon:nginx:1.27-alpine

The ctr line matters for Kubernetes: to preload an image on a kubeadm node without a registry, import it into the k8s.io namespace and set imagePullPolicy: IfNotPresent.

When a registry is still the better answer

  • More than a handful of hosts: pulling from a private registry (Harbor, ECR, GitLab, or a plain registry:2 container) is faster and de-duplicates layers per host.
  • Repeated updates: a registry transfers only changed layers; a tar always contains everything.
  • Signing and scanning: Cosign, Notation and vulnerability scanners work on registries, not on tar files.
  • Air-gapped but many hosts: run a registry inside the isolated network and use docker load + docker push once to seed it.

Troubleshooting

  • “open /var/lib/docker/tmp/…: no space left on device” – docker load extracts to Docker’s data root first; free space or move data-root.
  • Image loaded as <none>:<none> – saved by ID; retag with docker tag <id> name:tag.
  • “no matching manifest for linux/arm64” – archive contains only amd64 layers; save with --platform or use skopeo --all.
  • Slow save on large images – pipe through zstd -T0 instead of gzip, or use pigz for parallel gzip.

Use case: hand an image between CI jobs

Pipelines often build in one job and test in another on a different runner. Saving the image as an artifact avoids pushing untested images to the registry:

# GitHub Actions excerpt
- run: docker build -t app:ci . && docker save app:ci | zstd -T0 -o app.tar.zst
- uses: actions/upload-artifact@v4
  with: { name: image, path: app.tar.zst, retention-days: 1 }
# … in the test job:
- uses: actions/download-artifact@v4
  with: { name: image }
- run: zstd -dc app.tar.zst | docker load && docker run --rm app:ci pytest

Key takeaways

  • docker save name:tag → tar; docker load -i → image with tags and layers intact.
  • Always compress (gzip or zstd) and always save by name:tag, not by ID.
  • One archive can carry a whole Compose stack; docker compose config --images lists what to include.
  • export/import flatten a container; save/load preserve an image.
  • Beyond a few hosts, seed a private registry instead of copying tar files around.

Next tutorial

Next: Run your own Docker registry and Docker Compose. Official docs: docker image save, docker image load.

Retour parcours Docker — hub de la série et leçons sœurs.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *