feat(push): support --all-tags to push all tags - #5133
Open
ekalinin wants to merge 1 commit into
Open
Conversation
ekalinin
force-pushed
the
feat/push-all-tags
branch
from
August 8, 2026 18:25
dd35a5b to
f153bf6
Compare
`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
force-pushed
the
feat/push-all-tags
branch
from
August 8, 2026 19:22
34b1ba5 to
b8ceb3d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3751.
nerdctl pushaccepts a bare repository name, butreferenceutil.Parsenormalizes it to:latest, so only that single tag is pushed. This adds the Docker-compatible-a, --all-tagsflag, which pushes every local tag of the repository instead.Push()is split into a dispatcher andpushSingle(). With--all-tagsthe local tags are resolved through thename~=^<repo>:image filter - the same idiomnameFilterFor()uses fornerdctl image ls- then sorted by name and pushed one by one. The-tmp-reduced-platform/-tmp-esgzimagespushcreates 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-tagsdoes. The check looks atExplicitTag, notTag:Parse()runsTagNameOnly(), soTagis"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
TestPushcovering 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 afterTagNameOnly()normalization, so--all-tagsthere always fails withtag can't be used with --all-tags/-a.