ci: publish multi-arch operator images - #145
Open
zarcen wants to merge 1 commit into
Open
Conversation
zarcen
marked this pull request as draft
August 21, 2026 00:47
The published images are linux/amd64 only, which makes the operator uninstallable on an arm64 cluster: config/manager and config/daemon both reference the same ghcr.io/bootc-dev/bootc-operator tag, so neither the controller Deployment nor the daemon DaemonSet has an instance to run. The daemon in particular cannot be architecture independent, since it drives bootc on the host through nsenter resolved from its own filesystem. Nothing in the operator is architecture specific, so cross-compile both binaries via TARGETARCH rather than emulating the Go toolchain, build each architecture as its own CI matrix leg, and assemble a manifest list in the push job. Saving a manifest list through a CI artifact is poorly supported, whereas per-architecture archives round-trip cleanly. Push the list as v2s2 so the per-architecture manifests keep the media type published today, leaving the added list layer as the only change downstream sees. e2e stays on amd64, since bink publishes its node disk images for amd64 alone. Multi-arch node images are also out of scope: they may need platform-aware digest resolution. Signed-off-by: Wei-Chen Chen <zarcen@gmail.com> Closes: bootc-dev#144
zarcen
force-pushed
the
multiarch-operator-images
branch
from
August 21, 2026 01:04
7d88a1c to
7b3b091
Compare
zarcen
marked this pull request as ready for review
August 21, 2026 01:14
Collaborator
Author
Collaborator
Please go ahead, any contribution is welcome! |
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.
Closes #144.
The published images are
linux/amd64only, so the operator is not installable on an arm64 cluster.config/manager/manager.yamlandconfig/daemon/daemon.yamlboth reference the sameghcr.io/bootc-dev/bootc-operatortag, so neither the controller Deployment nor the daemon DaemonSet has an instance to run there. The daemon cannot be made architecture independent either, since it drivesbootcon the host throughnsenterresolved from its own filesystem, so its container has to match the node.Nothing in the operator is architecture specific, and the fedora-minimal base is already published for arm64, so this is build and publish plumbing rather than a code change.
What changed
Containerfilepins the buildroot stage to$BUILDPLATFORMand cross-compiles via$TARGETARCH. Both binaries are pure Go, and the final stage has noRUNsteps, so no emulation is involved at any point — a non-native build costs about the same as a native one.make buildimg PLATFORM=linux/arm64cross-builds a single image andmake buildimg-allbuilds the manifest list. Plainmake buildimgis unchanged and still builds for the host.Downstream compatibility
The tags under
ghcr.io/bootc-dev/bootc-operatorbecome manifest lists rather than single manifests, so it is worth being explicit about what existing consumers see.The list is pushed with
--format v2s2, which keeps the per-architecture manifests asapplication/vnd.docker.distribution.manifest.v2+json, the exact media type published today. The added list layer is therefore the only change. Pushing an OCI index instead would also have changed the child manifest media type, which seemed like unnecessary churn for a change about arm64 — happy to switch if maintainers would rather move that way, it is one flag.Existing digest pins keep resolving, since nothing is removed from the registry. Tag consumers get platform selection for free on Docker, containerd, CRI-O and podman. Tooling that assumes a tag resolves to a single manifest, such as anything reading an image config straight off a tag, would need to handle a list; I did not find such a consumer in this repo. The bink and e2e path is untouched, because
deploy-binkpushes a plain single-architecture image to the local registry rather than a list.Deliberately not included
config/manager/manager.yamlstill carries the commented-out kubebuildernodeAffinityscaffold, whose text this change makes stale: it points at adocker-buildxtarget that does not exist here, and once the image is a manifest list kubelet selects the right instance without any arch constraint. I raised it in #144 as a question rather than deciding it here, to keep this PR to one logical change. Say the word and I will drop the block or narrow it tokubernetes.io/os: linux.arm64 e2e coverage is also out of scope and wants its own issue: bink publishes its node disk images for amd64 alone, and GitHub's hosted arm64 runners do not expose
/dev/kvm, so it is a runner-infrastructure question rather than CI plumbing.Multi-arch node images are out of scope too.
internal/registry/resolver.goresolves a tag withremote.Get, which returns the index digest for a manifest list, whileinternal/daemon/reconciler.gocompares that against the digestbootc statusreports for the booted image. Every image in play today is a single manifest, so the two cannot disagree, but that may need platform-aware resolution later.How to validate
CI covers this end to end. Locally, verified with podman 5.6.2 on an arm64 host:
Pushing the assembled list to a local registry produces an
application/vnd.docker.distribution.manifest.list.v2+jsonwith both instances, andpodman pull --arch amd64|arm64against it selects the matching image. Each cross-build took about 1m40s on the same host, confirming no emulation is in the path.Generated-by: AI
I am knowledgeable in this problem domain and reviewed it carefully.