From 4a7100e0c9b9c80a8369a195f75b3f7ec9ccc60f Mon Sep 17 00:00:00 2001 From: Janik Besendorf Date: Tue, 15 Sep 2026 03:58:12 +0200 Subject: [PATCH] Build Docker images on native AMD64 and ARM64 runners --- .github/workflows/publish-release-docker.yml | 97 ++++++++++++++++++-- 1 file changed, 88 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish-release-docker.yml b/.github/workflows/publish-release-docker.yml index a90202d2..18f50e24 100644 --- a/.github/workflows/publish-release-docker.yml +++ b/.github/workflows/publish-release-docker.yml @@ -14,10 +14,10 @@ env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} -# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +# Build each architecture natively, then publish the combined image manifests. jobs: build-and-push-image: - runs-on: ubuntu-latest + runs-on: ${{ matrix.architecture.runner }} # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. permissions: contents: read @@ -26,7 +26,15 @@ jobs: id-token: write # strategy: + fail-fast: false matrix: + architecture: + - platform: linux/amd64 + runner: ubuntu-24.04 + name: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + name: arm64 platform: - dockerfile: "Dockerfile" tag-suffix: "" @@ -61,24 +69,95 @@ jobs: type=raw,enable=${{ github.ref_type == 'tag' }},value=${{ github.ref_name }},suffix=${{ matrix.platform.tag-suffix }} type=sha,suffix=${{ matrix.platform.tag-suffix }} type=sha,format=long,suffix=${{ matrix.platform.tag-suffix }} - # Register emulators so Buildx can build the ARM64 images on the AMD64 runner. - - name: Set up QEMU - uses: docker/setup-qemu-action@v4 # This step sets up some additional capabilities to generate the provenance and sbom attestations - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. - # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + # Push by digest so architecture builds do not overwrite shared image tags. - name: Build and push Docker image id: push uses: docker/build-push-action@v7 with: file: ${{ matrix.platform.dockerfile }} context: . - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} + platforms: ${{ matrix.architecture.platform }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true labels: ${{ steps.meta.outputs.labels }} provenance: mode=max sbom: true + - name: Export image digest + env: + DIGEST: ${{ steps.push.outputs.digest }} + run: | + mkdir -p "${RUNNER_TEMP}/digests" + touch "${RUNNER_TEMP}/digests/${DIGEST#sha256:}" + - name: Upload image digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.platform.dockerfile }}-${{ matrix.architecture.name }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + publish-manifests: + needs: build-and-push-image + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + strategy: + matrix: + platform: + - dockerfile: "Dockerfile" + tag-suffix: "" + - dockerfile: "Dockerfile.ios" + tag-suffix: "-ios" + - dockerfile: "Dockerfile.android" + tag-suffix: "-android" + steps: + - name: Download image digests + uses: actions/download-artifact@v4 + with: + pattern: digests-${{ matrix.platform.dockerfile }}-* + path: ${{ runner.temp }}/digests + merge-multiple: true + - name: Log in to the Container registry + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + flavor: | + latest=false + tags: | + type=raw,value=latest,enable={{ is_default_branch }},suffix=${{ matrix.platform.tag-suffix }} + type=raw,enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }},value=stable,suffix=${{ matrix.platform.tag-suffix }} + type=raw,enable=${{ github.event_name == 'release' }},value=${{ github.event.release.tag_name }},suffix=${{ matrix.platform.tag-suffix }} + type=raw,enable=${{ github.ref_type == 'tag' }},value=${{ github.ref_name }},suffix=${{ matrix.platform.tag-suffix }} + type=sha,suffix=${{ matrix.platform.tag-suffix }} + type=sha,format=long,suffix=${{ matrix.platform.tag-suffix }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + - name: Publish multi-platform image + working-directory: ${{ runner.temp }}/digests + env: + METADATA_JSON: ${{ steps.meta.outputs.json }} + run: | + mapfile -t tags < <(jq -r '.tags[]' <<< "$METADATA_JSON") + tag_args=() + for tag in "${tags[@]}"; do + tag_args+=(--tag "$tag") + done + images=() + for digest in *; do + images+=("${REGISTRY}/${IMAGE_NAME}@sha256:${digest}") + done + test "${#images[@]}" -eq 2 + docker buildx imagetools create "${tag_args[@]}" "${images[@]}" + docker buildx imagetools inspect "${tags[0]}"