Refactor image build, create multi-arch images, drop Builder usage#347
Refactor image build, create multi-arch images, drop Builder usage#347
Conversation
This PR fundamentally changes how our images are built. The usage of the Builder container is dropped in favor of "native" build using BuildKit with docker/build-push-action. Dockerfiles are now the single source of truth for all labels and build arguments - the build metadata (version, date, architecture, repository) is passed via --build-arg and consumed directly in the Dockerfile's LABEL instruction, removing the need for external label injection. Build caching uses GitHub Actions cache as the primary backend, with inline cache metadata embedded in pushed images as a fallback for cache reuse across git refs (since GHA cache is scoped per branch/tag). Registry images are verified with cosign before being used as cache sources. Images are compressed with zstd (level 9) instead of gzip, reducing image size and improving pull times on registries and runtimes that support it. Multi-arch support is handled by building per-architecture images in parallel on native runners (amd64 on ubuntu-24.04, aarch64 on ubuntu-24.04-arm), then combining them into a single manifest list using docker buildx imagetools. The reusable builder workflow (.github/workflows/reuseable-builder.yml) and the build-image composite action (.github/actions/build-image/) are designed to be generic enough to be extracted to the original home-assistant/builder repo, replacing the current docker-in-docker approach with a simpler, more cacheable workflow. Thanks to the caching, the builder workflow now also runs on push to the master branch, keeping the GHA cache warm for release builds without adding significant CI cost.
|
The build failures for Python are expected - it's a chicken-egg problem. Without having The builds were tested in my fork, so I'd say the CI can be ignored here - after merge, the base image should be published before the Python builds and everything should pass. |
This PR fundamentally changes how our images are built. The usage of the Builder container is dropped in favor of "native" build using BuildKit with docker/build-push-action. Dockerfiles are now the single source of truth for all labels and build arguments - the build metadata (version, date, architecture, repository) is passed via --build-arg and consumed directly in the Dockerfile's LABEL instruction, removing the need for external label injection. Build caching uses GitHub Actions cache as the primary backend, with inline cache metadata embedded in pushed images as a fallback for cache reuse across git refs (since GHA cache is scoped per branch/tag). Registry images are verified with cosign before being used as cache sources. Images are compressed with zstd (level 9) instead of gzip, reducing image size and improving pull times on registries and runtimes that support it. Multi-arch support is handled by building per-architecture images in parallel on native runners (amd64 on ubuntu-24.04, aarch64 on ubuntu-24.04-arm), then combining them into a single manifest list using docker buildx imagetools. Thanks to the caching, the builder workflow now also runs on push to the master branch, keeping the GHA cache warm for release builds without adding significant CI cost. A reference implementation is in home-assistant/docker-base#347.
agners
left a comment
There was a problem hiding this comment.
Looks quite good to me.
I wonder if it will feel easier to follow what exactly is happening. The old build.yaml was kinda nice summary of all parameters. We do have some in the builder workflow, and some in the Dockerfile now. But tradeoffs... We'll see.
The builder workflow now essentially supplies only the build date, version and source repository, which were (or should have been) dynamically generated anyway. For example for the Python images, builder injects these args: The Where we make a little trade-off is the dependencies versions, which were nicely on a single place before, but nothing what |
|
FTR, home-assistant/builder#273 needs to be merged first and references to the |
Because the Cosign subject is derived from the running workflow, we need to run the action using Cosign in a local workflow instead of calling reusable workflow from another repo.
This PR provides a set of reusable composite actions that replace the Builder container with "native" BuildKit builds using docker/build-push-action. `actions/build-image` builds and optionally pushes and signs a single-architecture image. Build metadata (`BUILD_ARCH`, `BUILD_VERSION`) is passed to the Dockerfile via `--build-arg`, while OCI and Home Assistant labels (`io.hass.arch`, `io.hass.version`, `org.opencontainers.image.*`) are applied directly by the action through docker/build-push-action's label support. Additional build args and labels can be passed through the `build-args` and `labels` inputs. Images are compressed with zstd (level 9) instead of gzip, reducing image size and improving pull times on registries and runtimes that support it. Build caching uses GitHub Actions cache as the primary backend, with inline cache metadata embedded in pushed images as a fallback for cache reuse across git refs (since GHA cache is scoped per branch/tag). Pushed images are signed with Cosign, with retry and exponential backoff. Base and cache images can optionally be verified before the build starts. `actions/cosign-verify` verifies the Cosign signature of a container image against a certificate identity and OIDC issuer, with retry logic and an optional allow-failure mode. `actions/prepare-multi-arch-matrix` validates the requested architectures (amd64, aarch64) and outputs a JSON matrix mapping each to a native runner (ubuntu-24.04, ubuntu-24.04-arm) and a registry image name, ready to be consumed by a build matrix job. `actions/publish-multi-arch-manifest` combines per-architecture images into a single manifest list using `docker buildx imagetools create`, applies all requested tags, and signs the resulting manifest with Cosign. Together, these actions support a workflow where per-architecture images are built in parallel on native runners, then combined into a multi-arch manifest. Thanks to the caching, the build can also run on push to the master branch to keep the GHA cache warm for release builds without adding significant CI cost. A reference implementation is in home-assistant/docker-base#347.
|
|
||
| Images are built for all platforms officially supported by Home Assistant, which are `amd64` and `arm64`. | ||
|
|
||
| Beginning with the 2026.03.1 release, all images are published as multi-arch images for these platforms. The old architecture-prefixed images (`aarch64-*`, `amd64-*`) are still available but preferably the multi-arch images should be used. |
There was a problem hiding this comment.
Reads a bit confusing as its about multi arch, then the prefixed, then multi arch again. Maybe:
| Beginning with the 2026.03.1 release, all images are published as multi-arch images for these platforms. The old architecture-prefixed images (`aarch64-*`, `amd64-*`) are still available but preferably the multi-arch images should be used. | |
| Beginning with the 2026.03.1 release, all images are published as multi-arch images under a single image name for all supported platforms. This image names should be used whenever possible. The old architecture-prefixed images (`aarch64-*`, `amd64-*`) are still available for compatibility. |

This PR drops the Builder container in favor of native BuildKit builds using the reusable actions and workflow introduced in home-assistant/builder#273.
Key changes in this PR:
home-assistant/builderio.hass.type,io.hass.base.name) rather than injected externally by the buildermaster, leveraging GHA cache and keeping it warm for release buildsSee home-assistant/builder#273 for details on the reusable workflow itself (caching strategy, cosign verification, zstd compression, runner selection, etc.).