Give a pull request build a tag that moves - #113
Merged
indiebrain merged 2 commits intoSep 4, 2026
Conversation
Every commit on an open branch publishes an image tagged with its own commit sha, and that tag never moves or expires. Only the newest of them is useful, so roughly five of every six branch images are superseded the moment the next commit lands and then stay in the package permanently. At two to three pull requests a day producing ten to eighteen builds, this is what fills the operator image package. Tag a pull request build with `pr-<number>` instead, which moves to the newest image on each push to that branch. The image it replaces keeps no tag, its platform children stop being reachable, and the scheduled prune removes it a day later, so the growth is collected by the cleanup that already runs rather than by a new rule that deletes tagged versions. Builds of master and of release tags are unchanged: both keep a unique commit-sha tag, and a release keeps its semantic version tag as well. An image built for a pull request is addressable while the branch is the one being worked on rather than forever, which trades away the ability to pull a superseded commit's image. Measured against the last 56 closed pull requests, half close within ten hours and all within thirteen days.
rodolfoasantos
approved these changes
Sep 4, 2026
indiebrain
deleted the
2026-09-03--tag-pull-request-images-with-a-moving-tag
branch
September 4, 2026 11:32
Merged
indiebrain
added a commit
that referenced
this pull request
Sep 4, 2026
Prepare the repository for release of version v4.6.0. Two files change: `Makefile` sets the version the tag is cut from, and `CHANGELOG.md` moves the `Unreleased` entries under a dated heading. ## Read this before upgrading **A `RedisFailover` that has `auth.secretPath` set restarts once when the operator is upgraded.** Its pod template gains a record of which password it was built for, and that record is new. A failover with no password is unaffected. The Redis pods go together rather than one at a time, because a restarted pod cannot replicate with one that has not restarted yet. Expect a minute or two, a few tens of seconds of it without a master. **This also reaches anyone tracking `latest`.** `docker/metadata-action` runs with the default `flavor.latest=auto`, so a `v*` tag build moves `latest`. Verified on the current release: `latest` and `v4.5.0` share digest `sha256:013e238e59fe…`, and `latest` was written seven seconds after `v4.5.0` in the same run and has not moved since. Publishing v4.6.0 moves `latest` to it. ## This release ships **Five fixes to behavior against a live cluster:** - Removing the `haproxy` block from a `RedisFailover` now removes the Deployment, Service and ConfigMap it created, instead of leaving a proxy routing traffic from a configuration nothing would update again (#107). - A Redis node that could not be inspected no longer counts as "not the master", which could drive recovery into promoting a second master or replacing a healthy one. A failed demotion no longer reports success (#106). - The HAProxy health check authenticates, so `auth.secretPath` is usable at all. Without it every backend stayed `DOWN` and the master endpoint had no backend (#98). - The connected-replicas check authenticates, so the operator stops killing a healthy master's replica connections on every reconcile under `requirepass` (#102). - A password change on a running `RedisFailover` is applied rather than leaving Redis on the password it started with and the operator locked out (#104). **Supply chain and image:** - Built with Go 1.25.14 with `golang.org/x/net` at v0.58.0, taking the reachable vulnerability count to zero, and the base images pinned by digest (#108). - The image carries a Software Bill of Materials, and provenance moves to `max` (#109). - Published to `ghcr.io/powerhome` alongside Docker Hub (#93). ## What this does to the open issues Referenced, not closed. Closing is left to a person once the claims are true of a published artifact. | Issue | State after this release | | --- | --- | | #99 (connected-replica check omits password) | Fixed by #102. | | #101 (v4.5.0 image has fixable vulnerabilities) | Fixed by #108. The issue reports against `v4.5.0`, which predates the fix, so it is only true of an artifact once v4.6.0 is published. | | #100 (no-quorum recovery can promote wrongly) | **Partially** addressed by #106. `SetOldestAsMaster` still sorts candidates by pod `CreationTimestamp` (`operator/redisfailover/service/heal.go:115`), which #100's acceptance criteria rule out directly. No integration coverage was added either: #106 records that the harness cannot induce the selective inspection failure the issue's reproduction turns on. Do not close it. | ## Changelog style The entries collected for this release had grown to multi-paragraph bodies running 88 lines. Every release entry from v4.5.0 back is a single line naming the change and linking its pull request, in blocks of five to eight lines. These are reduced to one line each to match. The reasoning lives in the linked pull requests, and the long form goes into the v4.6.0 GitHub Release body, which is what line 8 of the changelog already points readers to. The one thing a reader must not miss, the restart above, is kept in the file as an upgrade note above the entries. ## A correction to the changelog The changelog carried `## [v4.5.1] - 2026-08-18`. **No such release exists**: no `v4.5.1` git tag, and no `v4.5.1` image on Docker Hub or in the ghcr package. The heading was added by feature pull request #93 rather than by a release commit, and it is dated three days before #93 merged. The heading is removed. This file lists releases, and a heading for a version with no tag, no image and no release notes points a reader at something they cannot go and get. The record that the claim was retracted belongs in this repository's history, which carries it, rather than in the file. The change it listed ships here. ## Not included Workflow and continuous-integration changes (#95, #96, #110, #111, #112, #113, #114) are left out, along with the `/etc/cni/net.d` setup-minikube workaround that rode in on #93. They change no published artifact and no operator behavior, and the GitHub Release auto-notes list every merged pull request anyway. ## After this merges Capture the squash commit rather than tagging `HEAD` — this repository squash-merges, so the commit to tag does not exist until the merge and is not the branch head: ``` sha=$(gh pr view 115 --json mergeCommit -q .mergeCommit.oid) git fetch origin --tags git show $sha:Makefile | head -1 # must read VERSION := v4.6.0 git tag -a v4.6.0 -m "v4.6.0" "$sha" git push origin v4.6.0 ``` Then, once the tag build is green: ``` gh release create v4.6.0 --title v4.6.0 --notes-file <the long-form notes> ``` Order matters: `gh release create` against a tag that does not exist yet creates one from the default branch, which tags the wrong commit and fires another build. If the tag build fails, re-run the failed jobs. Do not delete and re-push the tag: re-running is idempotent since the bases are digest-pinned, while a deleted public `v*` tag also drops out of the image prune workflow's `exclude-tags: 'v*'` protection. ## Verification - `## Unreleased` is retained and empty, matching how v4.5.0, v4.4.1 and v4.4.0 were cut. - The v4.6.0 block is 24 lines against 5 to 8 for its neighbours, with one `### Added`, one `### Changed`, one `### Fixed`. The previous `Unreleased` section had two `Added` and two `Fixed` sections interleaved. - This will be the first semantic version image published to `ghcr.io`, so the first real exercise of `exclude-tags: 'v*'` in the prune workflow. Dry-run that workflow afterwards and confirm `v4.6.0` appears in no deletion list. - It is also the first tag build to carry a bill of materials, since #109 landed after v4.5.0. Worth confirming rather than assuming: `docker buildx imagetools inspect powerhome/redis-operator:v4.6.0 --format '{{ json .SBOM }}'`
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.
The problem
Every commit on an open branch publishes an image tagged with its own commit sha, and that tag never moves and never expires. Only the newest image of a branch is useful, so the rest are superseded the moment the next commit lands and then stay in the package permanently.
The rate makes it the dominant source of growth. Two to three pull requests are opened a day, but ten to eighteen builds are published a day, so roughly five in six published images are dead on arrival. Each build writes 11 versions: one index, five per-platform manifests, five attestation manifests.
The change
<40-char sha>, unique and permanentpr-123, moves to the newest imagemaster<40-char sha>v*tagv4.5.0+<40-char sha>When
pr-123moves, the index it pointed at keeps no tag and its platform children stop being reachable. That is exactly what the scheduled prune already deletes, so this routes the growth into cleanup that is already merged and has been observed running clean, instead of adding a rule that deletes tagged versions.The guard is written as
github.event_name != 'pull_request'rather than{{is_default_branch}}on purpose: the latter is false on a tag push, which would strip the commit-sha tag off release images.What this gives up
A pull request image is addressable while the branch is the one being worked on, rather than forever.
pr-123gives you that branch's current image, not the one from three commits ago.Measured against the last 56 closed pull requests in this repository: half close within about ten hours, 93% within seven days, and all within thirteen days. An image for an open pull request keeps being refreshed by each push, so it stays available for as long as the work is live.
Verification
make lint-workflowspasses. The behavior this changes only appears once a build runs for a pull request, which this pull request is, so its owndockerhub-imagejob is the first exercise of it: it should publishpr-<this number>and no commit-sha tag.Nothing else in the workflow depends on the pull request case. The "Verify the ghcr package is publicly pullable" step reads
steps.meta.outputs.version, which becomespr-<number>on a pull request, but that step is already guarded withif: github.event_name != 'pull_request'.DOCKER_METADATA_PR_HEAD_SHAstays, since it still selects the head sha for theorg.opencontainers.image.revisionlabel.Not in this change
Finished pull requests still leave one image each, which wants a second cleanup step (
delete-tags: 'pr-*'with its ownolder-than). That has to be a separate invocation of the action, becauseolder-thanrestricts every rule in a single step and orphan cleanup wants a much shorter interval than finished pull requests do. It is worth landing only after this change has been seen working, since it has nothing to act on untilpr-*tags exist.