Skip to content

ci(docker-publish): serialise publishes so two runs cannot race :latest - #106

Merged
acamarata merged 1 commit into
mainfrom
ci/docker-publish-concurrency
Sep 12, 2026
Merged

acamarata merged 1 commit into
mainfrom
ci/docker-publish-concurrency

Conversation

@acamarata

Copy link
Copy Markdown
Collaborator

Three triggers feed this workflow — tag push, cli-release dispatch, manual dispatch — and nothing stopped them overlapping.

This fired for real today. Two dispatches for 1.3.6 ran simultaneously (17:02:37 and 17:04:52); one was cancelled by hand. Both would have pushed :X.Y.Z, :X.Y, :X and :latest. The last writer of :latest wins, so a slower run of an older version can land on top of a newer one and silently become what every docker pull nself/nself-admin receives.

#105 had just removed release.yml's ungated publisher for the same class of reason. This is that problem one layer up — between two runs of the gated publisher itself.

Two deliberate choices

The group is a constant, not the version. Serialising per-version would still let 1.3.6 and 1.3.7 race, and the shared floating tags are exactly what they contend for. One image, one publisher.

cancel-in-progress: false. Cancelling a publish mid-push is not a safe stop — that is what happened to release.yml on v1.3.6, killed partway through pushing with the manifest unfinished. A queued run costs minutes; a half-pushed manifest is a broken :latest.

Trade-off, stated rather than hidden

GitHub keeps only one pending run per group, so a third request cancels the second while it waits. That is the right outcome for the duplicate-dispatch case this fixes (same version twice — drop the redundant one). For three distinct versions queued at once it would drop the middle one. The Hygiene release-tag gate catches a version whose image never published, and releases here are sequential and rare.

Verified: pure addition (28 insertions, 0 deletions, no existing key changed); workflow still parses with all three triggers and the docker-publish job intact.

Three triggers feed this workflow -- tag push, cli-release dispatch and
manual dispatch -- and nothing stopped them overlapping. On 2026-09-12 two
dispatches for 1.3.6 ran at once (17:02:37 and 17:04:52) and one had to be
cancelled by hand.

Both would have pushed :X.Y.Z, :X.Y, :X and :latest. The last writer of
:latest wins, so a slower run of an OLDER version can land on top of a
newer one and silently become what every `docker pull nself/nself-admin`
receives. #105 had just removed release.yml's ungated publisher for the
same class of reason; this is the same problem one layer up, between two
runs of the gated publisher itself.

The group is a constant, not the version. Serialising per-version would
still let 1.3.6 and 1.3.7 race, and the shared floating tags are exactly
what they contend for. One image, one publisher.

cancel-in-progress is false deliberately. Cancelling a publish mid-push is
not a safe stop -- that is what happened to release.yml on v1.3.6, killed
partway through pushing with the manifest unfinished. A queued run costs
minutes; a half-pushed manifest is a broken :latest.

Trade-off worth stating: GitHub keeps only one pending run per group, so a
third request cancels the second while it waits. That is correct for the
duplicate-dispatch case this fixes (same version twice -- drop the
redundant one). For three distinct versions queued at once it would drop
the middle; the Hygiene release-tag gate catches a version whose image
never published, and releases here are sequential and rare.

Pure addition: no existing key changed. Verified the workflow still parses
with all three triggers and the docker-publish job intact.
@acamarata
acamarata merged commit 975e9b6 into main Sep 12, 2026
32 checks passed
@acamarata
acamarata deleted the ci/docker-publish-concurrency branch September 12, 2026 19:32
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.

1 participant