From 9b250a679259b422f6d183eb1c29a6a5ef719dc8 Mon Sep 17 00:00:00 2001 From: mfaferek93 Date: Wed, 8 Jul 2026 22:27:12 +0200 Subject: [PATCH 1/2] ci: arm64 multi-arch image (tag/dispatch gated) Add native-runner multi-arch (amd64+arm64) publish for the gateway, gated on v* tags and workflow_dispatch only. Existing amd64-only docker-publish.yml (per-PR/push-to-main) is untouched so PR CI stays fast. Mirrors ros2_medkit_web_ui digest+manifest-merge pattern, extended with the ros_distro build-arg dimension. --- .../workflows/docker-publish-multiarch.yml | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 .github/workflows/docker-publish-multiarch.yml diff --git a/.github/workflows/docker-publish-multiarch.yml b/.github/workflows/docker-publish-multiarch.yml new file mode 100644 index 000000000..18100a903 --- /dev/null +++ b/.github/workflows/docker-publish-multiarch.yml @@ -0,0 +1,156 @@ +# Multi-arch (linux/amd64 + linux/arm64) image publish for the gateway. +# +# Gated on release tags and manual dispatch only - NOT on pull_request or +# push-to-main. Per-PR / push-to-main CI still runs the amd64-only +# docker-publish.yml, which stays fast (no emulated arm64 on every change). +# +# Each arch builds on its own NATIVE runner (arm64 on ubuntu-24.04-arm), so +# there is no QEMU emulation. Each arch is pushed by digest, then the merge +# job stitches the per-arch digests of a given ROS distro into one multi-arch +# manifest list and tags it. Mirrors the ros2_medkit_web_ui docker-publish.yml +# pattern, extended with the ros_distro build-arg dimension. + +name: Docker Publish (multi-arch, release) + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +env: + REGISTRY: ghcr.io + +jobs: + build: + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + ros_distro: [jazzy, humble, lyrical] + platform: + - linux/amd64 + - linux/arm64 + # `include` merges the runner key into every matrix combination + # whose platform matches - amd64 -> ubuntu-24.04, arm64 -> + # ubuntu-24.04-arm (native arm runner, free for public repos). + include: + - platform: linux/amd64 + runner: ubuntu-24.04 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + steps: + - name: Prepare platform pair + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV" + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/ros2_medkit-${{ matrix.ros_distro }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + context: . + platforms: ${{ matrix.platform }} + build-args: ROS_DISTRO=${{ matrix.ros_distro }} + labels: ${{ steps.meta.outputs.labels }} + # Separate cache scope per distro+arch so jobs do not evict each other. + cache-from: type=gha,scope=${{ matrix.ros_distro }}-${{ env.PLATFORM_PAIR }} + cache-to: type=gha,mode=max,scope=${{ matrix.ros_distro }}-${{ env.PLATFORM_PAIR }} + # Push each arch by digest; the merge job creates and tags the manifest list. + outputs: type=image,name=${{ env.REGISTRY }}/${{ github.repository_owner }}/ros2_medkit-${{ matrix.ros_distro }},push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.ros_distro }}-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + # Merge the per-arch digests of each ROS distro into one multi-arch manifest + # list and tag it. One merge job per distro (matrix), each pulling only its + # own digests--* artifacts. + merge: + runs-on: ubuntu-latest + needs: + - build + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + ros_distro: [jazzy, humble, lyrical] + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-${{ matrix.ros_distro }}-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/ros2_medkit-${{ matrix.ros_distro }} + tags: | + # version tag (v1.2.3 -> 1.2.3) + type=semver,pattern={{version}} + # major.minor (v1.2.3 -> 1.2) + type=semver,pattern={{major}}.{{minor}} + # always publish latest for release / dispatch + type=raw,value=latest + # sha for traceability + type=sha,prefix=sha-,format=short + + - name: Create multi-arch manifest list + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ github.repository_owner }}/ros2_medkit-${{ matrix.ros_distro }}@sha256:%s ' *) + + - name: Inspect manifest + run: | + docker buildx imagetools inspect \ + ${{ env.REGISTRY }}/${{ github.repository_owner }}/ros2_medkit-${{ matrix.ros_distro }}:${{ steps.meta.outputs.version }} From bf105acc544195344562e30f9afa410f07add944 Mon Sep 17 00:00:00 2001 From: mfaferek93 Date: Fri, 10 Jul 2026 22:34:30 +0200 Subject: [PATCH 2/2] ci: gate :latest tag to stable release tags only The multi-arch publish tagged :latest on every trigger, including pre-release tags (v*-rc/-alpha) and workflow_dispatch. Guard the raw latest tag with enable= so it only applies on a stable vX.Y.Z tag. --- .github/workflows/docker-publish-multiarch.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish-multiarch.yml b/.github/workflows/docker-publish-multiarch.yml index 18100a903..955ceefdb 100644 --- a/.github/workflows/docker-publish-multiarch.yml +++ b/.github/workflows/docker-publish-multiarch.yml @@ -138,8 +138,9 @@ jobs: type=semver,pattern={{version}} # major.minor (v1.2.3 -> 1.2) type=semver,pattern={{major}}.{{minor}} - # always publish latest for release / dispatch - type=raw,value=latest + # latest only on a stable release tag (vX.Y.Z with no + # pre-release suffix); skips -rc/-alpha tags and dispatch + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-') }} # sha for traceability type=sha,prefix=sha-,format=short