From b7874eab1d0610179e20e6eb540413a15523017d Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Wed, 23 Sep 2026 00:28:35 +0200 Subject: [PATCH] ci: publish the image for amd64 and arm64 natively The publish job built a single-arch image on ubuntu-latest, so every release manifest carried linux/amd64 only. Split it into a version job that exports the semver tag and image name, a matrix publish job that builds each architecture on its own native runner (ubuntu-latest, ubuntu-24.04-arm) and pushes an arch-suffixed tag, and a manifest job that joins both under the release tag. Native runners avoid the QEMU emulation layer. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 69 +++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96bd9fa..cd59a37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,8 @@ jobs: contents: read security-events: write - publish: - name: Publish image + version: + name: Detect release version runs-on: ubuntu-latest needs: - security @@ -41,7 +41,10 @@ jobs: if: github.event_name == 'push' permissions: contents: read - packages: write + outputs: + found: ${{ steps.semver.outputs.found }} + version: ${{ steps.semver.outputs.version }} + image: ${{ steps.image.outputs.name }} steps: - name: Checkout repository @@ -64,16 +67,34 @@ jobs: fi - name: Compute image name - if: steps.semver.outputs.found == 'true' id: image run: echo "name=ghcr.io/$(echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + publish: + name: Publish image (${{ matrix.arch }}) + runs-on: ${{ matrix.runner }} + needs: + - version + if: needs.version.outputs.found == 'true' + permissions: + contents: read + packages: write + strategy: + matrix: + include: + - arch: amd64 + runner: ubuntu-latest + - arch: arm64 + runner: ubuntu-24.04-arm + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up Docker Buildx - if: steps.semver.outputs.found == 'true' uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Login to GHCR - if: steps.semver.outputs.found == 'true' uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io @@ -81,10 +102,42 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and publish image - if: steps.semver.outputs.found == 'true' uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 with: context: . file: ./Dockerfile + platforms: linux/${{ matrix.arch }} push: true - tags: ${{ steps.image.outputs.name }}:${{ steps.semver.outputs.version }} + tags: ${{ needs.version.outputs.image }}:${{ needs.version.outputs.version }}-${{ matrix.arch }} + + manifest: + name: Join architectures + runs-on: ubuntu-latest + needs: + - version + - publish + permissions: + contents: read + packages: write + + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - name: Login to GHCR + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Create the multi-arch manifest + env: + IMAGE: ${{ needs.version.outputs.image }} + VERSION: ${{ needs.version.outputs.version }} + run: | + set -euo pipefail + docker buildx imagetools create -t "${IMAGE}:${VERSION}" \ + "${IMAGE}:${VERSION}-amd64" \ + "${IMAGE}:${VERSION}-arm64" + docker buildx imagetools inspect "${IMAGE}:${VERSION}"