Skip to content

ci: build server image on native arm64 runners instead of QEMU#92

Merged
joaoh82 merged 1 commit into
mainfrom
feat/ghcr-native-runners
Jun 13, 2026
Merged

ci: build server image on native arm64 runners instead of QEMU#92
joaoh82 merged 1 commit into
mainfrom
feat/ghcr-native-runners

Conversation

@joaoh82

@joaoh82 joaoh82 commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Why

The multi-arch server image landed in #91 built the linux/arm64 layer under QEMU emulation, which compiles the full Rust server and the Next.js dashboard on an emulated CPU. A dispatch run on v0.8.1 sat on the build step for over an hour and was cancelled. Worse, it's slow on every release — the source changes each tag, so the emulated compile re-runs and the layer cache can't rescue it.

What

Switch to the canonical native-runner pattern (Docker docs):

  • build matrix job — each arch on its own native runner (linux/amd64ubuntu-latest, linux/arm64 → the free ubuntu-24.04-arm runners, which this public repo gets at no cost). No QEMU; each arch compiles at native speed, in parallel (~native single-arch time instead of an hour).
  • Each leg pushes its image by digest (push-by-digest=true), uploads the digest as an artifact, with per-arch GHA cache scopes so the legs don't clobber each other.
  • merge job — downloads both digests and stitches them into one multi-arch manifest list tagged :X.Y.Z (+ :latest for stable tags) via docker buildx imagetools create, then inspects the result.

Metadata / package linking

  • OCI labels are applied to each arch image (build-args + labels:) and as index-level annotations on the manifest in the merge job. org.opencontainers.image.source links the GHCR package to this repo so the package page renders the README.
  • :latest stays stable-only (hyphen tags = pre-releases).

Unchanged

release.yml's publish-server-image job calls the same reusable workflow with the same tag input and contents: read + packages: write — no change needed there.

Verification

Follow-up (unchanged from #91)

After merge: gh workflow run release-docker.yml -f tag=v0.8.1 to seed :0.8.1 + :latest, then set the GHCR package public, then bump TUNNEL-14 web/seo/backlinks.md § 2.

🤖 Generated with Claude Code

The multi-arch image landed in #91 built linux/arm64 under QEMU emulation,
which compiles the full Rust server + Next.js dashboard on an emulated CPU —
~1hr+ cold, and slow on *every* release (the source changes each tag, so the
emulated compile re-runs and the layer cache can't save it).

Switch to the canonical native-runner pattern: build each arch on its own
native runner (amd64 on ubuntu-latest, arm64 on the free ubuntu-24.04-arm
runners), push each by digest, then a merge job stitches them into one
multi-arch manifest list tagged :X.Y.Z (+ :latest for stable tags). No
emulation — each arch compiles at native speed, in parallel.

- Per-arch GHA cache scopes so the two legs don't clobber each other.
- OCI labels on each arch image (build-args + labels) AND index-level
  annotations on the manifest in the merge job, so image.source links the
  GHCR package to the repo and the package page renders the README.
- :latest stays stable-only (hyphen tags are pre-releases).

release.yml's publish-server-image job is unchanged (same reusable-workflow
call + permissions).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@joaoh82 joaoh82 merged commit e42ee63 into main Jun 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant