Skip to content

feat(system): add nerdctl system df - #5130

Open
ekalinin wants to merge 2 commits into
containerd:mainfrom
ekalinin:feat/system-df
Open

feat(system): add nerdctl system df#5130
ekalinin wants to merge 2 commits into
containerd:mainfrom
ekalinin:feat/system-df

Conversation

@ekalinin

@ekalinin ekalinin commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Add nerdctl system df, the equivalent of docker system df: the disk space taken by the images,
containers and local volumes of the current namespace, plus the BuildKit build cache.

$ nerdctl system df
TYPE             TOTAL    ACTIVE    SIZE       RECLAIMABLE
Images           17       1         18.25GB    17.26GB (94%)
Containers       3        3         169.2MB    0B (0%)
Local Volumes    4        3         798.6GB    22.62MB (0%)
Build Cache      44       0         0B         0B

The size definitions

The issue was blocked on a question: Docker v29 changed how an image size is computed (#5027), so
what should system df report? The formulas below were derived from moby/moby v29 with the
containerd image store (daemon/disk_usage.go, daemon/containerd/service.go) and docker/cli v29
(cli/command/formatter/disk_usage.go).

Row TOTAL ACTIVE SIZE RECLAIMABLE
Images unique, by target digest with at least one container deduplicated: every snapshot and every present blob counted once Σ (Size - SharedSize) of the unreferenced
Containers all, stopped included Running / Paused / Restarting Σ SizeRw (read-write layer only) Σ SizeRw of the non-active
Local Volumes all with at least one container Σ Size Σ Size of the unreferenced
Build Cache all in use Σ Size Σ Size of those neither in use nor shared
  • The Images SIZE is not the sum of the image sizes: what images share is counted once, so
    the row reports the space really taken on disk.
  • A single image size is its present content plus its unpacked snapshots, the value nerdctl images already shows as DISK USAGE. The two commands agree. Docker sizes an image by walking
    its manifests, so the index listing them is counted in the row total while no single image is
    charged for it: an unused image is never quite 100% reclaimable.
  • Deduplicating by target digest is required, not cosmetic: one target can be stored under several
    names (repo:tag, repo@digest, plus the config digest under k8s.io), which would otherwise
    multiply every count and every size.
  • An image reports the created of its config, not the local record time, which only says when it
    was pulled or tagged and would show an old image as brand new. For a multi-platform image it is
    the config of the manifest the host platform matcher selects, since the platforms of an index are
    not necessarily built together; the record time stays as the fallback.

Output

-v/--verbose prints the four detail sections with Docker's SHARED SIZE / UNIQUE SIZE columns.

--format also accepts the Docker table TEMPLATE form, e.g. table {{.Type}}\t{{.Size}}: the
literal \t and \n are expanded, the columns get an aligned header, and the labels go through
docker/cli's HeaderFunctions, so table {{lower .Type}} still names the column TYPE. No
nerdctl command supported table TEMPLATE so far, so the helper lives in pkg/formatter for the
others to adopt. Identifiers are shortened for table output only, matching Format.IsTable(), so
that a custom format stays usable to look a resource up.

Notes

  • Images, containers and volumes are namespaced, the build cache is not: it comes from the BuildKit
    host of that namespace, and shows as zeros when BuildKit is down, so the shape of the output does
    not depend on which daemons happen to be up.
  • Split the way system prune already is: pkg/cmd/system orchestrates, each kind of resource is
    measured by its own package.
  • Unit tests cover the shared-size accounting, the reclaimable percentage (the build cache never
    gets one), the summary and verbose rendering, table TEMPLATE, the platform selection of the
    build time, and the parsing of buildctl du. The integration test runs in a private namespace
    and checks the counts after a pull, a run and a stop, plus the three output formats.

Depends on #5125

The first commit belongs to #5125, which pins the image a container runs to its digest. system df
uses the same in-use lookup: without it a retagged image lands in the wrong row and its layers
count as reclaimable while a container still holds them. Only the second commit is this PR.

Closes #3942

`nerdctl images` marks an image as in use by resolving the image name stored on the
container, which follows the tag wherever it points now. After `nerdctl tag` moves a tag
onto another image, the container gets attributed to an image it never ran: the U
indicator lands on the wrong row.

Record the image target digest on the container at creation time, in a new
nerdctl/image-digest label, and use it for the in-use lookup. Containers created before
this label existed, or created outside nerdctl, are still resolved by name; an unparsable
value falls back the same way rather than dropping the container from the set.

This also matters for the ACTIVE and RECLAIMABLE columns of `nerdctl system df`, which
build on the same lookup.

Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
@ekalinin
ekalinin force-pushed the feat/system-df branch 2 times, most recently from 3b7058e to 38b701c Compare August 7, 2026 19:51
Add `nerdctl system df`, the equivalent of `docker system df`, reporting how much disk
space the images, containers and local volumes of the current namespace use, plus the
BuildKit build cache.

The sizes follow the Docker v29 definitions for the containerd image store:

  - the size of an image is the content present in the content store plus its unpacked
    snapshots, which is the same value `nerdctl images` shows as DISK USAGE,
  - the SIZE of the Images row counts every snapshot and every blob once, so it is the
    space really taken on disk rather than the sum of the image sizes,
  - an image is active when a container references it, and only the part of an unused
    image that no other image shares is reclaimable,
  - containers contribute their read-write layer only, and everything not running is
    reclaimable,
  - a volume is active when a container mounts it, counted once however many paths it is
    mounted at, as the LINKS column of Docker is a count of containers,
  - a build cache record is reclaimable when it is neither in use nor shared.

An image reports when it was built, read from the config of the manifest the platform
matcher of this host selects: the platforms of an index are not necessarily built
together, and the creation time of the local record only says when the image was pulled
or tagged. The record time stays as the fallback for the images that state nothing.

Both `--format` and `-v/--verbose` are supported, including the Docker `table TEMPLATE`
format, e.g. `table {{.Type}}\t{{.Size}}`: the literal \t and \n are expanded, the chosen
columns get a header that names them whatever the template does to the values below, and
the rows stay aligned. That helper lives in pkg/formatter so that the other commands,
which all share this gap, can adopt it. Identifiers are shortened for the table output
only, so that a custom format stays usable to look a resource up.

The work is split the way `system prune` already is: `pkg/cmd/system` orchestrates,
and each kind of resource is measured by its own package.

Closes containerd#3942

Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support nerdctl system df command

1 participant