Skip to content

feat(push): support --all-tags to push all tags - #5133

Merged
AkihiroSuda merged 1 commit into
containerd:mainfrom
ekalinin:feat/push-all-tags
Aug 22, 2026
Merged

feat(push): support --all-tags to push all tags#5133
AkihiroSuda merged 1 commit into
containerd:mainfrom
ekalinin:feat/push-all-tags

Conversation

@ekalinin

@ekalinin ekalinin commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Closes #3751.

nerdctl push accepts a bare repository name, but referenceutil.Parse normalizes it to :latest, so only that single tag is pushed. This adds the Docker-compatible -a, --all-tags flag, which pushes every local tag of the repository instead.

Push() is split into a dispatcher and pushSingle(). With --all-tags the local tags are resolved through the name~=^<repo>: image filter - the same idiom nameFilterFor() uses for nerdctl image ls - then sorted by name and pushed one by one. The -tmp-reduced-platform / -tmp-esgz images push creates for itself are skipped, so an interrupted push cannot leak one into the registry as a real tag.

A tag or a digest in the reference is rejected, as docker push --all-tags does. The check looks at ExplicitTag, not Tag: Parse() runs TagNameOnly(), so Tag is "latest" even for a bare repository name.

The issue also reports that pushing several tags of one image overwrites the SOCI index each time. A SOCI index is attached to the image manifest rather than to the tag, so it is now built once per distinct target digest - deduplicating by digest rather than by position keeps each image indexed when the tags differ.

Tests: four sub-tests in TestPush covering all tags pushed (verified against /v2/<repo>/tags/list), explicit tag rejected, no local tags rejected, and SOCI with --all-tags.

Note: #4627 is an earlier attempt at this issue, inactive since December 2025. This is an independent implementation. For whoever picks that one up: its guard is parsedReference.Tag != "", which is always true after TagNameOnly() normalization, so --all-tags there always fails with tag can't be used with --all-tags/-a.

@ekalinin
ekalinin force-pushed the feat/push-all-tags branch 2 times, most recently from 34b1ba5 to b8ceb3d Compare August 8, 2026 19:22
@haytok
haytok requested review from haytok and a lite review from Copilot August 12, 2026 16:14
Comment thread cmd/nerdctl/image/image_push_linux_test.go
Comment thread pkg/cmd/image/push.go Outdated
Comment thread pkg/cmd/image/push.go Outdated
Comment thread pkg/cmd/image/push.go
Comment thread pkg/cmd/image/push.go Outdated
@AkihiroSuda AkihiroSuda added this to the v2.4.0 milestone Aug 19, 2026
@AkihiroSuda

Copy link
Copy Markdown
Member

Please squash the commits

@AkihiroSuda
AkihiroSuda requested a review from ktock August 19, 2026 12:34

@haytok haytok 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.

LGTM

`nerdctl push` accepts a bare repository name, but referenceutil.Parse
normalizes it to ":latest", so only that single tag is pushed. Add the
Docker-compatible `-a, --all-tags` flag, which pushes every local tag of
the repository instead.

Push is split into a dispatcher and pushSingle(): without --all-tags the
dispatcher just delegates, with it the local tags are resolved through
the `name~=^<repo>:` image filter and pushed one by one. The temporary
images push creates for itself are skipped, so an interrupted push cannot
leak a "-tmp-reduced-platform" tag into the registry, and the list is
sorted because ImageService().List() guarantees no order.

A tag or a digest in the reference is rejected, as docker does. The check
looks at ExplicitTag rather than Tag: Parse() runs TagNameOnly(), so Tag
is "latest" even for a bare repository name.

A SOCI index is attached to the image manifest rather than to the tag, so
it is now built once per distinct target digest. Pushing several tags of
one image no longer makes each tag overwrite the index pushed by the
previous one.

Pushing more than once per process also uncovered a bug in the plain HTTP
fallback. pushImageWithLocal builds a fresh in-memory tracker per push,
but the fallback rebuilt the resolver through dockerconfigresolver.New,
which silently substitutes the process-wide PushTracker. containerd's
dockerPusher keys that tracker by content ref ("index-<digest>"), not by
reference, and returns ErrAlreadyExists before issuing any request when
the digest is already committed; remotes.push() treats that as success,
so the manifest PUT that creates the tag never happens and the command
still exits 0. Rebuild the resolver from the host options instead,
reusing the resolver options assembled above so the fallback keeps the
per-push tracker.

The tests assert that the pushed tags are present in the registry rather
than that they are the only ones: the listing is a superset, since a SOCI
index is attached through the referrers fallback tag ("sha256-<hex>") on
registries without the referrers API. That fallback tag is also what the
SOCI sub-test checks to confirm the index reached the registry, since a
push without SOCI never creates one.

Closes containerd#3751

Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
@ekalinin
ekalinin force-pushed the feat/push-all-tags branch from 2aaaff0 to 349e1a8 Compare August 19, 2026 18:43
@ekalinin

Copy link
Copy Markdown
Contributor Author

Please squash the commits

Done.

@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 8da5040 into containerd:main Aug 22, 2026
83 of 90 checks passed
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 push --all-tags to push all tags

4 participants