Skip to content

Commit 4c0ab51

Browse files
committed
ci(docker): build linux/arm64 on a native runner instead of qemu
The publish workflow has been broken since the Node 24 bump (#1767). The arm64 leg ran under qemu-user on the amd64 runner and crashed with SIGILL (exit 132) inside `npm ci` - V8 13's JIT emits Arm v8.x instructions that the runner's binfmt qemu cannot decode. Switch to the docker/build-push-action multi-platform pattern: - Matrix per architecture, each on its native runner. amd64 stays on `ubuntu-latest`; arm64 moves to the free `ubuntu-24.04-arm` runner GitHub now provides for public repos. Both per-arch jobs push blob-only `push-by-digest` images so they never compete for shared tags. - A `merge` job downloads both digests and stitches them into the `:latest` and `:<sha>` manifest list with `docker buildx imagetools create`, reproducing the previous tag set. Also add per-arch GHA cache scopes so the two legs do not invalidate each other. Temporarily includes `chore/native-arm-publish` in the push trigger so the workflow can be validated end-to-end before the entry is removed and the change is opened for review.
1 parent 8ce1cde commit 4c0ab51

1 file changed

Lines changed: 96 additions & 15 deletions

File tree

.github/workflows/deploy_docker.yaml

Lines changed: 96 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,35 @@ on:
44
push:
55
branches:
66
- master
7+
# TEMP: validate native-arm runner build before merging to master.
8+
# Remove this branch entry before opening the PR for review.
9+
- chore/native-arm-publish
710

811
jobs:
9-
push_to_registry:
10-
name: Push Docker image to Docker Hub
11-
runs-on: ubuntu-latest
12+
# Build each platform on its own native runner. Building linux/arm64 under
13+
# qemu-user on an amd64 runner has been broken since the Node 24 bump
14+
# (qemu raises SIGILL on Arm v8.x instructions emitted by V8's JIT, exit
15+
# 132). GitHub provides free `ubuntu-24.04-arm` runners for public repos,
16+
# so we can build natively on each arch and stitch the results together
17+
# into a manifest list in the merge job below. Pattern lifted from the
18+
# docker/build-push-action multi-platform docs.
19+
build:
20+
name: Build ${{ matrix.platform }}
21+
runs-on: ${{ matrix.runner }}
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
include:
26+
- platform: linux/amd64
27+
runner: ubuntu-latest
28+
arch: amd64
29+
- platform: linux/arm64
30+
runner: ubuntu-24.04-arm
31+
arch: arm64
1232
steps:
1333
- name: Check out the repo
1434
uses: actions/checkout@v6
1535

16-
- name: Set up QEMU
17-
uses: docker/setup-qemu-action@v3
18-
1936
- name: Set up Docker Buildx
2037
uses: docker/setup-buildx-action@v3
2138

@@ -25,21 +42,85 @@ jobs:
2542
username: ${{ secrets.DOCKER_USERNAME }}
2643
password: ${{ secrets.DOCKER_PASSWORD }}
2744

28-
- name: Extract metadata (tags, labels) for Docker
45+
- name: Extract metadata (labels) for Docker
2946
id: meta
3047
uses: docker/metadata-action@v5
3148
with:
3249
images: banmanagement/webui
3350

34-
- name: Build and push multi-platform Docker image
51+
- name: Build and push by digest
52+
id: build
3553
uses: docker/build-push-action@v6
3654
with:
3755
context: .
38-
platforms: linux/amd64,linux/arm64
39-
push: true
40-
tags: |
41-
banmanagement/webui:${{ github.sha }}
42-
banmanagement/webui:latest
56+
platforms: ${{ matrix.platform }}
4357
labels: ${{ steps.meta.outputs.labels }}
44-
cache-from: type=gha
45-
cache-to: type=gha,mode=max
58+
# push-by-digest avoids touching shared tags from per-arch jobs;
59+
# the merge job below produces the actual `:latest` / `:<sha>`
60+
# manifest list referencing both digests.
61+
outputs: type=image,name=banmanagement/webui,push-by-digest=true,name-canonical=true,push=true
62+
# Per-arch cache scope so amd64 and arm64 do not stomp on each
63+
# other's layer caches.
64+
cache-from: type=gha,scope=${{ matrix.arch }}
65+
cache-to: type=gha,mode=max,scope=${{ matrix.arch }}
66+
67+
- name: Export digest
68+
run: |
69+
mkdir -p /tmp/digests
70+
digest='${{ steps.build.outputs.digest }}'
71+
# The digest output is `sha256:<hex>`; the merge job wants just
72+
# the hex (and to find the file in /tmp/digests).
73+
touch "/tmp/digests/${digest#sha256:}"
74+
75+
- name: Upload digest artifact
76+
uses: actions/upload-artifact@v4
77+
with:
78+
name: digests-${{ matrix.arch }}
79+
path: /tmp/digests/*
80+
if-no-files-found: error
81+
retention-days: 1
82+
83+
merge:
84+
name: Merge into manifest
85+
needs: [build]
86+
runs-on: ubuntu-latest
87+
steps:
88+
- name: Download digests
89+
uses: actions/download-artifact@v4
90+
with:
91+
path: /tmp/digests
92+
pattern: digests-*
93+
merge-multiple: true
94+
95+
- name: Set up Docker Buildx
96+
uses: docker/setup-buildx-action@v3
97+
98+
- name: Log in to Docker Hub
99+
uses: docker/login-action@v3
100+
with:
101+
username: ${{ secrets.DOCKER_USERNAME }}
102+
password: ${{ secrets.DOCKER_PASSWORD }}
103+
104+
- name: Extract metadata (tags) for Docker
105+
id: meta
106+
uses: docker/metadata-action@v5
107+
with:
108+
images: banmanagement/webui
109+
# Reproduce the previous workflow's tag set:
110+
# - `latest` on default branch
111+
# - the full commit SHA
112+
tags: |
113+
type=raw,value=latest,enable={{is_default_branch}}
114+
type=sha,format=long
115+
116+
- name: Create manifest list and push
117+
working-directory: /tmp/digests
118+
run: |
119+
docker buildx imagetools create \
120+
$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
121+
$(printf 'banmanagement/webui@sha256:%s ' *)
122+
123+
- name: Inspect image
124+
run: |
125+
docker buildx imagetools inspect \
126+
"banmanagement/webui:$(jq -r '.tags[0] | split(":")[1]' <<< "$DOCKER_METADATA_OUTPUT_JSON")"

0 commit comments

Comments
 (0)