refactor(ci): split docker-publish into native-arch matrix + manifest merge#40
Merged
Merged
Conversation
… merge The previous workflow built linux/amd64 + linux/arm64 in one buildx invocation on an x86_64 runner, which forced arm64 through QEMU. For the Python backend (Pillow, brother_ql with C extensions) that took ~3 minutes per leg; the whole publish ran ~3:30 end-to-end. GitHub has shipped free native arm64 runners (`ubuntu-24.04-arm`) for public repos since 2025-01. This refactor exploits them. Phase 1 — build (4 jobs in parallel): matrix = service × platform → backend/amd64, backend/arm64, frontend/amd64, frontend/arm64. Each leg runs on a NATIVE runner, builds its single-platform image, and pushes it to the registry by digest (no tag). The digest is exported as a per-leg artifact. Phase 2 — merge (2 jobs in parallel): matrix = service → backend, frontend. Each job downloads its two digest artifacts and calls `docker buildx imagetools create` to compose a multi-arch manifest list pointing at the per-platform digests, applying every tag (1.0.0, 1.0, 1, latest) and the index-level annotations in one shot. No re-build, no re-push of layers — just manifest assembly (~5 seconds). Expected end-to-end run time: ~1:30 instead of ~3:30 (slowest single- platform build dominates, not the sum). Side benefits: - Per-(service, arch) cache scope so amd64 and arm64 don't trash each other's caches. - Image-index annotations are emitted explicitly via the merge step, filtered to the `index:` prefix so the build phase's per-platform manifests are not double-annotated. - The Verify-Step is unchanged and still asserts both architectures are present on every published tag. No image, label, annotation, or tag scheme is changing — the only visible difference is faster runs.
|
Note Gemini is unable to generate a summary for this pull request due to the file types involved not being currently supported. |
There was a problem hiding this comment.
Pull request overview
Refactors the docker-publish GitHub Actions workflow into a two-phase pipeline that builds linux/amd64 and linux/arm64 on native runners, then merges the per-arch digests into a multi-arch manifest list via docker buildx imagetools create to avoid QEMU overhead.
Changes:
- Split publishing into a phase-1
(service × platform)native build matrix that pushes by digest and uploads digest artifacts. - Add a phase-2 per-service merge job that composes and tags a multi-arch index from the collected digests and applies index annotations.
- Adjust cache scoping to be per
(service, arch).
Comment on lines
136
to
144
| context: ./${{ matrix.service }} | ||
| file: ./${{ matrix.service }}/Dockerfile | ||
| platforms: linux/amd64,linux/arm64 | ||
| push: true | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| platforms: ${{ matrix.platform }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| # Pass through the annotations metadata-action computed (with both | ||
| # manifest:* and index:* levels, see DOCKER_METADATA_ANNOTATIONS_LEVELS | ||
| # at the workflow root). Without this the package shows up on GHCR | ||
| # with "No description provided". | ||
| annotations: ${{ steps.meta.outputs.annotations }} | ||
| # Build-args flow into the Dockerfile's ARG VERSION / REVISION / | ||
| # BUILD_DATE, which the Dockerfile then bakes into both OCI image | ||
| # labels (via LABEL) and runtime ENV vars (HUB_VERSION, …) so the | ||
| # running app can surface them through /healthz. | ||
| # `push-by-digest=true` skips the tag write and returns digest in | ||
| # `steps.build.outputs.digest`. `name-canonical=true` makes the | ||
| # registry reference the image by its canonical name. | ||
| outputs: type=image,name=${{ env.REGISTRY_GHCR }}/${{ github.repository }}-${{ matrix.service }},push-by-digest=true,name-canonical=true,push=true | ||
| build-args: | |
Comment on lines
+264
to
+267
| env: | ||
| DIGEST_REPO: ${{ env.REGISTRY_GHCR }}/${{ github.repository }}-${{ matrix.service }} | ||
| TAGS: ${{ steps.meta.outputs.tags }} | ||
| ANNOTATIONS: ${{ steps.meta.outputs.annotations }} |
Comment on lines
+179
to
+185
| name: Merge manifest (${{ matrix.service }}) | ||
| runs-on: ubuntu-24.04 | ||
| needs: build | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| service: [backend, frontend] |
Comment on lines
+193
to
+194
| merge-multiple: true | ||
|
|
github-actions Bot
pushed a commit
that referenced
this pull request
May 11, 2026
## <small>0.2.1 (2026-05-11)</small> * fix(ci): emit GHCR package description as index annotation (#39) ([12c6b6c](12c6b6c)), closes [#39](#39) * fix(ci): lowercase image ref before push-by-digest (#41) ([9dd954e](9dd954e)), closes [#41](#41) * fix(ci): repair docker-publish.yml startup failure (#37) ([fb7cb59](fb7cb59)), closes [#37](#37) * fix(ci): repair Verify multi-arch manifest step + drop fail-fast (#38) ([5d2ff7d](5d2ff7d)), closes [#38](#38) * refactor(ci): split docker-publish into native-arch matrix + manifest merge (#40) ([8cd824d](8cd824d)), closes [#40](#40) * chore(deps): bump github.com/go-chi/chi/v5 from 5.1.0 to 5.2.2 in /frontend (#36) ([a5971b9](a5971b9)), closes [#36](#36) [skip ci]
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.
Motivation
Current pipeline (after #34 / #37 / #38 / #39) builds
linux/amd64andlinux/arm64in one buildx invocation on an x86_64 runner. arm64 goes through QEMU emulation, which for the Python backend (Pillow + brother_ql with C extensions) costs ~3 minutes per leg. End-to-end run: ~3:30.GitHub has free native arm64 runners (
ubuntu-24.04-arm) for public repos since 2025-01. This PR uses them.Architecture
Two-phase pipeline, both phases use matrix parallelism:
The merge phase doesn't re-build anything — it just composes a manifest list pointing at the digests the build phase produced. End-to-end run time is now dominated by the slowest single-platform build, not the sum.
What stays the same
ghcr.io/strausmann/label-printer-hub-{backend,frontend}1.0.0,1.0,1,latest(stable) / full version only (pre-release)What's new
(service, arch)GHA cache scope (was: per-service only)imagetools createstep that composes the manifest list and applies tags + index annotations in one gopush-by-digest=true,name-canonical=true) instead of by tagExpected speedup
(Numbers are best-effort — first run will repopulate caches per arch.)
Test plan
gh workflow run docker-publish.yml -f tag=0.2.0 --ref mainand time it.ghcr.io/strausmann/label-printer-hub-{backend,frontend}:0.2.0still multi-arch (amd64 + arm64)./healthzof pulled image still reportsversion=0.2.0,revision=….