Skip to content

docker export import Vs. docker save load


You’ve now used docker container export to pull a container’s filesystem into a tarball, and docker image save to do something that looks almost identical with an image. Two commands, two tarballs, both end in .tar — so why does Docker bother having both? The answer matters more than it looks, and getting it wrong is one of the most common ways people accidentally ship broken images.

Two Pairs, One Question: What Are You Actually Copying?

Every one of these four commands answers the same underlying question — “how do I turn this into a file, and how do I turn that file back into something Docker can run?” — but they disagree on what “this” is.

  • docker container export / docker image import operate on a container. They flatten whatever is running (or stopped) into a single, layerless filesystem snapshot. Think of it as a photograph: it captures what the filesystem looks like right now, with no memory of how it got that way.
  • docker image save / docker image load operate on an image. They preserve the full stack of layers, along with the metadata that makes the image an image — layer history, environment variables, exposed ports, the default command, and tags.

That’s the whole distinction. Everything else in this section is a consequence of it.

Side-by-Side Comparison

Scopecontainer export / image importimage save / image load
Operates onA container (running or stopped)An image
Output containsSingle flattened filesystemAll original layers, intact
Layer history preserved?No — collapses to one layerYes — full layer history
Image metadata (CMD, ENV, EXPOSE, etc.)LostPreserved
Tags preserved?No — you must name the image on importYes — original name:tag comes back
Multiple images in one archive?No — one container onlyYes — can save several images at once
Typical use case“I just want this filesystem state as a base for something new”“I want to move this exact image somewhere else, unchanged”

Visualizing the Two Paths


    flowchart LR
    subgraph EI["Export / Import"]
        direction LR
        C1["Container<br/>(running state)"] -->|"container export"| T1["Flat tar<br/>(no layers)"]
        T1 -->|"image import"| I1["New image<br/>(1 layer, no metadata)"]
    end

    subgraph SL["Save / Load"]
        direction LR
        I2["Image<br/>(layers + metadata)"] -->|"image save"| T2["Layered tar<br/>(manifest + history)"]
        T2 -->|"image load"| I3["Same image<br/>(name:tag restored)"]
    end
  

Notice the asymmetry: the export/import path loses information on purpose (that’s the point — it’s a reset), while the save/load path is designed to be lossless. Save and load should get you back exactly what you started with, byte-for-byte identical in every layer.

Hands-On: Prove It Yourself

Numbers and diagrams are one thing — seeing the actual tarball contents makes this concrete. This example uses the public alpine image, so you don’t need anything pre-built.

Step 1 — Create a container and make a small change inside it.

docker container run -d --name distro-demo alpine sleep 3600
docker container exec distro-demo sh -c "echo 'hello from inside' > /demo.txt"

Step 2 — Export the container and save the image, side by side.

docker container export distro-demo -o container-export.tar
docker image save alpine -o image-save.tar

Step 3 — Look inside both tarballs.

tar -tvf container-export.tar | head -n 10

You’ll see a flat list of filesystem paths — bin/, etc/, demo.txt, and so on — with no mention of layers at all.

tar -tvf image-save.tar | head -n 20

This one looks different: you’ll see, manifest.json, a repositories file, and a set of directories named after long hex hashes — one per layer (blobs/). That structure is exactly what lets Docker reconstruct the image’s layer history on load.

Step 4 — Bring both back in and compare what you get.

cat container-export.tar | docker image import - demo:from-export
docker image load -i image-save.tar

Now inspect the history of each:

docker image history demo:from-export
docker image history alpine

demo:from-export shows a single layer with no history — Docker has no idea it started life as an Alpine container. alpine shows its full, original build history, exactly as it did before you saved it. That’s the export/import flattening effect, visible in a live command instead of just described in a table.

Step 5 — Clean up.

docker container stop distro-demo
docker container rm distro-demo
docker image rm demo:from-export
rm container-export.tar image-save.tar

Note

docker image load doesn’t take a “new name” argument the way docker image import does — it restores the image under its original name and tag automatically. If you already have an image with that same name:tag locally, the load will overwrite it.

Common Mistakes to Avoid

  • Using export/import to “back up” an image. You’ll get a filesystem, not an image — no CMD, no ENV, no exposed ports, no tags. If you need a faithful copy, that’s what save/load is for.
  • Expecting docker image import to preserve metadata. It doesn’t, by design. If you need the container’s original ENV or CMD values carried into the new image, you’ll have to reapply them yourself (something you’ll get proper tools for once you reach Dockerfiles and docker container commit).
  • Assuming save/load compresses anything. Both tarballs are uncompressed by default. If you’re moving a large image and file size matters, pipe through gzip on the way out and gunzip on the way in.

What’s Next

You now know exactly which pair of commands to reach for depending on whether you’re capturing a filesystem snapshot or moving a real image intact. That second case — moving images somewhere else — is about to get a lot more relevant, because next we’re leaving local tarballs behind and looking at how images actually get distributed in the real world: registries.

Last updated on