skip to content

What is the difference between `docker save` / `docker load` and `docker push` / `docker pull`, and when would you reach for the save/load pair?

level: juniorimportance: should knowfreq 44%

answer

  1. push/pull = HTTP registry, incremental
  2. save/load = tar file, full copy
  3. Air-gapped / no registry → save
  4. export = container filesystem, no layers
  5. gzip the save output

basics

~20 s

Push and pull transfer an image to and from a registry over HTTP, uploading only layers the registry lacks. Save writes the whole image — layers, config, tags — into a tar file that load reads back. Use save/load for air-gapped transfer or when no registry is available.

solid answer

~60 s

`docker push` and `docker pull` speak the registry HTTP API: the client checks which blobs the registry already has and transfers only what is missing, so a rebuilt image with one changed layer moves a few megabytes. The registry becomes the shared distribution point that any host can pull from. `docker save -o app.tar app:1.0` serialises the complete image — every layer, the config, and the tag metadata — into a single tar archive with no deduplication against the destination. `docker load -i app.tar` reads it back into another daemon's image store. You copy that file however you like: USB stick, scp, artifact storage. I use save/load for air-gapped or offline environments, moving an image into a test VM or a local cluster without standing up a registry, and archiving a build artifact. For anything routine, a registry wins: incremental transfer, one authoritative location, tags and digests, and access control. Worth distinguishing `docker export`, which dumps a *container's* flattened filesystem with no layers or history — not the same thing.

code

bash · 15 lines
bash
# Registry path: only missing blobs travel
docker tag app:1.0 registry.example.com/team/app:1.0
docker push registry.example.com/team/app:1.0
docker pull registry.example.com/team/app:1.0   # on another host

# File path: whole image every time
docker save app:1.0 | gzip > app-1.0.tar.gz
scp app-1.0.tar.gz airgapped-host:/tmp/
ssh airgapped-host 'gunzip -c /tmp/app-1.0.tar.gz | docker load'

# Several images in one bundle
docker save -o bundle.tar app:1.0 postgres:16 nginx:1.27

# Different operation entirely: a container's flattened filesystem
docker export my-container > rootfs.tar

go deeper

for a junior

Know that push/pull uses a registry and save/load uses a tar file, and that save is the offline option.

for a middle

Explain incremental blob transfer versus a full archive, and clearly separate save/load from export/import.

for a senior

Discuss when an air-gapped bundle is justified, compression and size handling, and the multi-platform limitation of local archives.

for a principal

Frame it as distribution strategy: a registry provides naming, provenance and access control that a file copy cannot, so offline bundles should be an exception with a documented process.

## Two different transports for the same thing An image is a config object plus an ordered set of layer blobs, tied together by a manifest. Both mechanisms move that structure; they differ in the channel and in how much they move. ## push / pull — the registry path `docker push registry/team/app:1.0` talks the Registry HTTP API v2 to a server. The essential property is **content addressing**: every blob is named by the SHA-256 digest of its bytes. Before uploading, the client asks the registry whether it already holds each blob; if it does, that blob is skipped. So pushing version 1.1 of an image whose base layers are unchanged uploads only the new application layer and a small manifest. `docker pull` is the mirror image: fetch the manifest, then fetch only the blobs the local store is missing, then unpack. Layers already present from another image on the same host are reused. What you get with a registry: incremental transfer, a single place that many hosts can fetch from concurrently, tags and immutable digests as names, authentication and authorization, and usually scanning, retention and audit around it. ## save / load — the file path `docker save -o app.tar app:1.0` produces a tar archive containing the image's layers, its config JSON, and metadata that records which tags the archive carries. It is a **full** copy every time — there is no negotiation with a destination, so nothing can be skipped. Saving a 900 MB image gives roughly a 900 MB file even if the target already holds 95% of those layers. Layers inside the archive are stored uncompressed by default, so `docker save app:1.0 | gzip > app.tar.gz` is normal practice. `docker load -i app.tar` imports the archive into a daemon's image store, restoring the layers and re-applying the tags recorded in it. Layers the destination already has are recognised by digest and not duplicated on disk — the saving is in storage, not in transfer. You can also save several images into one archive (`docker save -o bundle.tar app:1.0 db:15 proxy:1.25`), which is the usual way to ship a whole environment into an isolated network. ## When save/load is the right answer - **Air-gapped or classified environments** where the target network cannot reach any registry. Build outside, save, transfer the file through whatever approved channel exists, load inside. - **No registry available** — a demo, a customer site, a quick hand-off to a colleague, loading an image into a local single-node cluster's runtime. - **Archiving a specific build** as a self-contained artifact alongside other release files. - **Debugging image contents** — the archive is a plain tar you can unpack and inspect without a running daemon. ## When it is the wrong answer Anything repeated. Using save/load as a routine deployment mechanism means full-size transfers on every release, manual copying, no access control, no immutable digests, and no single source of truth about what "1.0" means. A small self-hosted registry removes all of that. ## The confusion to avoid: export / import `docker export <container>` writes the **flattened filesystem of a container** to a tar — no layers, no image config, no history, no entrypoint or environment settings. `docker import` turns such a tar into a brand-new single-layer image, and you must re-specify metadata like `CMD` yourself. That is a different operation with different uses (extracting a filesystem, squashing). Confusing `save`/`load` with `export`/`import` is a common interview stumble; the giveaway is that save operates on an **image** and preserves everything, while export operates on a **container** and preserves only files. ## Practical notes - `docker save` needs the image present locally; it does not fetch from a registry on your behalf. - Archive format has evolved toward the OCI layout, so an archive produced by a much newer toolchain may not load on a very old daemon. - Multi-platform images: a plain `docker save` of a locally built image captures what your daemon holds, typically one platform. Distributing a genuine multi-architecture image is a registry job, since the multi-platform index is a registry-side construct in normal workflows. - Loading does not push anywhere: after `docker load`, tagging and pushing to a registry is a separate step.

  • How does `docker export` differ from `docker save`?
    `docker save` operates on an image and preserves its layers, config and tags, so loading it restores the image exactly. `docker export` operates on a running or stopped container and writes only the flattened filesystem — no layer history, no entrypoint, no environment. Importing that tar yields a new single-layer image whose metadata you must re-declare yourself.
  • Why is a 900 MB save archive slower to distribute than pushing the same image, even to a fresh host?
    Push negotiates with the registry and skips every blob it already stores, and a fresh host still benefits because base layers are often shared with other images it has pulled. A save archive has no destination to negotiate with, so it always contains every layer in full. The archive is also uncompressed by default, which is why people pipe it through gzip.

Push/pull is a shared library where you only borrow the books you lack; save/load is photocopying the entire shelf and carrying the box across town.

saying these in an interview costs you the question

  • Saying save/load also deduplicates layers during transfer
  • Confusing `docker export`/`import` with `docker save`/`load`
  • Thinking `docker load` pushes the image to a configured registry
  • Claiming a plain `docker save` carries every architecture of a multi-platform image
  • Using save/load as the normal deployment path when a registry is available

context