From 7716e65eeafde98f763920268fe7e67e035a0e42 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 19 Jul 2026 23:00:47 +0200 Subject: [PATCH] ci(release): promote SHA-tagged images on release Publish branch builds under immutable commit SHA tags, then promote the released image to its version tag and stable releases to latest. --- .../workflows/coolify-production-build.yml | 48 ++------ .github/workflows/coolify-release.yml | 107 ++++++++++++++++++ tests/Unit/ProductionImageWorkflowTest.php | 34 ++++++ 3 files changed, 149 insertions(+), 40 deletions(-) create mode 100644 .github/workflows/coolify-release.yml create mode 100644 tests/Unit/ProductionImageWorkflowTest.php diff --git a/.github/workflows/coolify-production-build.yml b/.github/workflows/coolify-production-build.yml index 5ccb43a8e..a5fe461f5 100644 --- a/.github/workflows/coolify-production-build.yml +++ b/.github/workflows/coolify-production-build.yml @@ -3,17 +3,6 @@ name: Production Build (v4) on: push: branches: ["v4.x"] - paths-ignore: - - .github/workflows/coolify-helper.yml - - .github/workflows/coolify-helper-next.yml - - .github/workflows/coolify-realtime.yml - - .github/workflows/coolify-realtime-next.yml - - .github/workflows/pr-quality.yaml - - docker/coolify-helper/Dockerfile - - docker/coolify-realtime/Dockerfile - - docker/testing-host/Dockerfile - - templates/** - - CHANGELOG.md permissions: contents: read @@ -55,11 +44,6 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Get Version - id: version - run: | - echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT - - name: Build and Push Image (${{ matrix.arch }}) uses: docker/build-push-action@v6 with: @@ -68,17 +52,13 @@ jobs: platforms: ${{ matrix.platform }} push: true tags: | - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-${{ matrix.arch }} - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-${{ matrix.arch }} + ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-${{ matrix.arch }} + ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-${{ matrix.arch }} merge-manifest: runs-on: ubuntu-24.04 needs: build-push steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - uses: docker/setup-buildx-action@v3 - name: Login to ${{ env.GITHUB_REGISTRY }} @@ -95,28 +75,16 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Get Version - id: version - run: | - echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT - - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }} run: | docker buildx imagetools create \ - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-amd64 \ - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \ - --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \ - --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest + ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-amd64 \ + ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-aarch64 \ + --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }} run: | docker buildx imagetools create \ - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-amd64 \ - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}-aarch64 \ - --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.VERSION }} \ - --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest - - - uses: sarisia/actions-status-discord@v1 - if: always() - with: - webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }} + ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-amd64 \ + ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }}-aarch64 \ + --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ github.sha }} diff --git a/.github/workflows/coolify-release.yml b/.github/workflows/coolify-release.yml new file mode 100644 index 000000000..9cd3b90e5 --- /dev/null +++ b/.github/workflows/coolify-release.yml @@ -0,0 +1,107 @@ +name: Release Coolify + +on: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + GITHUB_REGISTRY: ghcr.io + DOCKER_REGISTRY: docker.io + IMAGE_NAME: coollabsio/coolify + +jobs: + promote-image: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.release.tag_name }} + + - uses: docker/setup-buildx-action@v3 + + - name: Login to ${{ env.GITHUB_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.GITHUB_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to ${{ env.DOCKER_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Resolve release image + id: release + env: + TAG_NAME: ${{ github.event.release.tag_name }} + run: | + if [[ ! "${TAG_NAME}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then + echo "Unsupported release tag: ${TAG_NAME}" + exit 1 + fi + + VERSION="${TAG_NAME#v}" + RELEASE_SHA=$(git rev-list -n 1 "${TAG_NAME}") + CONFIG_VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php) + + if [[ "${CONFIG_VERSION}" != "${VERSION}" ]]; then + echo "Release tag ${VERSION} does not match config version ${CONFIG_VERSION}." + exit 1 + fi + + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "sha=${RELEASE_SHA}" >> "$GITHUB_OUTPUT" + + - name: Promote version on ${{ env.GITHUB_REGISTRY }} + env: + REGISTRY: ${{ env.GITHUB_REGISTRY }} + VERSION: ${{ steps.release.outputs.version }} + RELEASE_SHA: ${{ steps.release.outputs.sha }} + run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + SOURCE_TAG="sha-${RELEASE_SHA}" + docker buildx imagetools create "${IMAGE}:${SOURCE_TAG}" --tag "${IMAGE}:${VERSION}" + + - name: Promote version on ${{ env.DOCKER_REGISTRY }} + env: + REGISTRY: ${{ env.DOCKER_REGISTRY }} + VERSION: ${{ steps.release.outputs.version }} + RELEASE_SHA: ${{ steps.release.outputs.sha }} + run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + SOURCE_TAG="sha-${RELEASE_SHA}" + docker buildx imagetools create "${IMAGE}:${SOURCE_TAG}" --tag "${IMAGE}:${VERSION}" + + - name: Promote latest on ${{ env.GITHUB_REGISTRY }} + if: ${{ ! github.event.release.prerelease }} + env: + REGISTRY: ${{ env.GITHUB_REGISTRY }} + RELEASE_SHA: ${{ steps.release.outputs.sha }} + run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + SOURCE_TAG="sha-${RELEASE_SHA}" + docker buildx imagetools create "${IMAGE}:${SOURCE_TAG}" --tag "${IMAGE}:latest" + + - name: Promote latest on ${{ env.DOCKER_REGISTRY }} + if: ${{ ! github.event.release.prerelease }} + env: + REGISTRY: ${{ env.DOCKER_REGISTRY }} + RELEASE_SHA: ${{ steps.release.outputs.sha }} + run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + SOURCE_TAG="sha-${RELEASE_SHA}" + docker buildx imagetools create "${IMAGE}:${SOURCE_TAG}" --tag "${IMAGE}:latest" + + - uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_WEBHOOK_PROD_RELEASE_CHANNEL }} diff --git a/tests/Unit/ProductionImageWorkflowTest.php b/tests/Unit/ProductionImageWorkflowTest.php new file mode 100644 index 000000000..06085a33f --- /dev/null +++ b/tests/Unit/ProductionImageWorkflowTest.php @@ -0,0 +1,34 @@ +toContain('sha-${{ github.sha }}-${{ matrix.arch }}') + ->toContain('sha-${{ github.sha }}') + ->not->toContain('bootstrap/getVersion.php') + ->not->toContain('steps.version.outputs.VERSION') + ->not->toContain('IMAGE_NAME }}:latest'); +}); + +it('promotes the released commit image without rebuilding it', function () { + $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-release.yml'); + + expect($workflow) + ->toContain('release:') + ->toContain('types: [published]') + ->toContain('TAG_NAME: ${{ github.event.release.tag_name }}') + ->toContain('git rev-list -n 1 "${TAG_NAME}"') + ->toContain('SOURCE_TAG="sha-${RELEASE_SHA}"') + ->toContain('bootstrap/getVersion.php') + ->toContain('--tag "${IMAGE}:${VERSION}"') + ->not->toContain('docker/build-push-action'); +}); + +it('only promotes stable releases to latest', function () { + $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-release.yml'); + + expect($workflow) + ->toContain('if: ${{ ! github.event.release.prerelease }}') + ->toContain('--tag "${IMAGE}:latest"'); +});