From 0cec4110878e2d0527bbc5721e60aedc545a3aae Mon Sep 17 00:00:00 2001 From: Tonio Date: Sat, 8 Aug 2026 17:48:57 -0700 Subject: [PATCH] ci(docker): never emit latest/v* from a manual preview build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workflow_dispatch `ref` input is meant to preview an unmerged branch, and the only ref guaranteed to carry this workflow after merge is master — so the expected way to run it is dispatch-from-master with `ref` set to the branch. But on a workflow_dispatch dispatched from master, `github.ref` is the default branch, so `type=raw,value=latest,enable={{is_default_branch}}` still fired: a preview image would move `latest` and poison every host that pulls it. `type=semver` was likewise ungated (a dispatch from a v* tag would have minted a release tag pointing at the previewed ref). Gate both off for workflow_dispatch so a manual run can only ever produce the `sha-` tag. The `latest` gate now checks the default branch explicitly instead of `{{is_default_branch}}` so it can also exclude the dispatch case; on push:master and tag:v* the emitted tags are unchanged. Tested: js-yaml and ruby YAML both parse the file; grep confirms both the self-hosted and cloud meta blocks are gated symmetrically. Co-Authored-By: Paperclip --- .github/workflows/docker.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 01d556be072..6eb3f2b4fbd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -169,9 +169,15 @@ jobs: with: images: ghcr.io/${{ github.repository }} tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} + # `latest` and `v*` are release pointers. Gate them off for manual + # (workflow_dispatch) runs — even one dispatched from master — so a + # preview build can never move `latest` or mint a release tag. The + # explicit default-branch check replaces `{{is_default_branch}}`, + # which is also true on a dispatch from master and would otherwise + # leak `latest` onto a preview image. + type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch && github.event_name != 'workflow_dispatch' }} + type=semver,pattern={{version}},enable=${{ github.event_name != 'workflow_dispatch' }} + type=semver,pattern={{major}}.{{minor}},enable=${{ github.event_name != 'workflow_dispatch' }} # push/tag builds keep the stock sha tag (sha- of github.sha). # A manual run may build a ref other than github.sha, so it tags the # resolved commit instead — same sha- format, correct commit. @@ -353,9 +359,15 @@ jobs: flavor: | suffix=-cloud,onlatest=true tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} + # `latest` and `v*` are release pointers. Gate them off for manual + # (workflow_dispatch) runs — even one dispatched from master — so a + # preview build can never move `latest` or mint a release tag. The + # explicit default-branch check replaces `{{is_default_branch}}`, + # which is also true on a dispatch from master and would otherwise + # leak `latest` onto a preview image. + type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch && github.event_name != 'workflow_dispatch' }} + type=semver,pattern={{version}},enable=${{ github.event_name != 'workflow_dispatch' }} + type=semver,pattern={{major}}.{{minor}},enable=${{ github.event_name != 'workflow_dispatch' }} # push/tag builds keep the stock sha tag (sha- of github.sha). # A manual run may build a ref other than github.sha, so it tags the # resolved commit instead — same sha- format, correct commit.