Skip to content

nerdctl save -o / export -o do not truncate an existing file #5159

Description

@ekalinin

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.

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

  1. nerdctl save -o out.tar <a larger image>
  2. nerdctl save -o out.tar <a smaller image>
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions