From 1e5084185ba6ef6fb7eb87a8f08b32160a3c8c97 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 01:29:39 +0200 Subject: [PATCH 1/6] ci: use native arm64 runners and GHA layer cache Splits each image build into parallel amd64 (ubuntu-latest) and arm64 (ubuntu-24.04-arm) jobs, then merges them into a multi-arch manifest via `docker buildx imagetools create`. This eliminates QEMU emulation for arm64, which was responsible for the ~50 min build time on rust/all. Adds GHA layer caching (type=gha, mode=max, scoped per image+platform) so that unchanged layers (e.g. candid-extractor when only icp-cli bumps) are served from cache on subsequent releases. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 246 ++++++++++++++++++++++++++++++++++----- 1 file changed, 219 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d23d1bc..5e126b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,8 @@ env: IMAGE_NAME_ALL: ${{ github.repository }}-all jobs: - build-and-push-image-motoko: + # ── motoko ────────────────────────────────────────────────────────────────── + build-motoko-amd64: runs-on: ubuntu-latest permissions: contents: read @@ -26,31 +27,62 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker + - name: Extract metadata (labels) + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - name: Build and push linux/amd64 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + platforms: linux/amd64 + context: motoko + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}:${{ github.sha }}-amd64 + labels: | + ${{ steps.meta.outputs.labels }} + org.opencontainers.image.description=A Motoko canister development environment for the Internet Computer (ICP) with icp-cli, moc, and mops. + cache-from: type=gha,scope=motoko-amd64 + cache-to: type=gha,mode=max,scope=motoko-amd64 + + build-motoko-arm64: + runs-on: ubuntu-24.04-arm + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Log in to the Container registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (labels) id: meta uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }} - tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' }} - type=ref,event=branch - type=semver,pattern={{version}} - - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push Docker image + - name: Build and push linux/arm64 uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: - platforms: linux/amd64,linux/arm64 + platforms: linux/arm64 context: motoko push: true - tags: ${{ steps.meta.outputs.tags }} + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}:${{ github.sha }}-arm64 labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A Motoko canister development environment for the Internet Computer (ICP) with icp-cli, moc, and mops. + cache-from: type=gha,scope=motoko-arm64 + cache-to: type=gha,mode=max,scope=motoko-arm64 - build-and-push-image-rust: + merge-motoko: + needs: [build-motoko-amd64, build-motoko-arm64] runs-on: ubuntu-latest permissions: contents: read @@ -64,31 +96,95 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker + - name: Extract metadata (tags) id: meta uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }} + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }} tags: | type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=ref,event=branch type=semver,pattern={{version}} - - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push Docker image + - name: Create and push multi-arch manifest + run: | + TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') + docker buildx imagetools create $TAGS \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}:${{ github.sha }}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}:${{ github.sha }}-arm64 + + # ── rust ──────────────────────────────────────────────────────────────────── + build-rust-amd64: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Log in to the Container registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (labels) + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - name: Build and push linux/amd64 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + platforms: linux/amd64 + context: rust + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}:${{ github.sha }}-amd64 + labels: | + ${{ steps.meta.outputs.labels }} + org.opencontainers.image.description=A Rust canister development environment for the Internet Computer (ICP) with icp-cli and Rust toolchain. + cache-from: type=gha,scope=rust-amd64 + cache-to: type=gha,mode=max,scope=rust-amd64 + + build-rust-arm64: + runs-on: ubuntu-24.04-arm + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Log in to the Container registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (labels) + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - name: Build and push linux/arm64 uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: - platforms: linux/amd64,linux/arm64 + platforms: linux/arm64 context: rust push: true - tags: ${{ steps.meta.outputs.tags }} + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}:${{ github.sha }}-arm64 labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A Rust canister development environment for the Internet Computer (ICP) with icp-cli and Rust toolchain. + cache-from: type=gha,scope=rust-arm64 + cache-to: type=gha,mode=max,scope=rust-arm64 - build-and-push-image-all: + merge-rust: + needs: [build-rust-amd64, build-rust-arm64] runs-on: ubuntu-latest permissions: contents: read @@ -102,26 +198,122 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker + - name: Extract metadata (tags) id: meta uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }} + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }} tags: | type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=ref,event=branch type=semver,pattern={{version}} - - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push Docker image + - name: Create and push multi-arch manifest + run: | + TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') + docker buildx imagetools create $TAGS \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}:${{ github.sha }}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}:${{ github.sha }}-arm64 + + # ── all ───────────────────────────────────────────────────────────────────── + build-all-amd64: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Log in to the Container registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (labels) + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - name: Build and push linux/amd64 uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64 context: all push: true - tags: ${{ steps.meta.outputs.tags }} + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}:${{ github.sha }}-amd64 labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A combined Motoko and Rust canister development environment for the Internet Computer (ICP) with icp-cli, Rust toolchain, and mops. + cache-from: type=gha,scope=all-amd64 + cache-to: type=gha,mode=max,scope=all-amd64 + + build-all-arm64: + runs-on: ubuntu-24.04-arm + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Log in to the Container registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (labels) + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - name: Build and push linux/arm64 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + platforms: linux/arm64 + context: all + push: true + tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}:${{ github.sha }}-arm64 + labels: | + ${{ steps.meta.outputs.labels }} + org.opencontainers.image.description=A combined Motoko and Rust canister development environment for the Internet Computer (ICP) with icp-cli, Rust toolchain, and mops. + cache-from: type=gha,scope=all-arm64 + cache-to: type=gha,mode=max,scope=all-arm64 + + merge-all: + needs: [build-all-amd64, build-all-arm64] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Log in to the Container registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags) + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }} + tags: | + type=raw,value=latest,enable=${{ github.event_name == 'release' }} + type=ref,event=branch + type=semver,pattern={{version}} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - name: Create and push multi-arch manifest + run: | + TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') + docker buildx imagetools create $TAGS \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}:${{ github.sha }}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}:${{ github.sha }}-arm64 From 873655bdb620afeb30427053a659f343e0e68b03 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 01:41:21 +0200 Subject: [PATCH 2/6] ci: push by digest and add event=tag rule Addresses Copilot review on #35: - Switch platform builds to push-by-digest (outputs: push-by-digest=true) so no SHA-suffixed intermediate tags accumulate in GHCR; digests are passed between jobs via outputs and referenced directly in imagetools create - Add type=ref,event=tag to all merge jobs so workflow_dispatch on a tag ref always produces at least one --tag destination Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 63 +++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e126b3..deb5668 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: # ── motoko ────────────────────────────────────────────────────────────────── build-motoko-amd64: runs-on: ubuntu-latest + outputs: + digest: ${{ steps.build.outputs.digest }} permissions: contents: read packages: write @@ -34,13 +36,13 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push linux/amd64 + - name: Build and push linux/amd64 by digest + id: build uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: platforms: linux/amd64 context: motoko - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}:${{ github.sha }}-amd64 + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }},push-by-digest=true,name-canonical=true,push=true labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A Motoko canister development environment for the Internet Computer (ICP) with icp-cli, moc, and mops. @@ -49,6 +51,8 @@ jobs: build-motoko-arm64: runs-on: ubuntu-24.04-arm + outputs: + digest: ${{ steps.build.outputs.digest }} permissions: contents: read packages: write @@ -68,13 +72,13 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push linux/arm64 + - name: Build and push linux/arm64 by digest + id: build uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: platforms: linux/arm64 context: motoko - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}:${{ github.sha }}-arm64 + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }},push-by-digest=true,name-canonical=true,push=true labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A Motoko canister development environment for the Internet Computer (ICP) with icp-cli, moc, and mops. @@ -104,6 +108,7 @@ jobs: tags: | type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=ref,event=branch + type=ref,event=tag type=semver,pattern={{version}} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 @@ -111,12 +116,14 @@ jobs: run: | TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}:${{ github.sha }}-amd64 \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}:${{ github.sha }}-arm64 + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}@${{ needs.build-motoko-amd64.outputs.digest }} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}@${{ needs.build-motoko-arm64.outputs.digest }} # ── rust ──────────────────────────────────────────────────────────────────── build-rust-amd64: runs-on: ubuntu-latest + outputs: + digest: ${{ steps.build.outputs.digest }} permissions: contents: read packages: write @@ -136,13 +143,13 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push linux/amd64 + - name: Build and push linux/amd64 by digest + id: build uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: platforms: linux/amd64 context: rust - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}:${{ github.sha }}-amd64 + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }},push-by-digest=true,name-canonical=true,push=true labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A Rust canister development environment for the Internet Computer (ICP) with icp-cli and Rust toolchain. @@ -151,6 +158,8 @@ jobs: build-rust-arm64: runs-on: ubuntu-24.04-arm + outputs: + digest: ${{ steps.build.outputs.digest }} permissions: contents: read packages: write @@ -170,13 +179,13 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push linux/arm64 + - name: Build and push linux/arm64 by digest + id: build uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: platforms: linux/arm64 context: rust - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}:${{ github.sha }}-arm64 + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }},push-by-digest=true,name-canonical=true,push=true labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A Rust canister development environment for the Internet Computer (ICP) with icp-cli and Rust toolchain. @@ -206,6 +215,7 @@ jobs: tags: | type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=ref,event=branch + type=ref,event=tag type=semver,pattern={{version}} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 @@ -213,12 +223,14 @@ jobs: run: | TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}:${{ github.sha }}-amd64 \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}:${{ github.sha }}-arm64 + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}@${{ needs.build-rust-amd64.outputs.digest }} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}@${{ needs.build-rust-arm64.outputs.digest }} # ── all ───────────────────────────────────────────────────────────────────── build-all-amd64: runs-on: ubuntu-latest + outputs: + digest: ${{ steps.build.outputs.digest }} permissions: contents: read packages: write @@ -238,13 +250,13 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push linux/amd64 + - name: Build and push linux/amd64 by digest + id: build uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: platforms: linux/amd64 context: all - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}:${{ github.sha }}-amd64 + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }},push-by-digest=true,name-canonical=true,push=true labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A combined Motoko and Rust canister development environment for the Internet Computer (ICP) with icp-cli, Rust toolchain, and mops. @@ -253,6 +265,8 @@ jobs: build-all-arm64: runs-on: ubuntu-24.04-arm + outputs: + digest: ${{ steps.build.outputs.digest }} permissions: contents: read packages: write @@ -272,13 +286,13 @@ jobs: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - name: Build and push linux/arm64 + - name: Build and push linux/arm64 by digest + id: build uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: platforms: linux/arm64 context: all - push: true - tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}:${{ github.sha }}-arm64 + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }},push-by-digest=true,name-canonical=true,push=true labels: | ${{ steps.meta.outputs.labels }} org.opencontainers.image.description=A combined Motoko and Rust canister development environment for the Internet Computer (ICP) with icp-cli, Rust toolchain, and mops. @@ -308,6 +322,7 @@ jobs: tags: | type=raw,value=latest,enable=${{ github.event_name == 'release' }} type=ref,event=branch + type=ref,event=tag type=semver,pattern={{version}} - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 @@ -315,5 +330,5 @@ jobs: run: | TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}:${{ github.sha }}-amd64 \ - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}:${{ github.sha }}-arm64 + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}@${{ needs.build-all-amd64.outputs.digest }} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}@${{ needs.build-all-arm64.outputs.digest }} From 2e5cbfda98940ffa17e7da59cb5238b8e2f44ca2 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 01:57:24 +0200 Subject: [PATCH 3/6] ci: guard against empty tags in imagetools create Replace echo with printf + grep -v '^$' in the TAGS construction of all three merge jobs. echo always emits a trailing newline so an empty tags output would produce '--tag ' (with no value), causing imagetools create to fail. The grep filter drops blank lines so TAGS is truly empty when no tags are generated. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index deb5668..8ca2c01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Create and push multi-arch manifest run: | - TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') + TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | grep -v '^$' | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}@${{ needs.build-motoko-amd64.outputs.digest }} \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}@${{ needs.build-motoko-arm64.outputs.digest }} @@ -221,7 +221,7 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Create and push multi-arch manifest run: | - TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') + TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | grep -v '^$' | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}@${{ needs.build-rust-amd64.outputs.digest }} \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}@${{ needs.build-rust-arm64.outputs.digest }} @@ -328,7 +328,7 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Create and push multi-arch manifest run: | - TAGS=$(echo "${{ steps.meta.outputs.tags }}" | sed 's/^/--tag /' | tr '\n' ' ') + TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | grep -v '^$' | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}@${{ needs.build-all-amd64.outputs.digest }} \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}@${{ needs.build-all-arm64.outputs.digest }} From 943e827bfb44d5efa52b86fc50947fc30175c2ea Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 02:01:49 +0200 Subject: [PATCH 4/6] ci: add actions: write permission to all build jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Required for cache-from/cache-to: type=gha — without it the GHA cache backend denies both reads and writes when permissions are explicitly set. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ca2c01..677f6c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: permissions: contents: read packages: write + actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -56,6 +57,7 @@ jobs: permissions: contents: read packages: write + actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -127,6 +129,7 @@ jobs: permissions: contents: read packages: write + actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -163,6 +166,7 @@ jobs: permissions: contents: read packages: write + actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -234,6 +238,7 @@ jobs: permissions: contents: read packages: write + actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -270,6 +275,7 @@ jobs: permissions: contents: read packages: write + actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 6d4ace9f891850baac8dc40fb321bdacd836d855 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 02:04:38 +0200 Subject: [PATCH 5/6] Revert "ci: add actions: write permission to all build jobs" This reverts commit 943e827bfb44d5efa52b86fc50947fc30175c2ea. --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 677f6c6..8ca2c01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,6 @@ jobs: permissions: contents: read packages: write - actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -57,7 +56,6 @@ jobs: permissions: contents: read packages: write - actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -129,7 +127,6 @@ jobs: permissions: contents: read packages: write - actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -166,7 +163,6 @@ jobs: permissions: contents: read packages: write - actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -238,7 +234,6 @@ jobs: permissions: contents: read packages: write - actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -275,7 +270,6 @@ jobs: permissions: contents: read packages: write - actions: write steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 From 79495bd87b323a936f87966038c808b9e4f19f70 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 11 Jun 2026 08:53:15 +0200 Subject: [PATCH 6/6] ci: replace grep with awk NF in TAGS construction grep -v '^$' exits 1 when all input lines are empty, which would fail the merge step under bash -e before imagetools create runs. awk NF filters blank lines identically but always exits 0. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ca2c01..117ae21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Create and push multi-arch manifest run: | - TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | grep -v '^$' | sed 's/^/--tag /' | tr '\n' ' ') + TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | awk NF | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}@${{ needs.build-motoko-amd64.outputs.digest }} \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_MOTOKO }}@${{ needs.build-motoko-arm64.outputs.digest }} @@ -221,7 +221,7 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Create and push multi-arch manifest run: | - TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | grep -v '^$' | sed 's/^/--tag /' | tr '\n' ' ') + TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | awk NF | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}@${{ needs.build-rust-amd64.outputs.digest }} \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_RUST }}@${{ needs.build-rust-arm64.outputs.digest }} @@ -328,7 +328,7 @@ jobs: uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Create and push multi-arch manifest run: | - TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | grep -v '^$' | sed 's/^/--tag /' | tr '\n' ' ') + TAGS=$(printf '%s\n' "${{ steps.meta.outputs.tags }}" | awk NF | sed 's/^/--tag /' | tr '\n' ' ') docker buildx imagetools create $TAGS \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}@${{ needs.build-all-amd64.outputs.digest }} \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_ALL }}@${{ needs.build-all-arm64.outputs.digest }}