Skip to content

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

Open
ekalinin wants to merge 1 commit into
containerd:mainfrom
ekalinin:feat/push-all-tags
Open

feat(push): support --all-tags to push all tags#5133
ekalinin wants to merge 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 from dd35a5b to f153bf6 Compare August 8, 2026 18:25
`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 (the same idiom nameFilterFor() uses
for `nerdctl image ls`) 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
v1 index is attached through the referrers fallback tag ("sha256-<digest>")
on registries without the referrers API.

Closes containerd#3751

Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
@ekalinin
ekalinin force-pushed the feat/push-all-tags branch from 34b1ba5 to b8ceb3d Compare August 8, 2026 19:22
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

1 participant