From c18e26f79817e9ed8e27d3fbf9a48398d22f52f7 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 12 Sep 2026 22:19:27 +0100 Subject: [PATCH] ci: keep prereleases off the latest tag `release: published` fires for prereleases too, and every manifest job pushed `latest` unconditionally, so publishing a release candidate by hand would have made it the image everyone pulls. The backend image had the same hole, so fix all three rather than only the two added here. A release the backend-release workflow calls is always stable, so the workflow_call path keeps moving `latest`; the release-event path moves it only when the release is not a prerelease. --- .github/workflows/ci.yml | 8 ++++++-- .github/workflows/cife.yml | 9 ++++++++- .github/workflows/sandbox-image.yml | 6 ++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc780607..b2985af5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,11 +142,15 @@ jobs: - name: Create and push multi-arch manifests env: TAG: ${{ env.RELEASE_TAG }}${{ matrix.variant }} - LATEST: latest${{ matrix.variant }} + # A stable release moves `latest`; a prerelease published by hand + # moves only its own tag (the release trigger fires for those too). + LATEST: ${{ (inputs.version || !github.event.release.prerelease) && format('latest{0}', matrix.variant) || '' }} run: | set -e + # $LATEST is deliberately unquoted: it is empty for a prerelease, and + # an empty word would create a manifest named "$repo:". for repo in "$DOCKERHUB_NAMESPACE/docsgpt" "ghcr.io/${{ github.repository_owner }}/docsgpt"; do - for name in "$TAG" "$LATEST"; do + for name in "$TAG" $LATEST; do docker manifest create "$repo:$name" \ --amend "$repo:$TAG-amd64" \ --amend "$repo:$TAG-arm64" diff --git a/.github/workflows/cife.yml b/.github/workflows/cife.yml index c8f629f5..6c8c301f 100644 --- a/.github/workflows/cife.yml +++ b/.github/workflows/cife.yml @@ -26,6 +26,10 @@ permissions: env: # The tag being published: passed in by the caller, or the release's own. RELEASE_TAG: ${{ inputs.version || github.event.release.tag_name }} + # A stable release also moves `latest`. A prerelease published by hand moves + # only its own tag: the release trigger fires for those too, and they must not + # become `latest`. + MOVING_TAG: ${{ (inputs.version || !github.event.release.prerelease) && 'latest' || '' }} jobs: build: @@ -139,10 +143,13 @@ jobs: - name: Create and push multi-arch manifests env: TAG: ${{ env.RELEASE_TAG }} + MOVING: ${{ env.MOVING_TAG }} run: | set -e + # $MOVING is deliberately unquoted: it is empty for a prerelease, and + # an empty word would create a manifest named "$repo:". for repo in "$DOCKERHUB_NAMESPACE/docsgpt-fe" "ghcr.io/${{ github.repository_owner }}/docsgpt-fe"; do - for name in "$TAG" latest; do + for name in "$TAG" $MOVING; do docker manifest create "$repo:$name" \ --amend "$repo:$TAG-amd64" \ --amend "$repo:$TAG-arm64" diff --git a/.github/workflows/sandbox-image.yml b/.github/workflows/sandbox-image.yml index 4541be4f..c26e5b8d 100644 --- a/.github/workflows/sandbox-image.yml +++ b/.github/workflows/sandbox-image.yml @@ -38,8 +38,10 @@ permissions: env: # The version being published, or `develop` for a push to main. RELEASE_TAG: ${{ inputs.version || github.event.release.tag_name || 'develop' }} - # A release also moves `latest`; a push to main moves nothing but `develop`. - MOVING_TAG: ${{ (inputs.version || github.event.release.tag_name) && 'latest' || '' }} + # A stable release also moves `latest`. A push to main moves nothing but + # `develop`, and a prerelease published by hand moves only its own tag: the + # release trigger fires for those too, and they must not become `latest`. + MOVING_TAG: ${{ (inputs.version || (github.event.release.tag_name && !github.event.release.prerelease)) && 'latest' || '' }} jobs: build: