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 importoperate 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 loadoperate 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
| Scope | container export / image import | image save / image load |
|---|---|---|
| Operates on | A container (running or stopped) | An image |
| Output contains | Single flattened filesystem | All original layers, intact |
| Layer history preserved? | No — collapses to one layer | Yes — full layer history |
| Image metadata (CMD, ENV, EXPOSE, etc.) | Lost | Preserved |
| Tags preserved? | No — you must name the image on import | Yes — original name:tag comes back |
| Multiple images in one archive? | No — one container only | Yes — 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.tarStep 3 — Look inside both tarballs.
tar -tvf container-export.tar | head -n 10You’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 20This 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.tarNow inspect the history of each:
docker image history demo:from-export
docker image history alpinedemo: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.tarNote
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, noENV, no exposed ports, no tags. If you need a faithful copy, that’s what save/load is for. - Expecting
docker image importto preserve metadata. It doesn’t, by design. If you need the container’s originalENVorCMDvalues carried into the new image, you’ll have to reapply them yourself (something you’ll get proper tools for once you reach Dockerfiles anddocker 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
gzipon the way out andgunzipon 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.