@@ -17,6 +17,14 @@ name: Build and Push to GHCR
1717# - workflow_dispatch: manual run; gated via `needs:` chain on local jobs that
1818# re-verify the head SHA passed GoFortress before publishing.
1919# ------------------------------------------------------------------------------------
20+ # Multi-arch strategy
21+ # ------------------------------------------------------------------------------------
22+ # Each architecture is built on its native runner (`ubuntu-24.04` for amd64,
23+ # `ubuntu-24.04-arm` for arm64) — no QEMU emulation. Each matrix job pushes a
24+ # single-arch image to GHCR by digest only (no tag). The merge-manifest job then
25+ # composes those digests into a single multi-arch manifest list under the existing
26+ # tag scheme (`<sha>` and `latest` / `<git-tag>`).
27+ # ------------------------------------------------------------------------------------
2028
2129on :
2230 workflow_run :
3139
3240permissions : {}
3341
42+ env :
43+ REGISTRY : ghcr.io
44+ IMAGE_NAME : bsv-blockchain/arcade
45+
3446jobs :
3547 # --------------------------------------------------------------------------------
3648 # Gate: ensure the upstream GoFortress run (lint, security, tests) succeeded.
@@ -85,15 +97,26 @@ jobs:
8597 outputs :
8698 deployment_tag : ${{ steps.deployment_tag.outputs.id }}
8799
100+ # --------------------------------------------------------------------------------
101+ # Per-arch native build. Each matrix entry runs on a native runner of its target
102+ # architecture, cross-compiles the Go binary into dist/linux-<arch>/arcade, then
103+ # uses buildx to assemble a single-arch image from the thin runtime Dockerfile
104+ # and pushes it to GHCR by digest only. PRs build for verification but don't push.
105+ # --------------------------------------------------------------------------------
88106 build-and-push :
89- # Gate the publishing step on:
90- # - get_tag (computes the image tag)
91- # - gofortress-gate (ensures lint/security/tests passed upstream)
92107 needs : [get_tag, gofortress-gate]
93- runs-on : ubuntu-latest
94108 permissions :
95109 contents : read
96110 packages : write
111+ strategy :
112+ fail-fast : true
113+ matrix :
114+ include :
115+ - arch : amd64
116+ runner : ubuntu-24.04
117+ - arch : arm64
118+ runner : ubuntu-24.04-arm
119+ runs-on : ${{ matrix.runner }}
97120 steps :
98121 # Step 1: checkout code. For workflow_run events, check out the exact SHA
99122 # that GoFortress validated, not whatever HEAD happens to be on main now.
@@ -102,31 +125,124 @@ jobs:
102125 with :
103126 ref : ${{ github.event.workflow_run.head_sha || github.sha }}
104127
105- # Step 2: Set up QEMU for multi-architecture builds
106- - name : Set up QEMU
107- uses : docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
128+ # Step 2: install Go and warm the module / build cache for this runner.
129+ - name : Set up Go
130+ uses : actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
131+ with :
132+ go-version-file : go.mod
133+ cache : true
108134
109- # Step 3: Set up Docker Buildx
135+ # Step 3: build the binary natively into the dist layout the Dockerfile
136+ # expects. GOOS/GOARCH are explicit so the path matches even if a future
137+ # runner image defaults differ.
138+ - name : Build arcade binary
139+ env :
140+ CGO_ENABLED : ' 0'
141+ GOOS : linux
142+ GOARCH : ${{ matrix.arch }}
143+ run : |
144+ mkdir -p dist/linux-${{ matrix.arch }}
145+ go build -trimpath -ldflags="-s -w" -o dist/linux-${{ matrix.arch }}/arcade ./cmd/arcade
146+
147+ # Step 4: Set up Docker Buildx (no QEMU needed — single-platform build on
148+ # a native runner of that platform).
110149 - name : Set up Docker Buildx
111150 uses : docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
112151
113- # Step 4 : login to GCHR
152+ # Step 5 : login to GHCR (skipped on PRs since we don't push).
114153 - name : Log in to GitHub Container Registry
154+ if : github.event_name != 'pull_request'
115155 uses : docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
116156 with :
117- registry : ghcr.io
157+ registry : ${{ env.REGISTRY }}
118158 username : ${{ github.actor }}
119159 password : ${{ secrets.GITHUB_TOKEN }}
120160
121- # Step 5: Build and push the Docker image
122- # Publishing is allowed only for non-PR events; PRs build for verification.
123- - name : Build and push Docker image
161+ # Step 6a: PR build verifies the Dockerfile assembles for this arch but
162+ # never pushes anywhere.
163+ - name : Build image (PR verification, no push)
164+ if : github.event_name == 'pull_request'
124165 uses : docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
125166 with :
126- context : . # Build context (root directory, adjust if Dockerfile is elsewhere)
127- file : ./Dockerfile # Path to Dockerfile
128- platforms : linux/amd64 # , linux/arm64 Disable ARM for now
129- push : ${{ github.event_name != 'pull_request' }} # Only push on non-PR events
130- tags : |
131- ghcr.io/bsv-blockchain/arcade:${{ github.event.workflow_run.head_sha || github.sha }}
132- ghcr.io/bsv-blockchain/arcade:${{ needs.get_tag.outputs.deployment_tag }}
167+ context : .
168+ file : ./Dockerfile
169+ platforms : linux/${{ matrix.arch }}
170+ push : false
171+
172+ # Step 6b: non-PR build pushes by digest only (no tag). The merge-manifest
173+ # job stitches per-arch digests into the final multi-arch tag.
174+ - name : Build and push image by digest
175+ if : github.event_name != 'pull_request'
176+ id : build
177+ uses : docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
178+ with :
179+ context : .
180+ file : ./Dockerfile
181+ platforms : linux/${{ matrix.arch }}
182+ outputs : type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
183+
184+ # Step 7: write the digest to a file named after itself so the merge job
185+ # can reconstruct the per-arch image references.
186+ - name : Export digest
187+ if : github.event_name != 'pull_request'
188+ env :
189+ DIGEST : ${{ steps.build.outputs.digest }}
190+ run : |
191+ mkdir -p "${RUNNER_TEMP}/digests"
192+ touch "${RUNNER_TEMP}/digests/${DIGEST#sha256:}"
193+
194+ - name : Upload digest artifact
195+ if : github.event_name != 'pull_request'
196+ uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
197+ with :
198+ name : digests-${{ matrix.arch }}
199+ path : ${{ runner.temp }}/digests/*
200+ if-no-files-found : error
201+ retention-days : 1
202+
203+ # --------------------------------------------------------------------------------
204+ # Compose the per-arch digests into a single multi-arch manifest tagged
205+ # :<sha> and :<deployment_tag>. Skipped on PRs (no digests produced).
206+ # --------------------------------------------------------------------------------
207+ merge-manifest :
208+ needs : [build-and-push, get_tag]
209+ if : github.event_name != 'pull_request'
210+ runs-on : ubuntu-latest
211+ permissions :
212+ contents : read
213+ packages : write
214+ steps :
215+ - name : Download digest artifacts
216+ uses : actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
217+ with :
218+ path : ${{ runner.temp }}/digests
219+ pattern : digests-*
220+ merge-multiple : true
221+
222+ - name : Set up Docker Buildx
223+ uses : docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
224+
225+ - name : Log in to GitHub Container Registry
226+ uses : docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
227+ with :
228+ registry : ${{ env.REGISTRY }}
229+ username : ${{ github.actor }}
230+ password : ${{ secrets.GITHUB_TOKEN }}
231+
232+ - name : Compose and push multi-arch manifest
233+ working-directory : ${{ runner.temp }}/digests
234+ env :
235+ IMAGE : ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
236+ SHA_TAG : ${{ github.event.workflow_run.head_sha || github.sha }}
237+ DEPLOYMENT_TAG : ${{ needs.get_tag.outputs.deployment_tag }}
238+ run : |
239+ docker buildx imagetools create \
240+ --tag "${IMAGE}:${SHA_TAG}" \
241+ --tag "${IMAGE}:${DEPLOYMENT_TAG}" \
242+ $(printf "${IMAGE}@sha256:%s " *)
243+
244+ - name : Inspect resulting manifest
245+ env :
246+ IMAGE : ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
247+ SHA_TAG : ${{ github.event.workflow_run.head_sha || github.sha }}
248+ run : docker buildx imagetools inspect "${IMAGE}:${SHA_TAG}"
0 commit comments