Registries are the normal way to move images, but sometimes there is no registry: an air-gapped server, a bug report, a migration between machines. docker save writes one or more images, with all their layers, config and tags, to a tar file, and docker load reads it back. docker export is a different tool: it writes a container's flattened filesystem, and docker import turns such a tarball into a new single-layer image:
docker save -o l3-layers.tar l3-layers:1.0
ls -lh l3-layers.tar
tar -tf l3-layers.tar | grep -v '^blobs/sha256/.'
docker rmi l3-layers:1.0 l3-layers:1 l3-layers:sha-7e7c388 | tail -1
docker load -i l3-layers.tar
docker create --name l3-exp l3-layers:1.0
docker export l3-exp -o l3-exp.tar
ls -lh l3-exp.tar
docker import l3-exp.tar l3-flat:1.0
docker image inspect l3-flat:1.0 --format '{{json .Config.Cmd}} {{len .RootFS.Layers}}'-rw------- 1 dev dev 24M Sep 25 17:06 l3-layers.tar blobs/ blobs/sha256/ index.json manifest.json oci-layout Deleted: sha256:effc91e93bded5a640443a3c45396f57c8b2a0a9035d395b19bee7a6bc6f8abd Loaded image: l3-layers:1.0 8fc7ed4ced1d4a7a678b5306a821656fb7840db8767c7ce2ec40aacaf1ada499 -rw------- 1 dev dev 8.4M Sep 25 17:06 l3-exp.tar sha256:d4567c963b16c5ca95f61d5958cc2ce819d90b6700f6548958ef8063312a53ce null 1
The saved file is an OCI image layout (oci-layout, index.json and a blobs/sha256 directory of compressed blobs), plus a manifest.json that older Docker 514 versions read, so Podman 47,580 , skopeo 11,267 and containerd 234,762 's ctr can load it too. It weighs 24 MB, including the 20 MB layer the rm could not remove. The export is only 8.4 MB, because it holds the final filesystem with the deleted file really gone, but it lost everything else: the history, the layers and the CMD (now null). Use save and load to move images, and export to archive a container's filesystem.