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.
This commit is contained in:
Andras Bacsai 2026-07-19 23:00:47 +02:00
parent a92ca6e3b8
commit 7716e65eea
3 changed files with 149 additions and 40 deletions

View file

@ -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 }}

107
.github/workflows/coolify-release.yml vendored Normal file
View file

@ -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 }}

View file

@ -0,0 +1,34 @@
<?php
it('publishes v4 branch builds only under the commit sha', function () {
$workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-production-build.yml');
expect($workflow)
->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"');
});