Skip to content

feat(image): adopt Docker v29 output for images list - #5093

Open
ekalinin wants to merge 1 commit into
containerd:mainfrom
ekalinin:feat/images-docker-v29-output
Open

feat(image): adopt Docker v29 output for images list#5093
ekalinin wants to merge 1 commit into
containerd:mainfrom
ekalinin:feat/images-docker-v29-output

Conversation

@ekalinin

@ekalinin ekalinin commented Jul 22, 2026

Copy link
Copy Markdown

Make the default nerdctl images output match Docker v29: a collapsed view with
IMAGE, ID, DISK USAGE, CONTENT SIZE and EXTRA columns, an "In Use" (U)
indicator, and <untagged> for dangling images. Multi-platform images are collapsed
into a single row with aggregated disk and content size.

Details that follow Docker's behavior:

  • DISK USAGE is the content store blobs plus the unpacked snapshots (Docker's
    Total), CONTENT SIZE is the blobs alone. Both are formatted with 3 significant
    digits, like Docker.
  • In use is resolved by image target digest, the way Docker matches containers to
    images, so every reference to a used target is flagged, not only the one the
    container was created from.
  • The "In Use" legend is only printed when the output is a terminal, so piped and
    redirected output stays clean.
  • Rows are ordered by image reference with untagged images last, rather than by
    creation time.
  • The ID column is the OCI digest of the image target, which matches Docker's ID
    with the containerd image store (it differs only from the legacy graphdriver
    image ID). The help text and docs are updated accordingly.

The new view is used only for the bare command. Passing --format, --quiet,
--no-trunc, --digests or --names falls back to the legacy table (REPOSITORY,
TAG, IMAGE ID, CREATED, PLATFORM, SIZE, BLOB SIZE), so existing scripts and
templates keep working, including their creation-time ordering. This mirrors Docker's
own shouldUseTree fallback.

Since the default output now matches Docker, the images tests also run against the
Docker target; only the nerdctl-specific --names subtest stays gated. Tests that
assert on the default images output for untagged images (image prune/remove and
build-without-tag) are updated to expect <untagged>.

The expanded per-platform --tree view is left for a follow-up.

Closes #5027

Comment thread cmd/nerdctl/image/image_list_test.go Outdated
@ekalinin
ekalinin force-pushed the feat/images-docker-v29-output branch 3 times, most recently from 21a312d to 97dbfbd Compare July 24, 2026 09:28
@AkihiroSuda AkihiroSuda added this to the v2.4.0 milestone Jul 25, 2026
@AkihiroSuda
AkihiroSuda requested a review from a team August 1, 2026 17:19
Comment thread cmd/nerdctl/image/image_list.go
Comment thread cmd/nerdctl/image/image_list.go Outdated

@AkihiroSuda AkihiroSuda left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@ekalinin
ekalinin force-pushed the feat/images-docker-v29-output branch from 97dbfbd to a22cd95 Compare August 3, 2026 15:25
Make the default `nerdctl images` output match Docker v29: a collapsed
view with IMAGE, ID, DISK USAGE, CONTENT SIZE and EXTRA columns, an
"In Use" (U) indicator, and <untagged> for dangling images.
Multi-platform images are collapsed into a single row with aggregated
disk and content size. Like Docker, the "In Use" legend is only printed
when the output is a terminal, so piped and redirected output stays
clean, and rows are ordered by image reference with untagged images
last rather than by creation time.

In-use is resolved by image target digest, the way Docker matches
containers to images, so every reference to a used target is flagged,
not only the one the container was created from.

The new view is used only for the bare command. Passing --format,
--quiet, --no-trunc, --digests or --names falls back to the legacy table
(REPOSITORY, TAG, IMAGE ID, CREATED, PLATFORM, SIZE, BLOB SIZE), so
existing scripts and templates keep working, including their
creation-time ordering. This mirrors Docker's own shouldUseTree
fallback.

Since the default output now matches Docker, the images tests also run
against the Docker target; only the nerdctl-specific --names subtest
stays gated. Tests that assert on the default `images` output for
untagged images (image prune/remove and build-without-tag) are updated
to expect <untagged>.

The expanded per-platform `--tree` view is left for a follow-up.

Closes containerd#5027

Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
@ekalinin
ekalinin force-pushed the feat/images-docker-v29-output branch from a22cd95 to 4dc5675 Compare August 3, 2026 20:21
@ekalinin

ekalinin commented Aug 3, 2026

Copy link
Copy Markdown
Author

Force-pushed. Both threads above are addressed (DISK USAGE is now Total = content + unpacked, and the ID note is updated). On top of that, two more Docker-compat fixes that I found while re-reading the diff:

1. "In Use" is now resolved by image target digest instead of image name

moby matches containers to images by digest (daemon/containerd/image_list.go#L338):

  if c.ImageManifest != nil && c.ImageManifest.Digest == target.Digest {

nerdctl was keying the in-use set on container.Image().Name(), so only the reference the container had been created from got the U flag. With nerdctl tag alpine:latest myalias:v1 and a container running off alpine:latest, the myalias:v1 row stayed empty even though it points at the same target. Now every reference to a used target is flagged. The "In use" integration test tags the image under a second name and asserts that all rows pointing at that target are marked, so it also covers the Docker target (where those names are collapsed into a single row).

2. Row ordering in the new view now follows docker/cli

docker/cli's tree view sorts by image name lexicographically and places untagged images last, while nerdctl kept its creation-time ordering. The new view is now sorted by the rendered IMAGE value with <untagged> last. This is applied to the new view only: the legacy table keeps the creation-time ordering that --format consumers (and the CheckCreatedTime subtest) rely on. Added a unit test for the comparator.


One known divergence left, which I'd rather fix separately - let me know if you want it here

CONTENT SIZE sums the descriptor sizes declared in the manifest without checking that the blobs are actually present locally, and skips attestation manifests entirely. moby walks only the present content (walkPresentChildren, ImageManifest.PresentContentSize) and adds it to TotalSize before the pseudo-image / attestation check, so attestation content is counted.

So nerdctl can over-report for partially pulled images - which is not exotic here, given the lazy-pulling snapshotters (stargz, nydus, SOCI, overlaybd) - and under-report when attestations are present. This math predates this PR (it is the same code behind today's BLOB SIZE column), but the new view now surfaces it in two columns instead of one, so it is worth calling out. I'll open a separate issue unless you prefer it folded in.

PTAL.

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.

nerdctl images: adopt Docker v29 output

2 participants