Repository navigation
Description
Description
nerdctl save -o FILE and nerdctl export -o FILE open the output with os.O_CREATE|os.O_WRONLY and no os.O_TRUNC. When FILE already exists and is longer than what is about to be written, the tail of the previous content survives past the end of the new archive.
- https://github.com/containerd/nerdctl/blob/main/cmd/nerdctl/image/image_save.go#L98
- https://github.com/containerd/nerdctl/blob/main/cmd/nerdctl/container/container_export.go#L71
docker save does not have this problem: it writes through an atomic writer (temp file plus rename), so the destination is replaced wholesale.
// docker/cli cli/command/image/save.go
writer, err := atomicwriter.New(opts.output, 0o600)Steps to reproduce the issue
nerdctl save -o out.tar <a larger image>nerdctl save -o out.tar <a smaller image>ls -l out.tar— it still has the size of the archive from step 1.
The same applies to nerdctl export -o out.tar <container>.
The open flags in isolation:
func open(path string) (*os.File, error) {
// as in image_save.go and container_export.go
return os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644)
}
f, _ := open(path)
f.Write([]byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")) // 30 bytes, the larger image
f.Close()
f, _ = open(path)
f.Write([]byte("BBBBBBBBBB")) // 10 bytes, the smaller image
f.Close()wrote 10 bytes, file holds 30: "BBBBBBBBBBAAAAAAAAAAAAAAAAAAAA"Describe the results you received and expected
Received: the file keeps the size of the previous, larger archive, with the leftover bytes of that archive sitting after the end of the new one.
Note that this does not break nerdctl load: a tar reader stops at the end-of-archive marker and never looks at what follows, which I checked with both archive/tar and the system tar. What is wrong is the artifact itself — it is larger than the archive it is supposed to contain, and the extra bytes belong to an unrelated image, which shows up in anything that checksums the file, accounts for its size, or ships it somewhere.
Expected: -o replaces the file, as docker save -o and docker export -o do, and as a shell redirect does.
pkg/healthcheck/log.go opens a file with the same flags, but seeks to the end and appends on purpose, so it is not affected.
What version of nerdctl are you using?
main, at c235f00.
The flags came in with 4b8061d (2024-08-30) for save and 3e50703 (2025-08-05) for export.
Activity
ekalinin commentedon Aug 24, 2026
Corrected the description above: I first wrote that the resulting file is not a valid tar, which is wrong. A tar reader stops at the end-of-archive marker, so
nerdctl loadreads such a file without complaining — I verified that with botharchive/tarand the systemtar. The defect is the artifact carrying the tail of an unrelated archive, not a read failure.fix(image): truncate the output file of save and export