ci: build each architecture on its own native runner - #88
Merged
Conversation
The publish job built linux/amd64 and linux/arm64 together on one amd64 runner, which meant the arm64 half ran under QEMU — and it was not a small tax. The same Dockerfile finishes in 1m58s for amd64 alone and had not finished after 25 minutes once arm64 joined it, because what gets emulated is `pnpm install` and the Vite build, instruction by instruction. The repository is public, so ubuntu-24.04-arm runners are free. Each architecture now builds natively on its own runner, concurrently. Neither leg tags anything. push-by-digest uploads an untagged image and returns a digest; a merge job assembles the two digests into one tagged manifest list. Tagging per leg would have let whichever finished last overwrite the other, leaving `latest` pointing at a single architecture. The merge job inspects the result and fails if both are not on it, since that is the failure this shape exists to prevent. Cache scopes are per architecture: sharing one would have the two legs evict each other's layers and start cold every run. Also drops `--filter tracker...` from the Dockerfile's install. tracker is a workspace dependency of web, so `--filter web...` already selects it — verified by building and booting the image, which serves /signin with a 200 and carries the same 5114-byte tracker.js at the same 590MB.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The publish job built
linux/amd64andlinux/arm64together on a singleamd64 runner, so the arm64 half ran under QEMU. Measured on this repository:
What gets emulated is
pnpm installand the Vite build, translated instructionby instruction. For reference, the same Dockerfile builds cold in 38s natively.
This repository is public, so
ubuntu-24.04-armrunners are free. Eacharchitecture now builds natively on its own runner, concurrently.
How it works
publishbecomes a two-entry matrix. Neither leg tags anything —push-by-digest=trueuploads an untagged image and returns its digest, whichtravels to the new
mergejob as an artifact.mergeassembles both digestsinto one tagged manifest list.
Tagging per leg would have let whichever finished last overwrite the other,
leaving
latestresolvable to only one architecture.mergethereforeinspects the pushed list and fails if both architectures are not on it —
that being the exact failure this shape exists to prevent.
Cache scopes are per architecture (
publish-amd64/publish-arm64); a sharedscope would have the two legs evict each other's layers and start cold on every
run. Tag selection is unchanged, still keyed off
release_created.Also
Drops
--filter tracker...from the Dockerfile's install.trackeris aworkspace dependency of
web, so--filter web...already selects it.Verified locally
tracker.jsis present at 5114 bytes, identical to a local build — so thetracker really is still being built
GET /signinwith HTTP 200I also tested replacing the
prod-depsstage withpnpm deploy --prod, whichpnpm's Docker docs recommend for monorepos. It produces an image that cannot
boot:
pnpm deployfollows publish semantics and honours.gitignore, whereapps/web/.gitignorelists/build/, so the build output is silently omitted.It was larger too (619MB). Not pursued — noting it so nobody else tries.