Skip to content

CI: decouple multi-arch image publishing into a standalone GHCR pipeline#1087

Open
aatchison wants to merge 7 commits into
mainfrom
ci/split-arch-image-build
Open

CI: decouple multi-arch image publishing into a standalone GHCR pipeline#1087
aatchison wants to merge 7 commits into
mainfrom
ci/split-arch-image-build

Conversation

@aatchison

@aatchison aatchison commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What

Splits container-image publishing out of the deploy pipeline into a standalone GHCR pipeline, and moves the multi-arch (amd64 + arm64) build onto native runners (no QEMU) using the split-worker strategy from sredevopsorg/multi-arch-docker-github-workflow.

Two independent pipelines

Pipeline Workflow Registry Arch Deploys?
Deploy-stage merge.yml ECR amd64 ✅ Pulumi → stage
Publish-images (new) publish-images.yml + build-multiarch-image.yml GHCR amd64 + arm64 ❌ publish only

See docs/ci/image-publish-pipeline.md for a mermaid diagram of both.

Changes

  • merge.yml — reverts the deploy build to single-arch amd64 → ECR (undoing the QEMU/Buildx multi-arch added in ci: build accounts image multi-arch (amd64+arm64) for Graviton EKS #1027 / ci(keycloak): build the keycloak image multi-arch (amd64 + arm64) #974). Deploy, artifacts, and detect-changes (its own job) are otherwise unchanged.
  • publish-images.yml (new) — on merge to main (or workflow_dispatch): a separate detect-changes job decides which image changed and reads the version, then builds accounts and keycloak to GHCR. No cloud credentials, no Pulumi.
  • build-multiarch-image.yml — reusable native split-arch builder targeting GHCR via GITHUB_TOKEN: amd64 on ubuntu-latest, arm64 on ubuntu-24.04-arm, pushed by digest, merged into one manifest list tagged :<sha>, :v<version>, :latest. Separate GHCR packages per image (.../thunderbird-accounts, .../thunderbird-accounts-keycloak).
  • docs/ci/image-publish-pipeline.md — architecture diagram + rationale.

Verified

An earlier revision of this branch ran the native builds green (all four amd64/arm64 build jobs succeeded; arm64 on native Graviton runners ~165s for accounts). actionlint passes on all three workflows.

⚠️ Reviewer call-out: arm64 in production

Reverting merge.yml to amd64-only means ECR no longer gets an arm64 image. The Graviton/arm64 EKS need that #1027/#974 addressed is now served only from GHCR. This is intentional per the decoupling, but anything currently pulling the arm64 variant from ECR must be repointed to GHCR (or ECR multi-arch restored) before those consumers rely on it. Flagging so it's a conscious decision.

Action versions (reviewer note)

All third-party actions in the two new workflows are pinned to a commit SHA (with a # vX.Y.Z comment); no existing workflow was touched. Getting to "latest" meant some notable major bumps that differ from the v3/v4 tags the rest of the repo pins:

Action Repo elsewhere New workflows
actions/checkout v4 v7.0.0
docker/login-action v3 v4.4.0
docker/setup-buildx-action v3 v4.2.0
actions/upload-artifact v4 v7.0.1
actions/download-artifact v4 v8.0.1
dorny/paths-filter v3 v4.0.2
SebRollen/toml-action v1.2.0 v1.2.0 (already latest)

Low-risk for our simple usage, and upload-artifact@v7download-artifact@v8 are the intended compatible pair (v8 supports v7's direct-upload feature). Flagging in case reviewers prefer version consistency with the rest of the repo — any single one is easy to dial back.

Follow-up

Deploy-side companion (pull the accounts image from GHCR on the Graviton EKS clusters): thunderbird/accounts-deploy#25 (draft) — blocked on this PR merging + the GHCR images publishing.

Notes

🤖 Generated with Claude Code

aatchison and others added 4 commits July 8, 2026 10:15
Replace the QEMU-emulated `docker buildx --platform amd64,arm64` build in
the deploy-stage workflow with a per-arch native-runner strategy (adapted
from sredevopsorg/multi-arch-docker-github-workflow):

- New reusable workflow build-multiarch-image.yml builds one arch per
  native runner (ubuntu-latest for amd64, ubuntu-24.04-arm for arm64),
  pushes each to ECR by digest, then stitches them into a manifest list
  with `docker buildx imagetools create`. Emitted tag is unchanged.
- merge.yml now calls it once for accounts and once for keycloak, gated on
  the same detect-changes outputs, and the deploy job consumes the
  resulting manifest tags instead of building inline (QEMU/buildx/ECR-login
  steps dropped from deploy).

Native builds run in parallel and skip emulation overhead. The published
tags stay multi-arch manifest lists, so release.yml's retag step and the
ECS (amd64) / EKS Graviton (arm64) runtimes are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lets this branch exercise the split-arch build (and stage deploy). Revert
before merging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For collecting evidence that the native-runner arm64/amd64 builds work,
before wiring up ECR/deploy:

- merge.yml: trigger on every push; deploy job disabled via an always-false
  condition (cascades to skip create-release + e2e); drop `secrets: inherit`
  from the build caller jobs.
- build-multiarch-image.yml: build each arch natively but do NOT push;
  AWS/ECR steps, digest export/upload, and the imagetools merge are
  commented out. Merge job neutralized to a no-op.

This also resolves the ci-cd-trust-boundary finding for the branch trigger:
with no secrets:inherit and no role assumption on the build path, and deploy
disabled, no secret-bearing job runs on branch pushes. All changes are
marked TEMPORARY and reverted before merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reworks PR to separate image publishing from deployment:

- merge.yml: revert the deploy-stage build to single-arch amd64 -> ECR
  (removes the QEMU/Buildx multi-arch build added in #1027/#974). Deploy
  behavior is otherwise unchanged; change detection stays its own job.
- publish-images.yml (new): standalone pipeline that, on merge to main,
  detects which image changed (separate detect-changes job), reads the
  version, and builds accounts + keycloak as native multi-arch manifest
  lists pushed to GHCR. No cloud creds, no Pulumi, no deploy.
- build-multiarch-image.yml: reusable native split-arch builder, now
  targeting GHCR via GITHUB_TOKEN. Builds amd64 (ubuntu-latest) and arm64
  (ubuntu-24.04-arm) on native runners, pushes by digest, merges into one
  manifest list tagged :<sha>, :v<version>, and :latest. Separate GHCR
  packages per image.
- docs/ci/image-publish-pipeline.md: mermaid architecture diagram of both
  pipelines and why they're decoupled.

Decoupling keeps cloud credentials out of the image-publish path (resolves
the earlier ci-cd-trust-boundary concern) and moves arm64 off QEMU onto
native Graviton runners.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aatchison aatchison changed the title CI: split arm64/amd64 image builds onto native runners CI: decouple multi-arch image publishing into a standalone GHCR pipeline Jul 8, 2026
publish-images.yml (change detection):
- accounts-changed now includes keycloak/themes/** -- the accounts frontend
  imports the shared keycloak theme via the @kc vite alias, so `npm run build`
  bakes it into the accounts image; theme edits must rebuild accounts too.
- fix vite.config.mjs -> vite.config.mts typo; add tsconfig.json, env.d.ts
  (all COPYd by the Dockerfile) so they trigger rebuilds.
- broaden scripts/entry.sh -> scripts/** (Dockerfile COPYs the whole dir).
- drop README.md as a trigger (docs-only, no runtime effect) to avoid a full
  multi-arch republish + :latest churn on doc changes.
- concurrency cancel-in-progress: false so a merged commit's :<sha> image is
  never dropped and a run is never aborted mid-push.

build-multiarch-image.yml (build):
- --provenance=false so each per-arch push is a plain single manifest (clean
  2-entry merged manifest list, no attestation index).
- add GitHub Actions layer cache (type=gha) scoped per image+arch; ephemeral
  runners otherwise rebuild every layer on every merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aatchison aatchison marked this pull request as ready for review July 8, 2026 18:24
Same change-detection gaps the GHCR pipeline review surfaced, applied to the
deploy pipeline's src-changed filter (which gates the accounts stage deploy):

- fix vite.config.mjs -> vite.config.mts typo; add tsconfig.json + env.d.ts
  (all COPYd by the Dockerfile).
- add scripts/** (Dockerfile COPYs the whole dir; was only scripts/entry.sh).
- add keycloak/themes/** -- the accounts bundle imports the shared keycloak
  theme via the @kc vite alias, so a theme-only change previously rebuilt the
  keycloak image but NOT accounts, deploying a stale accounts frontend.

Additive only (never under-builds); no change to deploy behavior otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update and SHA-pin every third-party action in the two new workflows
(build-multiarch-image.yml, publish-images.yml). No existing workflow touched.

  actions/checkout            v4  -> v7.0.0
  docker/login-action         v3  -> v4.4.0
  docker/setup-buildx-action  v3  -> v4.2.0
  actions/upload-artifact     v4  -> v7.0.1
  actions/download-artifact   v4  -> v8.0.1  (v8 pairs with upload v7)
  dorny/paths-filter          v3  -> v4.0.2
  SebRollen/toml-action       v1.2.0 (pinned; already latest)

Each is pinned to the commit SHA the release tag resolves to, with the
version in a trailing comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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