Publish multi-arch image (add linux/arm64) #10

Closed
opened 2026-07-27 09:44:36 +00:00 by james.oates · 2 comments
Owner

forge.isohex.uk/isohex/edge-router:latest is currently linux/amd64 only. On Apple Silicon dev machines (devinfra under OrbStack) it runs under Rosetta emulation — functional, but slower to start and heavier on CPU than a native build.

Add linux/arm64 to the CI image build (e.g. docker buildx build --platform linux/amd64,linux/arm64) so the manifest serves the native architecture on both the homelab amd64 hosts and arm64 dev machines.

Noticed 2026-07-27 while verifying the devinfra stack after the OrbStack migration.

`forge.isohex.uk/isohex/edge-router:latest` is currently `linux/amd64` only. On Apple Silicon dev machines (devinfra under OrbStack) it runs under Rosetta emulation — functional, but slower to start and heavier on CPU than a native build. Add `linux/arm64` to the CI image build (e.g. `docker buildx build --platform linux/amd64,linux/arm64`) so the manifest serves the native architecture on both the homelab amd64 hosts and arm64 dev machines. Noticed 2026-07-27 while verifying the devinfra stack after the OrbStack migration.
Author
Owner

arm64 publishing is live (via the self-hosted Mac runner)

Added .forgejo/workflows/docker-publish-arm64.yml (commit 69e816a). On every push to main the macos-arm64 runner builds a native linux/arm64 image (OrbStack, no emulation) and pushes :develop-arm64, :<version>-arm64, :<sha7>-arm64; on a v* tag it pushes :<tag>-arm64 + :latest-arm64. First green run: task 362. Verified :develop-arm64 is linux/arm64 in the registry.

Deliberate design choice: separate -arm64 tags, NOT a combined multi-arch :latest

Production (Roku, amd64) pulls :latest via Watchtower and must never wait on the laptop. A true combined multi-arch manifest can only be assembled once both arches build, which would couple prod deploys to the intermittent Mac runner. So the amd64 flow (docker-publish.yml/docker-release.yml) is untouched, and arm64 lives on its own -arm64 tags. The laptop points its edge-router at …:latest-arm64 / …:develop-arm64. Intermittency is a non-issue: the laptop is both the only arm64 builder and the only arm64 consumer, and Forge queues the job until it reconnects.

Two host-runner gotchas fixed along the way

  • The runner is host-mode with no Node in its launchd PATH, so the JS actions/checkout action can't run → the workflow clones manually with git.
  • OrbStack's docker CLI lives in ~/.orbstack/bin, which isn't on the runner's launchd PATH → the workflow prepends it (otherwise docker: command not found).

If you'd rather have a single docker pull …:latest that auto-selects arch (true multi-arch manifest), that's doable but reintroduces the prod-coupling above — leaving this open in case you want that later; otherwise it can be closed as resolved by the separate-tags approach.

## arm64 publishing is live (via the self-hosted Mac runner) Added `.forgejo/workflows/docker-publish-arm64.yml` (commit `69e816a`). On every push to `main` the `macos-arm64` runner builds a **native** `linux/arm64` image (OrbStack, no emulation) and pushes `:develop-arm64`, `:<version>-arm64`, `:<sha7>-arm64`; on a `v*` tag it pushes `:<tag>-arm64` + `:latest-arm64`. First green run: task 362. Verified `:develop-arm64` is `linux/arm64` in the registry. ### Deliberate design choice: separate `-arm64` tags, NOT a combined multi-arch `:latest` Production (Roku, amd64) pulls `:latest` via Watchtower and must never wait on the laptop. A true combined multi-arch manifest can only be assembled once **both** arches build, which would couple prod deploys to the intermittent Mac runner. So the amd64 flow (`docker-publish.yml`/`docker-release.yml`) is untouched, and arm64 lives on its own `-arm64` tags. The laptop points its edge-router at `…:latest-arm64` / `…:develop-arm64`. Intermittency is a non-issue: the laptop is both the only arm64 builder and the only arm64 consumer, and Forge queues the job until it reconnects. ### Two host-runner gotchas fixed along the way - The runner is host-mode with **no Node** in its launchd PATH, so the JS `actions/checkout` action can't run → the workflow clones manually with `git`. - OrbStack's `docker` CLI lives in `~/.orbstack/bin`, which **isn't** on the runner's launchd PATH → the workflow prepends it (otherwise `docker: command not found`). If you'd rather have a single `docker pull …:latest` that auto-selects arch (true multi-arch manifest), that's doable but reintroduces the prod-coupling above — leaving this open in case you want that later; otherwise it can be closed as resolved by the separate-tags approach.
Author
Owner

Resolved — images are multi-arch

edge-router is now published as proper multi-platform images. Each of these is a single OCI image index carrying both linux/amd64 and linux/arm64, so docker pull auto-selects the right arch on any host:

  • :latest
  • :develop
  • :69e816a (and :v1.0.6-69e816a)

The old arch-split :*-arm64 tags were removed, and the arm64 CI workflow no longer emits latest-arm64 (commit eb9fbb6) — :latest is the multi-arch manifest.

How they're produced today (semi-manual)

  • arm64 builds natively on the self-hosted Mac runner (docker-publish-arm64.yml, via buildx/BuildKit — PR #17), publishing :*-arm64 tags.
  • amd64 was built manually here (emulated --platform linux/amd64) because the CI amd64 build is currently down.
  • The per-arch images are combined into the multi-arch manifests with docker buildx imagetools create (no rebuild needed).

What's deferred (not blocking this ticket)

Fully automated multi-arch in CI needs the amd64 build working in CI, which is blocked on the roku forgejo-runner failing every job at actions/checkout — tracked in james.oates/homelab#6. That CI automation is being folded into the in-progress server rebuild.

Closing: the deliverable (multi-arch image available for both arches) is met. The CI-automation remainder is carried by james.oates/homelab#6.

## Resolved — images are multi-arch ✅ `edge-router` is now published as proper multi-platform images. Each of these is a single OCI image index carrying **both `linux/amd64` and `linux/arm64`**, so `docker pull` auto-selects the right arch on any host: - `:latest` - `:develop` - `:69e816a` (and `:v1.0.6-69e816a`) The old arch-split `:*-arm64` tags were removed, and the arm64 CI workflow no longer emits `latest-arm64` (commit `eb9fbb6`) — `:latest` is the multi-arch manifest. ### How they're produced today (semi-manual) - **arm64** builds natively on the self-hosted Mac runner (`docker-publish-arm64.yml`, via buildx/BuildKit — PR #17), publishing `:*-arm64` tags. - **amd64** was built manually here (emulated `--platform linux/amd64`) because the CI amd64 build is currently down. - The per-arch images are combined into the multi-arch manifests with `docker buildx imagetools create` (no rebuild needed). ### What's deferred (not blocking this ticket) Fully **automated** multi-arch in CI needs the amd64 build working in CI, which is blocked on the roku `forgejo-runner` failing every job at `actions/checkout` — tracked in **james.oates/homelab#6**. That CI automation is being folded into the in-progress server rebuild. Closing: the deliverable (multi-arch image available for both arches) is met. The CI-automation remainder is carried by james.oates/homelab#6.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
IsoHex/edge-router#10
No description provided.