Skip to content

fix: image ls with a bare repository name should match all tags - #5115

Merged
AkihiroSuda merged 1 commit into
containerd:mainfrom
ankit090701:fix/image-ls-bare-repo-name-matches-all-tags
Aug 1, 2026
Merged

fix: image ls with a bare repository name should match all tags#5115
AkihiroSuda merged 1 commit into
containerd:mainfrom
ankit090701:fix/image-ls-bare-repo-name-matches-all-tags

Conversation

@ankit090701

Copy link
Copy Markdown
Contributor

What's the bug

nerdctl image ls myapp, where myapp is a bare repository name (no tag or digest), returns nothing unless myapp happens to have a :latest tag. This is different from docker image ls myapp, which lists every tag of the repository.

$ nerdctl image ls myapp
REPOSITORY    TAG    IMAGE ID    CREATED    PLATFORM    SIZE    BLOB SIZE

even though myapp:9.8-ltsc2022-... clearly exists.

Root cause

referenceutil.Parse normalizes a bare repository name by unconditionally applying distribution/reference's TagNameOnly, which appends an implicit :latest tag (referenceutil.go#L133-L134). listOptions in cmd/nerdctl/image/image_list.go then built an exact-match filter straight from that normalized reference:

filters = []string{fmt.Sprintf("name==%s", parsedReference)}

which becomes name==docker.io/library/myapp:latest - so any image tagged with anything other than latest is silently excluded.

The fix

Extracted the filter-construction logic into a small nameFilterFor helper:

  • If the argument named an explicit tag or digest (parsedReference.ExplicitTag != "" || parsedReference.Digest != ""), keep matching it exactly with name==....
  • If it was a bare repository name, build a name~=^<repo>: regex filter instead (escaping the repository name with regexp.QuoteMeta), so it matches any tag under that repository.

This mirrors the ~= regex-filter pattern already used elsewhere in the codebase for similar name/id matching, e.g. pkg/idutil/imagewalker/imagewalker.go and pkg/idutil/containerwalker/containerwalker.go, both of which already build field~=regex filters with regexp.QuoteMeta escaping - so this isn't introducing a new pattern, just applying an existing one to close this gap.

Testing

  • Added TestNameFilterFor, a focused unit test on the extracted helper (no daemon required), covering:
    • a bare repository name → now produces a name~=^docker\.io/library/myapp: regex filter (matches any tag)
    • an explicit tag (myapp:v1) → still an exact name==...:v1 filter
    • an explicit :latest tag → still an exact name==...:latest filter
    • a digest reference → still an exact name==...@sha256:... filter
    • a bare name with a domain/path that needs regex-escaping (registry.example.com/foo/my.app)
  • Verified this test fails without the fix, reproducing the exact reported bug: it produces name==docker.io/library/myapp:latest instead of a repo-wide match.
  • go build ./... and go vet ./... pass. The rest of the package's tests are tigron/E2E tests that shell out to a built nerdctl binary against a live containerd daemon, which I don't have available in my sandboxed environment - those fail with unable to find binary "nerdctl" regardless of this change (verified against an unmodified checkout).

Fixes #5113

`nerdctl image ls myapp`, where myapp is a bare repository name (no tag
or digest), returned nothing unless myapp happened to have a `:latest`
tag - unlike `docker image ls myapp`, which lists every tag of the
repository.

referenceutil.Parse normalizes a bare repository name by unconditionally
applying distribution/reference's TagNameOnly, which appends an implicit
":latest" tag. listOptions then built an exact-match filter from that
normalized reference (`name==docker.io/library/myapp:latest`), so any
image tagged anything other than "latest" was silently excluded.

Extract the filter-construction logic into a small nameFilterFor helper.
When the argument named an explicit tag or digest, keep matching it
exactly. When it was a bare repository name, build a
`name~=^<repo>:` regex filter instead (escaping the repository name with
regexp.QuoteMeta), so it matches any tag under that repository. This
mirrors the `~=` regex-filter pattern already used elsewhere in the
codebase for similar purposes, e.g. pkg/idutil/imagewalker/imagewalker.go
and pkg/idutil/containerwalker/containerwalker.go.

Added TestNameFilterFor covering a bare repository name (now matches any
tag), an explicit tag, an explicit ":latest" tag, a digest, and a bare
name with a domain that needs regex-escaping. Verified this test fails
without the fix, reproducing the exact reported behaviour
(name==docker.io/library/myapp:latest instead of a repo-wide match).

Fixes containerd#5113

Signed-off-by: ankit090701 <ankitanku090701@gmail.com>
@ankit090701

Copy link
Copy Markdown
Contributor Author

The 5 red CI jobs are all pre-existing environmental flakiness, unrelated to this change - I traced each one down to the actual assertion/error, not just the test name:

  • in-host / docker < linux - TestAttachForAutoRemovedContainer: fails on assertion failed: strings.Contains(helpers.Capture("ps", "-a"), data.Identifier()) - a timing race where the container (started with implicit --rm) is auto-removed by the time docker ps -a runs.
  • in-host / rootful linux - TestBuildContextWithOCILayout (tar: Unexpected EOF in archive) and TestSaveMultipleImagesWithSameIDAndLoad (short read: expected 2811947 bytes but got 2785280: unexpected EOF) both fail on truncated/corrupted archives in the same job - looks like runner-level disk/IO trouble, not test logic.
  • in-host / rootful linux (arm) and in-host / rootless linux (arm) - fail before any test runs, in test-integration-env.sh install, with E: Sub-process /usr/bin/dpkg returned an error code (1) - pure environment provisioning failure.
  • in-host / windows - TestNetworkInspectWithContainers fails with hcnCreateNetwork failed in Win32: The object already exists. - leftover Windows HNS network state from a previous run on the same runner. This matches the same flaky category as [CI/Windows]: TestNetworkInspect/Verify_that_only_active_containers_appear_in_the_network_inspect_output is flaky #4344.

None of these touch cmd/nerdctl/image/image_list.go or nameFilterFor: the failing tests live in the container, builder, and network packages, and the one failure in the same package as my change (TestSaveMultipleImagesWithSameIDAndLoad) calls images with no name argument, so it never reaches the code this PR touches. My new test, TestNameFilterFor, passed in every job.

I don't have permission to re-run the failed jobs on this repo (gh run rerun says the run "cannot be rerun"), so flagging here in case a maintainer wants to re-trigger.

@AkihiroSuda AkihiroSuda added this to the v2.4.0 milestone Aug 1, 2026

@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

@AkihiroSuda
AkihiroSuda merged commit 802f71b into containerd:main Aug 1, 2026
57 of 68 checks passed
@mloskot

mloskot commented Aug 2, 2026

Copy link
Copy Markdown

@ankit090701 Thanks!

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 image ls myapp shows nothing for images without latest tag

3 participants