From 23e02d9b5484bf4e8a578394732d0771a454066e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 3 Mar 2026 18:52:27 +0100 Subject: [PATCH 01/23] Refactor image build, create multi-arch images, drop Builder usage 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. --- .github/actions/build-image/action.yml | 245 ++++++++++++++++++++++ .github/actions/cosign-verify/action.yml | 61 ++++++ .github/workflows/builder.yml | 192 ++++++++++------- .github/workflows/reuseable-builder.yml | 255 +++++++++++++++++------ README.md | 12 +- alpine/Dockerfile | 60 ++++-- alpine/build.yaml | 14 -- debian/Dockerfile | 58 ++++-- debian/build.yaml | 13 -- python/3.12/Dockerfile | 36 +++- python/3.12/build.yaml | 15 -- python/3.13/Dockerfile | 34 ++- python/3.13/build.yaml | 15 -- python/3.14/Dockerfile | 36 +++- python/3.14/build.yaml | 15 -- ubuntu/Dockerfile | 58 ++++-- ubuntu/build.yaml | 13 -- 17 files changed, 827 insertions(+), 305 deletions(-) create mode 100644 .github/actions/build-image/action.yml create mode 100644 .github/actions/cosign-verify/action.yml delete mode 100644 alpine/build.yaml delete mode 100644 debian/build.yaml delete mode 100644 python/3.12/build.yaml delete mode 100644 python/3.13/build.yaml delete mode 100644 python/3.14/build.yaml delete mode 100644 ubuntu/build.yaml diff --git a/.github/actions/build-image/action.yml b/.github/actions/build-image/action.yml new file mode 100644 index 00000000..37424c78 --- /dev/null +++ b/.github/actions/build-image/action.yml @@ -0,0 +1,245 @@ +name: Build image +description: Build, push, and optionally sign a single-arch container image + +inputs: + arch: + description: Architecture to build (e.g., "amd64") + required: true + platform: + description: Platform string (e.g., "linux/amd64") + required: true + image: + description: Full image name without tag (e.g., "ghcr.io/home-assistant/amd64-base") + required: true + image_tag: + description: Main image tag (e.g., "3.23") + required: true + image_extra_tags: + description: Additional tags, one per line + required: false + default: "" + context: + description: Build context (usually the directory with Dockerfile) + required: true + file: + description: Dockerfile path (defaults to "Dockerfile" in the context directory) + required: false + default: "" + version: + description: Image version label + required: true + build_args: + description: Additional build arguments (key=value format, one per line) + required: false + default: "" + push: + description: Whether to push images to registry + required: false + default: "false" + cache_scope: + description: Scope for build cache sharing (defaults to architecture, set if building multiple images from a single repo) + required: false + default: "" + cache_image_tag: + description: Tag of the image containing BuildKit inline cache metadata + required: false + default: "latest" + docker_registry: + description: Docker registry (e.g., "ghcr.io") + required: false + default: "ghcr.io" + docker_username: + description: Username for Docker registry (defaults to repository owner) + required: false + default: ${{ github.repository_owner }} + docker_password: + description: Password for Docker registry (use secrets.GITHUB_TOKEN for GHCR) + required: true + cosign: + description: Whether to sign images with Cosign + required: false + default: "true" + cosign_identity: + description: Certificate identity regexp for verifying cache images (defaults to current repo pattern) + required: false + default: "" + cosign_issuer: + description: Certificate OIDC issuer regexp for all cosign verification + required: false + default: "https://token.actions.githubusercontent.com" + verify_base: + description: Base image reference to verify with cosign before building + required: false + default: "" + cosign_base_identity: + description: Certificate identity regexp for verifying the base (FROM) image + required: false + default: "" + cosign_base_issuer: + description: Certificate OIDC issuer regexp for base image verification (defaults to cosign_issuer) + required: false + default: "" + +outputs: + digest: + description: Image digest from the build + value: ${{ steps.build.outputs.digest }} + +runs: + using: composite + steps: + - name: Login to Docker Registry + if: inputs.push == 'true' + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 + with: + registry: ${{ inputs.docker_registry }} + username: ${{ inputs.docker_username }} + password: ${{ inputs.docker_password }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - name: Install Cosign + if: inputs.cosign == 'true' || inputs.verify_base != '' + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + with: + cosign-release: "v2.5.3" + + - name: Verify cache image ${{ inputs.image }}:${{ inputs.cache_image_tag }} + if: inputs.cosign == 'true' + id: verify_cache + uses: ./.github/actions/cosign-verify + with: + image: ${{ inputs.image }}:${{ inputs.cache_image_tag }} + cosign_identity: ${{ inputs.cosign_identity || env.DEFAULT_COSIGN_IDENTITY }} + cosign_issuer: ${{ inputs.cosign_issuer }} + allow_failure: true + env: + DEFAULT_COSIGN_IDENTITY: https://github.com/${{ github.repository }}/.* + + - name: Verify base image + if: inputs.push == 'true' && inputs.verify_base != '' && inputs.cosign_base_identity != '' + uses: ./.github/actions/cosign-verify + with: + image: ${{ inputs.verify_base }} + cosign_identity: ${{ inputs.cosign_base_identity }} + cosign_issuer: ${{ inputs.cosign_base_issuer || inputs.cosign_issuer }} + allow_failure: false + + - name: Set build options + id: options + shell: bash + env: + IMAGE: ${{ inputs.image }} + IMAGE_TAG: ${{ inputs.image_tag }} + IMAGE_EXTRA_TAGS: ${{ inputs.image_extra_tags }} + PUSH: ${{ inputs.push }} + VERSION: ${{ inputs.version }} + ARCH: ${{ inputs.arch }} + GITHUB_REPOSITORY: ${{ github.repository }} + BUILD_ARGS_INPUT: ${{ inputs.build_args }} + COSIGN_ENABLED: ${{ inputs.cosign }} + CACHE_SCOPE: ${{ inputs.cache_scope }} + CACHE_VERIFIED: ${{ steps.verify_cache.outputs.verified }} + FILE_INPUT: ${{ inputs.file }} + CONTEXT: ${{ inputs.context }} + run: | + tags=() + tags+=("${IMAGE}:${IMAGE_TAG}") + while IFS= read -r tag; do + [[ -n "$tag" ]] && tags+=("${IMAGE}:${tag}") + done <<< "${IMAGE_EXTRA_TAGS}" + + { + echo "tags<> "$GITHUB_OUTPUT" + + build_date="$(date --rfc-3339=seconds --utc)" + + build_args=() + build_args+=("BUILD_VERSION=${VERSION}") + build_args+=("BUILD_ARCH=${ARCH}") + build_args+=("BUILD_DATE=${build_date}") + build_args+=("BUILD_REPOSITORY=https://github.com/${GITHUB_REPOSITORY}") + while IFS= read -r line; do + [[ -n "$line" ]] && build_args+=("$line") + done <<< "${BUILD_ARGS_INPUT}" + { + echo "build_args<> "$GITHUB_OUTPUT" + + if [[ "${PUSH}" == "true" ]]; then + echo output="type=image,push=true,compression=zstd,compression-level=9,force-compression=true,oci-mediatypes=true" >> "$GITHUB_OUTPUT" + else + echo output="type=docker" >> "$GITHUB_OUTPUT" + fi + + if [[ -z "${CACHE_SCOPE}" ]]; then + cache_scope="${ARCH}" + else + cache_scope="${ARCH}-${CACHE_SCOPE}" + fi + echo cache_scope="${cache_scope}" >> "$GITHUB_OUTPUT" + + cache_from=("type=gha,scope=${cache_scope}") + if [[ "${COSIGN_ENABLED}" != "true" ]] || [[ "${CACHE_VERIFIED}" == "true" ]]; then + cache_from+=("type=registry,ref=${IMAGE}:${IMAGE_TAG}") + fi + { + echo "cache_from<> "$GITHUB_OUTPUT" + + if [ -z "${FILE_INPUT}" ]; then + echo file="${CONTEXT}/Dockerfile" >> "$GITHUB_OUTPUT" + else + echo file="${FILE_INPUT}" >> "$GITHUB_OUTPUT" + fi + + - name: Build image + id: build + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 + with: + context: ${{ env.CONTEXT }} # zizmor: ignore[template-injection] + file: ${{ steps.options.outputs.file }} + platforms: ${{ inputs.platform }} + pull: true + push: ${{ inputs.push == 'true' }} + load: ${{ inputs.push != 'true' }} + build-args: ${{ steps.options.outputs.build_args }} + tags: ${{ steps.options.outputs.tags }} + outputs: ${{ steps.options.outputs.output }} + cache-to: | + type=inline + type=gha,mode=max,scope=${{ steps.options.outputs.cache_scope }} + cache-from: ${{ steps.options.outputs.cache_from }} + + env: + CONTEXT: ${{ inputs.context }} + + - name: Sign per-arch image + if: inputs.push == 'true' && inputs.cosign == 'true' + shell: bash + env: + IMAGE_REF: ${{ inputs.image }}@${{ steps.build.outputs.digest }} + run: | + echo "::group::Signing image: ${IMAGE_REF}" + + for i in {1..5}; do + if cosign sign --yes "${IMAGE_REF}"; then + echo "Signed: ${IMAGE_REF}" + exit 0 + fi + echo "Signing attempt ${i} failed, retrying..." + sleep $((2 ** i)) + done + + echo "::endgroup::" + + echo "::error::Failed to sign image ${IMAGE_REF}" + exit 1 diff --git a/.github/actions/cosign-verify/action.yml b/.github/actions/cosign-verify/action.yml new file mode 100644 index 00000000..9acf292e --- /dev/null +++ b/.github/actions/cosign-verify/action.yml @@ -0,0 +1,61 @@ +name: Verify a signature on the supplied container image +description: | + Verify Cosign signature of a container image. + Requires Cosign to be installed in the runner environment. + +inputs: + image: + description: Container image reference to verify + cosign_identity: + description: Certificate identity regexp for verifying cache images (defaults to current repo pattern) + required: false + default: "" + cosign_issuer: + description: Certificate OIDC issuer regexp for all cosign verification + allow_failure: + description: Whether to allow failure of this step (defaults to false). Only shows a warning if verification fails. + required: false + default: "false" + +outputs: + verified: + description: Whether the image was successfully verified with Cosign + value: ${{ steps.verify.outputs.verified }} + +runs: + using: composite + steps: + - name: Verify the image + id: verify + shell: bash + env: + IMAGE_REF: ${{ inputs.image }} + COSIGN_IDENTITY: ${{ inputs.cosign_identity }} + COSIGN_ISSUER: ${{ inputs.cosign_issuer }} + ALLOW_FAILURE: ${{ inputs.allow_failure }} + run: | + echo "::group::Verifying image: ${IMAGE_REF}" + + for i in {1..5}; do + if cosign verify \ + --certificate-identity-regexp "${COSIGN_IDENTITY}" \ + --certificate-oidc-issuer-regexp "${COSIGN_ISSUER}" \ + "${IMAGE_REF}"; then + echo "Image verified: ${IMAGE_REF}" + echo "verified=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "Verification attempt ${i} failed, retrying..." + sleep $((2 ** i)) + done + + echo "::endgroup::" + + echo "verified=false" >> "$GITHUB_OUTPUT" + + if [[ "${ALLOW_FAILURE}" == "true" ]]; then + echo "::warning::Image verification failed for ${IMAGE_REF}, ignoring" + else + echo "::error::Image verification failed for ${IMAGE_REF}" + exit 1 + fi diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index b1b63825..1d526214 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -1,129 +1,167 @@ name: Build base images on: + push: + branches: ["master"] pull_request: branches: ["master"] release: types: ["published"] env: - BUILD_TYPE: base + REGISTRY_PREFIX: ghcr.io/${{ github.repository_owner }} + ARCHITECTURES: '["amd64", "aarch64"]' ALPINE_LATEST: "3.23" DEBIAN_LATEST: "trixie" UBUNTU_LATEST: "24.04" PYTHON_LATEST: "3.14" +permissions: + contents: read + id-token: write # zizmor: ignore[excessive-permissions] + packages: write # zizmor: ignore[excessive-permissions] + jobs: init: name: Initialize build runs-on: ubuntu-latest + permissions: + contents: read outputs: - architectures_alpine: ${{ steps.info_alpine.outputs.architectures }} - architectures_debian: ${{ steps.info_debian.outputs.architectures }} - architectures_ubuntu: ${{ steps.info_ubuntu.outputs.architectures }} - release: ${{ steps.version.outputs.version }} - alpine_latest: ${{ steps.set-latest.outputs.alpine_latest}} - debian_latest: ${{ steps.set-latest.outputs.debian_latest}} - ubuntu_latest: ${{ steps.set-latest.outputs.ubuntu_latest}} - python_latest: ${{ steps.set-latest.outputs.python_latest}} + architectures: ${{ steps.meta.outputs.architectures }} + version: ${{ steps.meta.outputs.version }} + alpine_latest: ${{ steps.meta.outputs.alpine_latest }} + debian_latest: ${{ steps.meta.outputs.debian_latest }} + ubuntu_latest: ${{ steps.meta.outputs.ubuntu_latest }} + python_latest: ${{ steps.meta.outputs.python_latest }} + registry_prefix: ${{ steps.meta.outputs.registry_prefix }} + push: ${{ steps.meta.outputs.push }} steps: - - name: Checkout the repository - uses: actions/checkout@v6.0.2 - with: - fetch-depth: 0 - - - name: Get information Alpine - id: info_alpine - uses: home-assistant/actions/helpers/info@master - with: - path: "${{ github.workspace }}/alpine" - - - name: Get information Debian - id: info_debian - uses: home-assistant/actions/helpers/info@master - with: - path: "${{ github.workspace }}/debian" - - - name: Get information Ubuntu - id: info_ubuntu - uses: home-assistant/actions/helpers/info@master - with: - path: "${{ github.workspace }}/ubuntu" - - - name: Get version - id: version - uses: home-assistant/actions/helpers/version@master - with: - type: ${{ env.BUILD_TYPE }} - - - name: Set latest tags - id: set-latest + - name: Set build metadata + id: meta + shell: bash + env: + EVENT_NAME: ${{ github.event_name }} + RELEASE_TAG: ${{ github.event.release.tag_name }} + REGISTRY_PREFIX: ${{ env.REGISTRY_PREFIX }} + ARCHITECTURES: ${{ env.ARCHITECTURES }} + ALPINE_LATEST: ${{ env.ALPINE_LATEST }} + DEBIAN_LATEST: ${{ env.DEBIAN_LATEST }} + UBUNTU_LATEST: ${{ env.UBUNTU_LATEST }} + PYTHON_LATEST: ${{ env.PYTHON_LATEST }} run: | - echo "alpine_latest=${{ env.ALPINE_LATEST }}" >> $GITHUB_OUTPUT - echo "debian_latest=${{ env.DEBIAN_LATEST }}" >> $GITHUB_OUTPUT - echo "ubuntu_latest=${{ env.UBUNTU_LATEST }}" >> $GITHUB_OUTPUT - echo "python_latest=${{ env.PYTHON_LATEST }}" >> $GITHUB_OUTPUT + if [[ "${EVENT_NAME}" == "release" ]]; then + version="${RELEASE_TAG}" + push=true + else + version="${GITHUB_SHA::7}" + fi + + echo "architectures=${ARCHITECTURES}" >> "$GITHUB_OUTPUT" + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "push=${push}" >> "$GITHUB_OUTPUT" + echo "alpine_latest=${ALPINE_LATEST}" >> "$GITHUB_OUTPUT" + echo "debian_latest=${DEBIAN_LATEST}" >> "$GITHUB_OUTPUT" + echo "ubuntu_latest=${UBUNTU_LATEST}" >> "$GITHUB_OUTPUT" + echo "python_latest=${PYTHON_LATEST}" >> "$GITHUB_OUTPUT" + echo "registry_prefix=${REGISTRY_PREFIX}" >> "$GITHUB_OUTPUT" build_alpine: - name: Alpine ${{ matrix.version }} + name: Alpine ${{ matrix.alpine_version }} needs: init strategy: + fail-fast: false matrix: - version: ["3.21", "3.22", "3.23"] + alpine_version: ["3.21", "3.22", "3.23"] uses: ./.github/workflows/reuseable-builder.yml with: - target: alpine - architectures: ${{ needs.init.outputs.architectures_alpine }} - version: ${{ matrix.version }} - release_name: ${{ needs.init.outputs.release }} - tag_latest: ${{ needs.init.outputs.alpine_latest }} + architectures: ${{ needs.init.outputs.architectures }} + registry_prefix: ${{ needs.init.outputs.registry_prefix }} + image_name: base + image_tag: ${{ matrix.alpine_version }} + image_extra_tags: | + ${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} + ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && 'latest' || '' }} + context: alpine + version: ${{ needs.init.outputs.version }} + build_args: | + ALPINE_VERSION=${{ matrix.alpine_version }} + push: ${{ needs.init.outputs.push == 'true' }} + cache_scope: alpine-${{ matrix.alpine_version }} + cache_image_tag: ${{ matrix.alpine_version }} build_debian: - name: Debian ${{ matrix.version }} + name: Debian ${{ matrix.debian_version }} needs: init strategy: fail-fast: false matrix: - version: ["bookworm", "trixie"] + debian_version: ["bookworm", "trixie"] uses: ./.github/workflows/reuseable-builder.yml with: - target: debian - architectures: ${{ needs.init.outputs.architectures_debian }} - version: ${{ matrix.version }} - release_name: ${{ needs.init.outputs.release }} - version_from: ${{ matrix.version }}-slim - tag_latest: ${{ needs.init.outputs.debian_latest }} + architectures: ${{ needs.init.outputs.architectures }} + registry_prefix: ${{ needs.init.outputs.registry_prefix }} + image_name: debian + image_tag: ${{ matrix.debian_version }} + image_extra_tags: | + ${{ matrix.debian_version }}-${{ needs.init.outputs.version }} + ${{ matrix.debian_version == needs.init.outputs.debian_latest && 'latest' || '' }} + context: debian + version: ${{ needs.init.outputs.version }} + build_args: | + DEBIAN_VERSION=${{ matrix.debian_version }} + push: ${{ needs.init.outputs.push == 'true' }} + cache_scope: debian-${{ matrix.debian_version }} + cache_image_tag: ${{ matrix.debian_version }} build_ubuntu: - name: Ubuntu ${{ matrix.version }} + name: Ubuntu ${{ matrix.ubuntu_version }} needs: init strategy: fail-fast: false matrix: - version: ["22.04", "24.04"] + ubuntu_version: ["22.04", "24.04"] uses: ./.github/workflows/reuseable-builder.yml with: - target: ubuntu - architectures: ${{ needs.init.outputs.architectures_ubuntu }} - version: ${{ matrix.version }} - release_name: ${{ needs.init.outputs.release }} - tag_latest: ${{ needs.init.outputs.ubuntu_latest }} + architectures: ${{ needs.init.outputs.architectures }} + registry_prefix: ${{ needs.init.outputs.registry_prefix }} + image_name: ubuntu + image_tag: ${{ matrix.ubuntu_version }} + image_extra_tags: | + ${{ matrix.ubuntu_version }}-${{ needs.init.outputs.version }} + ${{ matrix.ubuntu_version == needs.init.outputs.ubuntu_latest && 'latest' || '' }} + context: ubuntu + version: ${{ needs.init.outputs.version }} + build_args: | + UBUNTU_VERSION=${{ matrix.ubuntu_version }} + push: ${{ needs.init.outputs.push == 'true' }} + cache_scope: ubuntu-${{ matrix.ubuntu_version }} + cache_image_tag: ${{ matrix.ubuntu_version }} build_python: - name: Alpine ${{ matrix.version }} - python ${{ matrix.python }} + name: Python ${{ matrix.python_version }} (Alpine ${{ matrix.alpine_version }}) needs: [init, build_alpine] strategy: fail-fast: false matrix: - version: ["3.21", "3.22", "3.23"] - python: ["3.12", "3.13", "3.14"] + alpine_version: ["3.21", "3.22", "3.23"] + python_version: ["3.12", "3.13", "3.14"] uses: ./.github/workflows/reuseable-builder.yml with: - target: python/${{ matrix.python }} - architectures: ${{ needs.init.outputs.architectures_alpine }} - version: ${{ matrix.version }} - python: ${{ matrix.python }} - release_name: ${{ needs.init.outputs.release }} - version_from: ${{ matrix.version }} - tag_latest: ${{ needs.init.outputs.alpine_latest }} - python_latest: ${{ needs.init.outputs.python_latest }} + architectures: ${{ needs.init.outputs.architectures }} + registry_prefix: ${{ needs.init.outputs.registry_prefix }} + image_name: base-python + image_tag: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} + image_extra_tags: | + ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} + ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && matrix.python_version == needs.init.outputs.python_latest && 'latest' || '' }} + context: python/${{ matrix.python_version }} + version: ${{ needs.init.outputs.version }} + build_args: | + BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base + BASE_VERSION=${{ matrix.alpine_version }} + push: ${{ needs.init.outputs.push == 'true' }} + cache_scope: python-${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} + verify_base: ghcr.io/${{ github.repository_owner }}/base:${{ matrix.alpine_version }} + cosign_base_identity: "https://github.com/${{ github.repository }}/.*" + cache_image_tag: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} diff --git a/.github/workflows/reuseable-builder.yml b/.github/workflows/reuseable-builder.yml index 11631d30..e5e0fe6a 100644 --- a/.github/workflows/reuseable-builder.yml +++ b/.github/workflows/reuseable-builder.yml @@ -3,39 +3,126 @@ name: Reusable builder on: workflow_call: inputs: - target: - description: Target name + architectures: + description: Architectures to build (JSON array, e.g., '["amd64", "aarch64"]') required: true type: string - architectures: - description: List of architectures to build + multi_arch: + description: Prefix per-arch image names with architecture (required for multiple architectures) + required: false + default: true + type: boolean + registry_prefix: + description: Registry and namespace prefix (e.g., "ghcr.io/owner") required: true type: string - version: - description: Version to build + image_name: + description: Image name without a tag (e.g., "base-python") required: true type: string - python: - description: Python version to build + image_tag: + description: Base image tag (e.g., "3.23") + required: true + type: string + image_extra_tags: + description: Additional tags, one per line + required: false + default: "" type: string - release_name: - description: Release name + context: + description: Build context (usually the directory with Dockerfile) required: true type: string - version_from: - description: Version to build image from + file: + description: Dockerfile path (defaults to "Dockerfile" in the context directory) + required: false + default: "" type: string - tag_latest: - description: Tag to mark docker image as latest + version: + description: Image version label required: true type: string - python_latest: - description: Python tag to mark docker image as latest + build_args: + description: Additional build arguments (key=value format, one per line) + required: false + default: "" + type: string + push: + description: Whether to push images to registry + required: false + default: false + type: boolean + cache_scope: + description: Scope for build cache sharing (defaults to architecture, set if building multiple images from a single repo) + required: false + default: "" + type: string + cache_image_tag: + description: Tag of the image containing BuildKit inline cache metadata + required: false + default: "latest" + type: string + cosign: + description: Whether to sign images with Cosign + required: false + default: true + type: boolean + cosign_identity: + description: Certificate identity regexp for verifying cache images (defaults to current repo pattern) + required: false + default: "" + type: string + cosign_issuer: + description: Certificate OIDC issuer regexp for all cosign verification + required: false + default: "https://token.actions.githubusercontent.com" + type: string + verify_base: + description: Base image reference to verify with cosign before building + required: false + default: "" + type: string + cosign_base_identity: + description: Certificate identity regexp for verifying the base (FROM) image + required: false + default: "" + type: string + cosign_base_issuer: + description: Certificate OIDC issuer regexp for base image verification (defaults to cosign_issuer) + required: false + default: "" type: string jobs: + prepare: + name: Prepare build matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Build matrix from architectures + id: set-matrix + shell: bash + env: + ARCHITECTURES: ${{ inputs.architectures }} + MULTI_ARCH: ${{ inputs.multi_arch }} + run: | + arch_count=$(jq 'length' <<< "${ARCHITECTURES}") + if [[ "${MULTI_ARCH}" != "true" ]] && (( arch_count > 1 )); then + echo "::error::multi_arch is false but ${arch_count} architectures were specified; use multi_arch: true or pass a single architecture" + exit 1 + fi + + matrix=$(jq -c '{include: [.[] | + if . == "amd64" then {arch: "amd64", os: "ubuntu-24.04", platform: "linux/amd64"} + elif . == "aarch64" then {arch: "aarch64", os: "ubuntu-24.04-arm", platform: "linux/arm64"} + else empty end + ]}' <<< "${ARCHITECTURES}") + echo "matrix=${matrix}" >> "$GITHUB_OUTPUT" + build: - name: Build ${{ matrix.arch }} base image + name: Build ${{ matrix.arch }} image + needs: prepare runs-on: ${{ matrix.os }} permissions: contents: read @@ -43,60 +130,108 @@ jobs: packages: write strategy: fail-fast: false - matrix: - arch: ${{ fromJson(inputs.architectures) }} - include: - - os: ubuntu-24.04 - - arch: aarch64 - os: ubuntu-24.04-arm + matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }} steps: - name: Checkout the repository - uses: actions/checkout@v6.0.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Compute image name + id: image + shell: bash + env: + REGISTRY_PREFIX: ${{ inputs.registry_prefix }} + IMAGE_NAME: ${{ inputs.image_name }} + ARCH: ${{ matrix.arch }} + MULTI_ARCH: ${{ inputs.multi_arch }} + run: | + if [[ "${MULTI_ARCH}" == "true" ]]; then + image="${REGISTRY_PREFIX}/${ARCH}-${IMAGE_NAME}" + else + image="${REGISTRY_PREFIX}/${IMAGE_NAME}" + fi + echo "name=${image}" >> "$GITHUB_OUTPUT" + + - name: Build image + id: build + uses: ./.github/actions/build-image + with: + image: ${{ steps.image.outputs.name }} + image_tag: ${{ inputs.image_tag }} + image_extra_tags: ${{ inputs.image_extra_tags }} + arch: ${{ matrix.arch }} + platform: ${{ matrix.platform }} + push: ${{ inputs.push }} + cache_scope: ${{ inputs.cache_scope }} + cache_image_tag: ${{ inputs.cache_image_tag }} + docker_password: ${{ secrets.GITHUB_TOKEN }} + context: ${{ inputs.context }} + file: ${{ inputs.file }} + version: ${{ inputs.version }} + build_args: ${{ inputs.build_args }} + cosign: ${{ inputs.cosign }} + cosign_identity: ${{ inputs.cosign_identity }} + cosign_base_identity: ${{ inputs.cosign_base_identity }} + cosign_issuer: ${{ inputs.cosign_issuer }} + cosign_base_issuer: ${{ inputs.cosign_base_issuer }} + verify_base: ${{ inputs.verify_base }} + + manifest: + name: Publish multi-arch manifest + if: inputs.push && inputs.multi_arch + needs: [prepare, build] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + packages: write + steps: - name: Login to GitHub Container Registry - if: github.event_name == 'release' - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Set build arguments + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + + - name: Install Cosign + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + if: inputs.cosign + with: + cosign-release: "v2.5.3" + + - name: Create multi-arch manifest and sign it shell: bash + env: + ARCHITECTURES: ${{ inputs.architectures }} + REGISTRY_PREFIX: ${{ inputs.registry_prefix }} + IMAGE_NAME: ${{ inputs.image_name }} + IMAGE_TAG: ${{ inputs.image_tag }} + IMAGE_EXTRA_TAGS: ${{ inputs.image_extra_tags }} + COSIGN: ${{ inputs.cosign }} run: | - if [[ "${{ github.event_name }}" == "release" ]]; then - if [[ -n "${{ inputs.python }}" ]]; then - BUILD_ARGS="--additional-tag ${{ inputs.python }}-alpine${{ inputs.version }}-${{ github.event.release.tag_name }}" - else - BUILD_ARGS="--additional-tag ${{ inputs.version }}-${{ github.event.release.tag_name }}" - fi - if [[ "${{ inputs.tag_latest }}" != "${{ inputs.version }}" ]] \ - || [[ -n "${{ inputs.python }}" && "${{ inputs.python_latest }}" != "${{ inputs.python }}" ]]; - then - BUILD_ARGS="$BUILD_ARGS --no-latest" - fi - else - BUILD_ARGS="--test" - fi + source_images=() + for arch in $(jq -r '.[]' <<< "${ARCHITECTURES}"); do + source_images+=("${REGISTRY_PREFIX}/${arch}-${IMAGE_NAME}:${IMAGE_TAG}") + done - if [[ -n "${{ inputs.version_from }}" ]]; then - BUILD_ARGS="$BUILD_ARGS --version-from ${{ inputs.version_from }}" - fi - if [[ -n "${{ inputs.python }}" ]]; then - BUILD_ARGS="$BUILD_ARGS --version ${{ inputs.python }} --base ${{ inputs.python }}-alpine${{ inputs.version }}" - else - BUILD_ARGS="$BUILD_ARGS --base ${{ inputs.version }}" - fi + tags=("${REGISTRY_PREFIX}/${IMAGE_NAME}:${IMAGE_TAG}") + while IFS= read -r tag; do + [[ -n "$tag" ]] && tags+=("${REGISTRY_PREFIX}/${IMAGE_NAME}:${tag}") + done <<< "${IMAGE_EXTRA_TAGS}" - echo "BUILD_ARGS=$BUILD_ARGS" >> $GITHUB_ENV + tag_args=() + for tag in "${tags[@]}"; do + tag_args+=("--tag" "${tag}") + done - - name: Build base image - uses: home-assistant/builder@2026.02.1 - with: - image: ${{ matrix.arch }} - args: | - $BUILD_ARGS \ - --${{ matrix.arch }} \ - --target /data/${{ inputs.target }} \ - --cosign \ - --release ${{ inputs.release_name }} \ + docker buildx imagetools create "${tag_args[@]}" "${source_images[@]}" + + if [[ "${COSIGN}" == "true" ]]; then + # All tags for the manifest point to the same digest, get digest from the first tag + digest=$(skopeo inspect --raw --no-tags "docker://${tags[0]}" | skopeo manifest-digest /dev/stdin) + cosign sign --yes "${REGISTRY_PREFIX}/${IMAGE_NAME}@${digest}" + fi diff --git a/README.md b/README.md index b478b52e..91ff7e77 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,7 @@ We support version that are not EOL: https://alpinelinux.org/releases/ | Image | OS | Tags | latest | |-------|----|------|--------| -| aarch64-base | Alpine | 3.21, 3.22, 3.23 | 3.23 | -| amd64-base | Alpine | 3.21, 3.22, 3.23 | 3.23 | +| base | Alpine | 3.21, 3.22, 3.23 | 3.23 | ### jemalloc @@ -26,8 +25,7 @@ We support the latest 3 release with the latest 3 Alpine version. | Image | OS | Python versions | Tags | latest | |-------|----|-----------------|------|--------| -| aarch64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.21, 3.12-alpine3.22, 3.12-alpine3.23, 3.13-alpine3.21, 3.13-alpine3.22, 3.13-alpine3.23, 3.14-alpine3.21, 3.14-alpine3.22, 3.14-alpine3.23 | 3.14-alpine3.23 | -| amd64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.21, 3.12-alpine3.22, 3.12-alpine3.23, 3.13-alpine3.21, 3.13-alpine3.22, 3.13-alpine3.23, 3.14-alpine3.21, 3.14-alpine3.22, 3.14-alpine3.23 | 3.14-alpine3.23 | +| base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.21, 3.12-alpine3.22, 3.12-alpine3.23, 3.13-alpine3.21, 3.13-alpine3.22, 3.13-alpine3.23, 3.14-alpine3.21, 3.14-alpine3.22, 3.14-alpine3.23 | 3.14-alpine3.23 | ## Others @@ -37,8 +35,7 @@ We support the latest 3 release with the latest 3 Alpine version. | Image | OS | Tags | latest | |-------|----|------|--------| -| aarch64-base-debian | Debian | bookworm, trixie | trixie | -| amd64-base-debian | Debian | bookworm, trixie | trixie | +| base-debian | Debian | bookworm, trixie | trixie | ### Ubuntu images @@ -46,5 +43,4 @@ We support the latest 3 release with the latest 3 Alpine version. | Image | OS | Tags | latest | |-------|----|------|--------| -| aarch64-base-ubuntu | Ubuntu | 22.04, 24.04 | 24.04 | -| amd64-base-ubuntu | Ubuntu | 22.04, 24.04 | 24.04 | +| base-ubuntu | Ubuntu | 22.04, 24.04 | 24.04 | diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 33e784b0..84ff9871 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -1,7 +1,6 @@ -ARG BUILD_FROM -# amd64: alpine:${VERSION} -# aarch64: arm64v8/alpine:${VERSION} - +ARG BASE_IMAGE=alpine +ARG ALPINE_VERSION=3.23 +ARG BUILD_FROM=${BASE_IMAGE}:${ALPINE_VERSION} FROM ${BUILD_FROM} # Default ENV @@ -16,19 +15,29 @@ ENV \ # Set shell SHELL ["/bin/ash", "-o", "pipefail", "-c"] -# Build Args -ARG \ - BASHIO_VERSION \ - TEMPIO_VERSION \ - S6_OVERLAY_VERSION \ - JEMALLOC_VERSION - # Base system WORKDIR /usr/src -ARG BUILD_ARCH + +ARG TARGETARCH +ARG BASHIO_VERSION=0.17.5 +ARG TEMPIO_VERSION=2024.11.2 +ARG S6_OVERLAY_VERSION=3.1.6.2 +ARG JEMALLOC_VERSION=5.3.0 RUN \ set -x \ + && if [ -z "${TARGETARCH}" ]; then \ + case "$(uname -m)" in \ + x86_64) TARGETARCH="amd64" ;; \ + aarch64|arm64) TARGETARCH="arm64" ;; \ + *) echo "Unsupported architecture: $(uname -m)" && exit 1 ;; \ + esac; \ + fi \ + && case "${TARGETARCH}" in \ + amd64) TEMPIO_ARCH="amd64"; S6_ARCH="x86_64" ;; \ + arm64) TEMPIO_ARCH="aarch64"; S6_ARCH="aarch64" ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac \ && apk add --no-cache \ bash \ bind-tools \ @@ -44,12 +53,6 @@ RUN \ autoconf \ git \ \ - && if [ "${BUILD_ARCH}" = "amd64" ]; then \ - export S6_ARCH="x86_64"; \ - else \ - export S6_ARCH="${BUILD_ARCH}"; \ - fi \ - \ && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" \ | tar Jxvf - -C / \ && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" \ @@ -76,7 +79,7 @@ RUN \ && ln -s /usr/lib/bashio/bashio /usr/bin/bashio \ \ && curl -L -f -s -o /usr/bin/tempio \ - "https://github.com/home-assistant/tempio/releases/download/${TEMPIO_VERSION}/tempio_${BUILD_ARCH}" \ + "https://github.com/home-assistant/tempio/releases/download/${TEMPIO_VERSION}/tempio_${TEMPIO_ARCH}" \ && chmod a+x /usr/bin/tempio \ \ && apk del .build-deps \ @@ -88,3 +91,22 @@ COPY rootfs / # S6-Overlay WORKDIR / ENTRYPOINT ["/init"] + +ARG BUILD_ARCH=amd64 +ARG BUILD_DATE="1970-01-01 00:00:00+00:00" +ARG BUILD_FROM +ARG BUILD_NAME="alpine" +ARG BUILD_REPOSITORY +ARG BUILD_VERSION=0.0.0-local + +LABEL \ + io.hass.type="base" \ + io.hass.base.name="${BUILD_NAME}" \ + io.hass.base.version="${BUILD_VERSION}" \ + io.hass.base.arch="${BUILD_ARCH}" \ + io.hass.base.image="${BUILD_FROM}" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.version="${BUILD_VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.version="${BUILD_VERSION}" \ + org.opencontainers.image.source="${BUILD_REPOSITORY}" diff --git a/alpine/build.yaml b/alpine/build.yaml deleted file mode 100644 index 809c6536..00000000 --- a/alpine/build.yaml +++ /dev/null @@ -1,14 +0,0 @@ -image: ghcr.io/home-assistant/{arch}-base -build_from: - aarch64: "arm64v8/alpine:" - amd64: "alpine:" -cosign: - identity: https://github.com/home-assistant/docker-base/.* -args: - BASHIO_VERSION: 0.17.5 - TEMPIO_VERSION: 2024.11.2 - S6_OVERLAY_VERSION: 3.1.6.2 - JEMALLOC_VERSION: 5.3.0 -labels: - io.hass.base.name: alpine - org.opencontainers.image.source: https://github.com/home-assistant/docker-base diff --git a/debian/Dockerfile b/debian/Dockerfile index 2f94cbd1..e94ad8f0 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -1,7 +1,6 @@ -ARG BUILD_FROM -# amd64: debian:${VERSION}-slim -# aarch64: arm64v8/debian:${VERSION}-slim - +ARG BASE_IMAGE=debian +ARG DEBIAN_VERSION=trixie +ARG BUILD_FROM=${BASE_IMAGE}:${DEBIAN_VERSION}-slim FROM ${BUILD_FROM} # Default ENV @@ -17,18 +16,28 @@ ENV \ # Set shell SHELL ["/bin/bash", "-o", "pipefail", "-c"] -# Build Args -ARG \ - BASHIO_VERSION \ - TEMPIO_VERSION \ - S6_OVERLAY_VERSION - # Base system WORKDIR /usr/src -ARG BUILD_ARCH + +ARG TARGETARCH +ARG BASHIO_VERSION=0.17.5 +ARG TEMPIO_VERSION=2024.11.2 +ARG S6_OVERLAY_VERSION=3.1.6.2 RUN \ set -x \ + && if [ -z "${TARGETARCH}" ]; then \ + case "$(uname -m)" in \ + x86_64) TARGETARCH="amd64" ;; \ + aarch64|arm64) TARGETARCH="arm64" ;; \ + *) echo "Unsupported architecture: $(uname -m)" && exit 1 ;; \ + esac; \ + fi \ + && case "${TARGETARCH}" in \ + amd64) TEMPIO_ARCH="amd64"; S6_ARCH="x86_64" ;; \ + arm64) TEMPIO_ARCH="aarch64"; S6_ARCH="aarch64" ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac \ && apt-get update && apt-get install -y --no-install-recommends \ bash \ jq \ @@ -38,12 +47,6 @@ RUN \ xz-utils \ && mkdir -p /usr/share/man/man1 \ \ - && if [ "${BUILD_ARCH}" = "amd64" ]; then \ - export S6_ARCH="x86_64"; \ - else \ - export S6_ARCH="${BUILD_ARCH}"; \ - fi \ - \ && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" \ | tar Jxvf - -C / \ && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" \ @@ -56,7 +59,7 @@ RUN \ && mkdir -p /etc/services.d \ \ && curl -L -f -s -o /usr/bin/tempio \ - "https://github.com/home-assistant/tempio/releases/download/${TEMPIO_VERSION}/tempio_${BUILD_ARCH}" \ + "https://github.com/home-assistant/tempio/releases/download/${TEMPIO_VERSION}/tempio_${TEMPIO_ARCH}" \ && chmod a+x /usr/bin/tempio \ \ && mkdir -p /usr/src/bashio \ @@ -71,3 +74,22 @@ RUN \ # S6-Overlay WORKDIR / ENTRYPOINT ["/init"] + +ARG BUILD_ARCH=amd64 +ARG BUILD_DATE="1970-01-01 00:00:00+00:00" +ARG BUILD_FROM +ARG BUILD_NAME="debian" +ARG BUILD_REPOSITORY +ARG BUILD_VERSION=0.0.0-local + +LABEL \ + io.hass.type="base" \ + io.hass.base.name="${BUILD_NAME}" \ + io.hass.base.version="${BUILD_VERSION}" \ + io.hass.base.arch="${BUILD_ARCH}" \ + io.hass.base.image="${BUILD_FROM}" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.version="${BUILD_VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.version="${BUILD_VERSION}" \ + org.opencontainers.image.source="${BUILD_REPOSITORY}" diff --git a/debian/build.yaml b/debian/build.yaml deleted file mode 100644 index ffae78b5..00000000 --- a/debian/build.yaml +++ /dev/null @@ -1,13 +0,0 @@ -image: ghcr.io/home-assistant/{arch}-base-debian -build_from: - aarch64: "arm64v8/debian:" - amd64: "debian:" -cosign: - identity: https://github.com/home-assistant/docker-base/.* -args: - BASHIO_VERSION: 0.17.5 - TEMPIO_VERSION: 2024.11.2 - S6_OVERLAY_VERSION: 3.1.6.2 -labels: - io.hass.base.name: debian - org.opencontainers.image.source: https://github.com/home-assistant/docker-base diff --git a/python/3.12/Dockerfile b/python/3.12/Dockerfile index 9c646c27..1e98e796 100644 --- a/python/3.12/Dockerfile +++ b/python/3.12/Dockerfile @@ -1,10 +1,15 @@ +ARG BASE_IMAGE=ghcr.io/home-assistant/base +ARG BASE_VERSION=3.23 +ARG BUILD_FROM=${BASE_IMAGE}:${BASE_VERSION} +FROM ${BUILD_FROM} +# Redeclare for usage in base image labels +ARG BASE_IMAGE +ARG BASE_VERSION ARG BUILD_FROM -FROM $BUILD_FROM -ARG \ - PYTHON_VERSION \ - CERT_IDENTITY \ - CERT_OIDC_ISSUER +ARG PYTHON_VERSION=3.12.12 +ARG CERT_IDENTITY=thomas@python.org +ARG CERT_OIDC_ISSUER=https://accounts.google.com # ensure local python is preferred over distribution python ENV PATH=/usr/local/bin:$PATH @@ -108,9 +113,28 @@ RUN \ && ln -s python3 python \ && ln -s python3-config python-config -ARG PIP_VERSION +ARG PIP_VERSION=25.3 RUN set -ex; \ python -m ensurepip --upgrade --default-pip; \ pip3 install --no-cache-dir --upgrade pip=="${PIP_VERSION}"; \ pip --version + +ARG BUILD_ARCH=amd64 +ARG BUILD_DATE="1970-01-01 00:00:00+00:00" +ARG BUILD_FROM +ARG BUILD_NAME="python" +ARG BUILD_REPOSITORY +ARG BUILD_VERSION=0.0.0-local + +LABEL \ + io.hass.type="base" \ + io.hass.base.name="python" \ + io.hass.base.version="${BUILD_VERSION}" \ + io.hass.base.arch="${BUILD_ARCH}" \ + io.hass.base.image="${BUILD_FROM}" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.version="${BUILD_VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.version="${BUILD_VERSION}" \ + org.opencontainers.image.source="${BUILD_REPOSITORY}" diff --git a/python/3.12/build.yaml b/python/3.12/build.yaml deleted file mode 100644 index 955e94cd..00000000 --- a/python/3.12/build.yaml +++ /dev/null @@ -1,15 +0,0 @@ -image: ghcr.io/home-assistant/{arch}-base-python -build_from: - aarch64: "ghcr.io/home-assistant/aarch64-base:" - amd64: "ghcr.io/home-assistant/amd64-base:" -cosign: - base_identity: https://github.com/home-assistant/docker-base/.* - identity: https://github.com/home-assistant/docker-base/.* -args: - PYTHON_VERSION: "3.12.12" - PIP_VERSION: "25.3" - CERT_IDENTITY: thomas@python.org - CERT_OIDC_ISSUER: https://accounts.google.com -labels: - io.hass.base.name: python - org.opencontainers.image.source: https://github.com/home-assistant/docker-base diff --git a/python/3.13/Dockerfile b/python/3.13/Dockerfile index 9c646c27..f968159b 100644 --- a/python/3.13/Dockerfile +++ b/python/3.13/Dockerfile @@ -1,10 +1,11 @@ -ARG BUILD_FROM -FROM $BUILD_FROM +ARG BASE_IMAGE=ghcr.io/home-assistant/base +ARG BASE_VERSION=3.23 +ARG BUILD_FROM=${BASE_IMAGE}:${BASE_VERSION} +FROM ${BUILD_FROM} -ARG \ - PYTHON_VERSION \ - CERT_IDENTITY \ - CERT_OIDC_ISSUER +ARG PYTHON_VERSION=3.13.12 +ARG CERT_IDENTITY=thomas@python.org +ARG CERT_OIDC_ISSUER=https://accounts.google.com # ensure local python is preferred over distribution python ENV PATH=/usr/local/bin:$PATH @@ -108,9 +109,28 @@ RUN \ && ln -s python3 python \ && ln -s python3-config python-config -ARG PIP_VERSION +ARG PIP_VERSION=25.3 RUN set -ex; \ python -m ensurepip --upgrade --default-pip; \ pip3 install --no-cache-dir --upgrade pip=="${PIP_VERSION}"; \ pip --version + +ARG BUILD_ARCH=amd64 +ARG BUILD_DATE="1970-01-01 00:00:00+00:00" +ARG BUILD_FROM +ARG BUILD_NAME="python" +ARG BUILD_REPOSITORY +ARG BUILD_VERSION=0.0.0-local + +LABEL \ + io.hass.type="base" \ + io.hass.base.name="python" \ + io.hass.base.version="${BUILD_VERSION}" \ + io.hass.base.arch="${BUILD_ARCH}" \ + io.hass.base.image="${BUILD_FROM}" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.version="${BUILD_VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.version="${BUILD_VERSION}" \ + org.opencontainers.image.source="${BUILD_REPOSITORY}" diff --git a/python/3.13/build.yaml b/python/3.13/build.yaml deleted file mode 100644 index fc1ad81e..00000000 --- a/python/3.13/build.yaml +++ /dev/null @@ -1,15 +0,0 @@ -image: ghcr.io/home-assistant/{arch}-base-python -build_from: - aarch64: "ghcr.io/home-assistant/aarch64-base:" - amd64: "ghcr.io/home-assistant/amd64-base:" -cosign: - base_identity: https://github.com/home-assistant/docker-base/.* - identity: https://github.com/home-assistant/docker-base/.* -args: - PYTHON_VERSION: "3.13.12" - PIP_VERSION: "25.3" - CERT_IDENTITY: thomas@python.org - CERT_OIDC_ISSUER: https://accounts.google.com -labels: - io.hass.base.name: python - org.opencontainers.image.source: https://github.com/home-assistant/docker-base diff --git a/python/3.14/Dockerfile b/python/3.14/Dockerfile index 3476f11b..9cac69ee 100644 --- a/python/3.14/Dockerfile +++ b/python/3.14/Dockerfile @@ -1,10 +1,11 @@ -ARG BUILD_FROM -FROM $BUILD_FROM +ARG BASE_IMAGE=ghcr.io/home-assistant/base +ARG BASE_VERSION=3.23 +ARG BUILD_FROM=${BASE_IMAGE}:${BASE_VERSION} +FROM ${BUILD_FROM} -ARG \ - PYTHON_VERSION \ - CERT_IDENTITY \ - CERT_OIDC_ISSUER +ARG PYTHON_VERSION=3.14.3 +ARG CERT_IDENTITY=hugo@python.org +ARG CERT_OIDC_ISSUER=https://github.com/login/oauth # ensure local python is preferred over distribution python ENV PATH=/usr/local/bin:$PATH @@ -109,9 +110,30 @@ RUN \ && ln -s python3 python \ && ln -s python3-config python-config -ARG PIP_VERSION +ARG PIP_VERSION=25.3 RUN set -ex; \ python -m ensurepip --upgrade --default-pip; \ pip3 install --no-cache-dir --upgrade pip=="${PIP_VERSION}"; \ pip --version + +ARG BASE_IMAGE=ghcr.io/home-assistant/base +ARG BASE_VERSION=3.23 +ARG BUILD_ARCH=amd64 +ARG BUILD_DATE="1970-01-01 00:00:00+00:00" +ARG BUILD_FROM +ARG BUILD_NAME="python" +ARG BUILD_REPOSITORY +ARG BUILD_VERSION=0.0.0-local + +LABEL \ + io.hass.type="base" \ + io.hass.base.name="python" \ + io.hass.base.version="${BUILD_VERSION}" \ + io.hass.base.arch="${BUILD_ARCH}" \ + io.hass.base.image="${BUILD_FROM}" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.version="${BUILD_VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.version="${BUILD_VERSION}" \ + org.opencontainers.image.source="${BUILD_REPOSITORY}" diff --git a/python/3.14/build.yaml b/python/3.14/build.yaml deleted file mode 100644 index e1f55542..00000000 --- a/python/3.14/build.yaml +++ /dev/null @@ -1,15 +0,0 @@ -image: ghcr.io/home-assistant/{arch}-base-python -build_from: - aarch64: "ghcr.io/home-assistant/aarch64-base:" - amd64: "ghcr.io/home-assistant/amd64-base:" -cosign: - base_identity: https://github.com/home-assistant/docker-base/.* - identity: https://github.com/home-assistant/docker-base/.* -args: - PYTHON_VERSION: "3.14.3" - PIP_VERSION: "25.3" - CERT_IDENTITY: hugo@python.org - CERT_OIDC_ISSUER: https://github.com/login/oauth -labels: - io.hass.base.name: python - org.opencontainers.image.source: https://github.com/home-assistant/docker-base diff --git a/ubuntu/Dockerfile b/ubuntu/Dockerfile index f97fb4d3..e881448d 100644 --- a/ubuntu/Dockerfile +++ b/ubuntu/Dockerfile @@ -1,7 +1,6 @@ -ARG BUILD_FROM -# amd64: ubuntu:${VERSION} -# aarch64: arm64v8/ubuntu:${VERSION} - +ARG BASE_IMAGE=ubuntu +ARG UBUNTU_VERSION=24.04 +ARG BUILD_FROM=${BASE_IMAGE}:${UBUNTU_VERSION} FROM ${BUILD_FROM} # Default ENV @@ -16,18 +15,28 @@ ENV \ # Set shell SHELL ["/bin/bash", "-o", "pipefail", "-c"] -# Version -ARG \ - BASHIO_VERSION \ - TEMPIO_VERSION \ - S6_OVERLAY_VERSION - # Base system WORKDIR /usr/src -ARG BUILD_ARCH + +ARG TARGETARCH +ARG BASHIO_VERSION=0.17.5 +ARG TEMPIO_VERSION=2024.11.2 +ARG S6_OVERLAY_VERSION=3.1.6.2 RUN \ set -x \ + && if [ -z "${TARGETARCH}" ]; then \ + case "$(uname -m)" in \ + x86_64) TARGETARCH="amd64" ;; \ + aarch64|arm64) TARGETARCH="arm64" ;; \ + *) echo "Unsupported architecture: $(uname -m)" && exit 1 ;; \ + esac; \ + fi \ + && case "${TARGETARCH}" in \ + amd64) TEMPIO_ARCH="amd64"; S6_ARCH="x86_64" ;; \ + arm64) TEMPIO_ARCH="aarch64"; S6_ARCH="aarch64" ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac \ && apt-get update && apt-get install -y --no-install-recommends \ bash \ jq \ @@ -36,12 +45,6 @@ RUN \ ca-certificates \ xz-utils \ \ - && if [ "${BUILD_ARCH}" = "amd64" ]; then \ - export S6_ARCH="x86_64"; \ - else \ - export S6_ARCH="${BUILD_ARCH}"; \ - fi \ - \ && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" \ | tar Jxvf - -C / \ && curl -L -f -s "https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz" \ @@ -54,7 +57,7 @@ RUN \ && mkdir -p /etc/services.d \ \ && curl -L -f -s -o /usr/bin/tempio \ - "https://github.com/home-assistant/tempio/releases/download/${TEMPIO_VERSION}/tempio_${BUILD_ARCH}" \ + "https://github.com/home-assistant/tempio/releases/download/${TEMPIO_VERSION}/tempio_${TEMPIO_ARCH}" \ && chmod a+x /usr/bin/tempio \ \ && mkdir -p /usr/src/bashio \ @@ -69,3 +72,22 @@ RUN \ # S6-Overlay WORKDIR / ENTRYPOINT ["/init"] + +ARG BUILD_ARCH=amd64 +ARG BUILD_DATE="1970-01-01 00:00:00+00:00" +ARG BUILD_FROM +ARG BUILD_NAME="ubuntu" +ARG BUILD_REPOSITORY +ARG BUILD_VERSION=0.0.0-local + +LABEL \ + io.hass.type="base" \ + io.hass.base.name="${BUILD_NAME}" \ + io.hass.base.version="${BUILD_VERSION}" \ + io.hass.base.arch="${BUILD_ARCH}" \ + io.hass.base.image="${BUILD_FROM}" \ + io.hass.arch="${BUILD_ARCH}" \ + io.hass.version="${BUILD_VERSION}" \ + org.opencontainers.image.created="${BUILD_DATE}" \ + org.opencontainers.image.version="${BUILD_VERSION}" \ + org.opencontainers.image.source="${BUILD_REPOSITORY}" diff --git a/ubuntu/build.yaml b/ubuntu/build.yaml deleted file mode 100644 index 558c92e4..00000000 --- a/ubuntu/build.yaml +++ /dev/null @@ -1,13 +0,0 @@ -image: ghcr.io/home-assistant/{arch}-base-ubuntu -build_from: - aarch64: "arm64v8/ubuntu:" - amd64: "ubuntu:" -cosign: - identity: https://github.com/home-assistant/docker-base/.* -args: - BASHIO_VERSION: 0.17.5 - TEMPIO_VERSION: 2024.11.2 - S6_OVERLAY_VERSION: 3.1.6.2 -labels: - io.hass.base.name: ubuntu - org.opencontainers.image.source: https://github.com/home-assistant/docker-base From a79a9a96a371278790e37dc943456cfb4073ac8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 3 Mar 2026 18:54:44 +0100 Subject: [PATCH 02/23] Add readme entry with instructions for local builds --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index 91ff7e77..43d05ca1 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,50 @@ We support the latest 3 release with the latest 3 Alpine version. | Image | OS | Tags | latest | |-------|----|------|--------| | base-ubuntu | Ubuntu | 22.04, 24.04 | 24.04 | + +## Building images locally + +Docker BuildKit (`docker buildx`) can be used for building the images locally without any extra tooling. Following are examples of building the images for a single (host) architecture. + +Alpine base using the default version from the Dockerfile: + +```bash +docker buildx build -t base alpine/ +``` + +To use a specific Alpine base version: + +```bash +docker buildx build \ + --build-arg ALPINE_VERSION=3.21 \ + -t base:3.21 \ + alpine/ +``` + +Debian base: + +```bash +docker buildx build \ + --build-arg DEBIAN_VERSION=trixie + -t debian:trixie \ + debian/ +``` + +Ubuntu base: + +```bash +docker buildx build \ + --build-arg UBUNTU_VERSION=24.04 \ + -t ubuntu:24.04 \ + ubuntu/ +``` + +Python 3.14 image, using the official Alpine 3.23 base image from GHCR: + +```bash +docker buildx build \ + --build-arg BASE_IMAGE=ghcr.io/home-assistant/base \ + --build-arg BASE_VERSION=3.23 \ + -t base-python:3.14-alpine3.23 \ + python/3.14/ +``` From fb9b3d932a417a3c058e69311f5b6f4b48745bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 4 Mar 2026 09:45:37 +0100 Subject: [PATCH 03/23] Extract workflow and action to the builder repo --- .github/actions/build-image/action.yml | 245 ----------------------- .github/actions/cosign-verify/action.yml | 61 ------ .github/workflows/builder.yml | 8 +- .github/workflows/reuseable-builder.yml | 237 ---------------------- 4 files changed, 4 insertions(+), 547 deletions(-) delete mode 100644 .github/actions/build-image/action.yml delete mode 100644 .github/actions/cosign-verify/action.yml delete mode 100644 .github/workflows/reuseable-builder.yml diff --git a/.github/actions/build-image/action.yml b/.github/actions/build-image/action.yml deleted file mode 100644 index 37424c78..00000000 --- a/.github/actions/build-image/action.yml +++ /dev/null @@ -1,245 +0,0 @@ -name: Build image -description: Build, push, and optionally sign a single-arch container image - -inputs: - arch: - description: Architecture to build (e.g., "amd64") - required: true - platform: - description: Platform string (e.g., "linux/amd64") - required: true - image: - description: Full image name without tag (e.g., "ghcr.io/home-assistant/amd64-base") - required: true - image_tag: - description: Main image tag (e.g., "3.23") - required: true - image_extra_tags: - description: Additional tags, one per line - required: false - default: "" - context: - description: Build context (usually the directory with Dockerfile) - required: true - file: - description: Dockerfile path (defaults to "Dockerfile" in the context directory) - required: false - default: "" - version: - description: Image version label - required: true - build_args: - description: Additional build arguments (key=value format, one per line) - required: false - default: "" - push: - description: Whether to push images to registry - required: false - default: "false" - cache_scope: - description: Scope for build cache sharing (defaults to architecture, set if building multiple images from a single repo) - required: false - default: "" - cache_image_tag: - description: Tag of the image containing BuildKit inline cache metadata - required: false - default: "latest" - docker_registry: - description: Docker registry (e.g., "ghcr.io") - required: false - default: "ghcr.io" - docker_username: - description: Username for Docker registry (defaults to repository owner) - required: false - default: ${{ github.repository_owner }} - docker_password: - description: Password for Docker registry (use secrets.GITHUB_TOKEN for GHCR) - required: true - cosign: - description: Whether to sign images with Cosign - required: false - default: "true" - cosign_identity: - description: Certificate identity regexp for verifying cache images (defaults to current repo pattern) - required: false - default: "" - cosign_issuer: - description: Certificate OIDC issuer regexp for all cosign verification - required: false - default: "https://token.actions.githubusercontent.com" - verify_base: - description: Base image reference to verify with cosign before building - required: false - default: "" - cosign_base_identity: - description: Certificate identity regexp for verifying the base (FROM) image - required: false - default: "" - cosign_base_issuer: - description: Certificate OIDC issuer regexp for base image verification (defaults to cosign_issuer) - required: false - default: "" - -outputs: - digest: - description: Image digest from the build - value: ${{ steps.build.outputs.digest }} - -runs: - using: composite - steps: - - name: Login to Docker Registry - if: inputs.push == 'true' - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 - with: - registry: ${{ inputs.docker_registry }} - username: ${{ inputs.docker_username }} - password: ${{ inputs.docker_password }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - - - name: Install Cosign - if: inputs.cosign == 'true' || inputs.verify_base != '' - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - with: - cosign-release: "v2.5.3" - - - name: Verify cache image ${{ inputs.image }}:${{ inputs.cache_image_tag }} - if: inputs.cosign == 'true' - id: verify_cache - uses: ./.github/actions/cosign-verify - with: - image: ${{ inputs.image }}:${{ inputs.cache_image_tag }} - cosign_identity: ${{ inputs.cosign_identity || env.DEFAULT_COSIGN_IDENTITY }} - cosign_issuer: ${{ inputs.cosign_issuer }} - allow_failure: true - env: - DEFAULT_COSIGN_IDENTITY: https://github.com/${{ github.repository }}/.* - - - name: Verify base image - if: inputs.push == 'true' && inputs.verify_base != '' && inputs.cosign_base_identity != '' - uses: ./.github/actions/cosign-verify - with: - image: ${{ inputs.verify_base }} - cosign_identity: ${{ inputs.cosign_base_identity }} - cosign_issuer: ${{ inputs.cosign_base_issuer || inputs.cosign_issuer }} - allow_failure: false - - - name: Set build options - id: options - shell: bash - env: - IMAGE: ${{ inputs.image }} - IMAGE_TAG: ${{ inputs.image_tag }} - IMAGE_EXTRA_TAGS: ${{ inputs.image_extra_tags }} - PUSH: ${{ inputs.push }} - VERSION: ${{ inputs.version }} - ARCH: ${{ inputs.arch }} - GITHUB_REPOSITORY: ${{ github.repository }} - BUILD_ARGS_INPUT: ${{ inputs.build_args }} - COSIGN_ENABLED: ${{ inputs.cosign }} - CACHE_SCOPE: ${{ inputs.cache_scope }} - CACHE_VERIFIED: ${{ steps.verify_cache.outputs.verified }} - FILE_INPUT: ${{ inputs.file }} - CONTEXT: ${{ inputs.context }} - run: | - tags=() - tags+=("${IMAGE}:${IMAGE_TAG}") - while IFS= read -r tag; do - [[ -n "$tag" ]] && tags+=("${IMAGE}:${tag}") - done <<< "${IMAGE_EXTRA_TAGS}" - - { - echo "tags<> "$GITHUB_OUTPUT" - - build_date="$(date --rfc-3339=seconds --utc)" - - build_args=() - build_args+=("BUILD_VERSION=${VERSION}") - build_args+=("BUILD_ARCH=${ARCH}") - build_args+=("BUILD_DATE=${build_date}") - build_args+=("BUILD_REPOSITORY=https://github.com/${GITHUB_REPOSITORY}") - while IFS= read -r line; do - [[ -n "$line" ]] && build_args+=("$line") - done <<< "${BUILD_ARGS_INPUT}" - { - echo "build_args<> "$GITHUB_OUTPUT" - - if [[ "${PUSH}" == "true" ]]; then - echo output="type=image,push=true,compression=zstd,compression-level=9,force-compression=true,oci-mediatypes=true" >> "$GITHUB_OUTPUT" - else - echo output="type=docker" >> "$GITHUB_OUTPUT" - fi - - if [[ -z "${CACHE_SCOPE}" ]]; then - cache_scope="${ARCH}" - else - cache_scope="${ARCH}-${CACHE_SCOPE}" - fi - echo cache_scope="${cache_scope}" >> "$GITHUB_OUTPUT" - - cache_from=("type=gha,scope=${cache_scope}") - if [[ "${COSIGN_ENABLED}" != "true" ]] || [[ "${CACHE_VERIFIED}" == "true" ]]; then - cache_from+=("type=registry,ref=${IMAGE}:${IMAGE_TAG}") - fi - { - echo "cache_from<> "$GITHUB_OUTPUT" - - if [ -z "${FILE_INPUT}" ]; then - echo file="${CONTEXT}/Dockerfile" >> "$GITHUB_OUTPUT" - else - echo file="${FILE_INPUT}" >> "$GITHUB_OUTPUT" - fi - - - name: Build image - id: build - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 - with: - context: ${{ env.CONTEXT }} # zizmor: ignore[template-injection] - file: ${{ steps.options.outputs.file }} - platforms: ${{ inputs.platform }} - pull: true - push: ${{ inputs.push == 'true' }} - load: ${{ inputs.push != 'true' }} - build-args: ${{ steps.options.outputs.build_args }} - tags: ${{ steps.options.outputs.tags }} - outputs: ${{ steps.options.outputs.output }} - cache-to: | - type=inline - type=gha,mode=max,scope=${{ steps.options.outputs.cache_scope }} - cache-from: ${{ steps.options.outputs.cache_from }} - - env: - CONTEXT: ${{ inputs.context }} - - - name: Sign per-arch image - if: inputs.push == 'true' && inputs.cosign == 'true' - shell: bash - env: - IMAGE_REF: ${{ inputs.image }}@${{ steps.build.outputs.digest }} - run: | - echo "::group::Signing image: ${IMAGE_REF}" - - for i in {1..5}; do - if cosign sign --yes "${IMAGE_REF}"; then - echo "Signed: ${IMAGE_REF}" - exit 0 - fi - echo "Signing attempt ${i} failed, retrying..." - sleep $((2 ** i)) - done - - echo "::endgroup::" - - echo "::error::Failed to sign image ${IMAGE_REF}" - exit 1 diff --git a/.github/actions/cosign-verify/action.yml b/.github/actions/cosign-verify/action.yml deleted file mode 100644 index 9acf292e..00000000 --- a/.github/actions/cosign-verify/action.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Verify a signature on the supplied container image -description: | - Verify Cosign signature of a container image. - Requires Cosign to be installed in the runner environment. - -inputs: - image: - description: Container image reference to verify - cosign_identity: - description: Certificate identity regexp for verifying cache images (defaults to current repo pattern) - required: false - default: "" - cosign_issuer: - description: Certificate OIDC issuer regexp for all cosign verification - allow_failure: - description: Whether to allow failure of this step (defaults to false). Only shows a warning if verification fails. - required: false - default: "false" - -outputs: - verified: - description: Whether the image was successfully verified with Cosign - value: ${{ steps.verify.outputs.verified }} - -runs: - using: composite - steps: - - name: Verify the image - id: verify - shell: bash - env: - IMAGE_REF: ${{ inputs.image }} - COSIGN_IDENTITY: ${{ inputs.cosign_identity }} - COSIGN_ISSUER: ${{ inputs.cosign_issuer }} - ALLOW_FAILURE: ${{ inputs.allow_failure }} - run: | - echo "::group::Verifying image: ${IMAGE_REF}" - - for i in {1..5}; do - if cosign verify \ - --certificate-identity-regexp "${COSIGN_IDENTITY}" \ - --certificate-oidc-issuer-regexp "${COSIGN_ISSUER}" \ - "${IMAGE_REF}"; then - echo "Image verified: ${IMAGE_REF}" - echo "verified=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - echo "Verification attempt ${i} failed, retrying..." - sleep $((2 ** i)) - done - - echo "::endgroup::" - - echo "verified=false" >> "$GITHUB_OUTPUT" - - if [[ "${ALLOW_FAILURE}" == "true" ]]; then - echo "::warning::Image verification failed for ${IMAGE_REF}, ignoring" - else - echo "::error::Image verification failed for ${IMAGE_REF}" - exit 1 - fi diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 1d526214..cee66d54 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -73,7 +73,7 @@ jobs: fail-fast: false matrix: alpine_version: ["3.21", "3.22", "3.23"] - uses: ./.github/workflows/reuseable-builder.yml + uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} registry_prefix: ${{ needs.init.outputs.registry_prefix }} @@ -97,7 +97,7 @@ jobs: fail-fast: false matrix: debian_version: ["bookworm", "trixie"] - uses: ./.github/workflows/reuseable-builder.yml + uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} registry_prefix: ${{ needs.init.outputs.registry_prefix }} @@ -121,7 +121,7 @@ jobs: fail-fast: false matrix: ubuntu_version: ["22.04", "24.04"] - uses: ./.github/workflows/reuseable-builder.yml + uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} registry_prefix: ${{ needs.init.outputs.registry_prefix }} @@ -146,7 +146,7 @@ jobs: matrix: alpine_version: ["3.21", "3.22", "3.23"] python_version: ["3.12", "3.13", "3.14"] - uses: ./.github/workflows/reuseable-builder.yml + uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} registry_prefix: ${{ needs.init.outputs.registry_prefix }} diff --git a/.github/workflows/reuseable-builder.yml b/.github/workflows/reuseable-builder.yml deleted file mode 100644 index e5e0fe6a..00000000 --- a/.github/workflows/reuseable-builder.yml +++ /dev/null @@ -1,237 +0,0 @@ -name: Reusable builder - -on: - workflow_call: - inputs: - architectures: - description: Architectures to build (JSON array, e.g., '["amd64", "aarch64"]') - required: true - type: string - multi_arch: - description: Prefix per-arch image names with architecture (required for multiple architectures) - required: false - default: true - type: boolean - registry_prefix: - description: Registry and namespace prefix (e.g., "ghcr.io/owner") - required: true - type: string - image_name: - description: Image name without a tag (e.g., "base-python") - required: true - type: string - image_tag: - description: Base image tag (e.g., "3.23") - required: true - type: string - image_extra_tags: - description: Additional tags, one per line - required: false - default: "" - type: string - context: - description: Build context (usually the directory with Dockerfile) - required: true - type: string - file: - description: Dockerfile path (defaults to "Dockerfile" in the context directory) - required: false - default: "" - type: string - version: - description: Image version label - required: true - type: string - build_args: - description: Additional build arguments (key=value format, one per line) - required: false - default: "" - type: string - push: - description: Whether to push images to registry - required: false - default: false - type: boolean - cache_scope: - description: Scope for build cache sharing (defaults to architecture, set if building multiple images from a single repo) - required: false - default: "" - type: string - cache_image_tag: - description: Tag of the image containing BuildKit inline cache metadata - required: false - default: "latest" - type: string - cosign: - description: Whether to sign images with Cosign - required: false - default: true - type: boolean - cosign_identity: - description: Certificate identity regexp for verifying cache images (defaults to current repo pattern) - required: false - default: "" - type: string - cosign_issuer: - description: Certificate OIDC issuer regexp for all cosign verification - required: false - default: "https://token.actions.githubusercontent.com" - type: string - verify_base: - description: Base image reference to verify with cosign before building - required: false - default: "" - type: string - cosign_base_identity: - description: Certificate identity regexp for verifying the base (FROM) image - required: false - default: "" - type: string - cosign_base_issuer: - description: Certificate OIDC issuer regexp for base image verification (defaults to cosign_issuer) - required: false - default: "" - type: string - -jobs: - prepare: - name: Prepare build matrix - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - steps: - - name: Build matrix from architectures - id: set-matrix - shell: bash - env: - ARCHITECTURES: ${{ inputs.architectures }} - MULTI_ARCH: ${{ inputs.multi_arch }} - run: | - arch_count=$(jq 'length' <<< "${ARCHITECTURES}") - if [[ "${MULTI_ARCH}" != "true" ]] && (( arch_count > 1 )); then - echo "::error::multi_arch is false but ${arch_count} architectures were specified; use multi_arch: true or pass a single architecture" - exit 1 - fi - - matrix=$(jq -c '{include: [.[] | - if . == "amd64" then {arch: "amd64", os: "ubuntu-24.04", platform: "linux/amd64"} - elif . == "aarch64" then {arch: "aarch64", os: "ubuntu-24.04-arm", platform: "linux/arm64"} - else empty end - ]}' <<< "${ARCHITECTURES}") - echo "matrix=${matrix}" >> "$GITHUB_OUTPUT" - - build: - name: Build ${{ matrix.arch }} image - needs: prepare - runs-on: ${{ matrix.os }} - permissions: - contents: read - id-token: write - packages: write - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }} - steps: - - name: Checkout the repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Compute image name - id: image - shell: bash - env: - REGISTRY_PREFIX: ${{ inputs.registry_prefix }} - IMAGE_NAME: ${{ inputs.image_name }} - ARCH: ${{ matrix.arch }} - MULTI_ARCH: ${{ inputs.multi_arch }} - run: | - if [[ "${MULTI_ARCH}" == "true" ]]; then - image="${REGISTRY_PREFIX}/${ARCH}-${IMAGE_NAME}" - else - image="${REGISTRY_PREFIX}/${IMAGE_NAME}" - fi - echo "name=${image}" >> "$GITHUB_OUTPUT" - - - name: Build image - id: build - uses: ./.github/actions/build-image - with: - image: ${{ steps.image.outputs.name }} - image_tag: ${{ inputs.image_tag }} - image_extra_tags: ${{ inputs.image_extra_tags }} - arch: ${{ matrix.arch }} - platform: ${{ matrix.platform }} - push: ${{ inputs.push }} - cache_scope: ${{ inputs.cache_scope }} - cache_image_tag: ${{ inputs.cache_image_tag }} - docker_password: ${{ secrets.GITHUB_TOKEN }} - context: ${{ inputs.context }} - file: ${{ inputs.file }} - version: ${{ inputs.version }} - build_args: ${{ inputs.build_args }} - cosign: ${{ inputs.cosign }} - cosign_identity: ${{ inputs.cosign_identity }} - cosign_base_identity: ${{ inputs.cosign_base_identity }} - cosign_issuer: ${{ inputs.cosign_issuer }} - cosign_base_issuer: ${{ inputs.cosign_base_issuer }} - verify_base: ${{ inputs.verify_base }} - - manifest: - name: Publish multi-arch manifest - if: inputs.push && inputs.multi_arch - needs: [prepare, build] - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - packages: write - steps: - - name: Login to GitHub Container Registry - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - - - name: Install Cosign - uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - if: inputs.cosign - with: - cosign-release: "v2.5.3" - - - name: Create multi-arch manifest and sign it - shell: bash - env: - ARCHITECTURES: ${{ inputs.architectures }} - REGISTRY_PREFIX: ${{ inputs.registry_prefix }} - IMAGE_NAME: ${{ inputs.image_name }} - IMAGE_TAG: ${{ inputs.image_tag }} - IMAGE_EXTRA_TAGS: ${{ inputs.image_extra_tags }} - COSIGN: ${{ inputs.cosign }} - run: | - source_images=() - for arch in $(jq -r '.[]' <<< "${ARCHITECTURES}"); do - source_images+=("${REGISTRY_PREFIX}/${arch}-${IMAGE_NAME}:${IMAGE_TAG}") - done - - tags=("${REGISTRY_PREFIX}/${IMAGE_NAME}:${IMAGE_TAG}") - while IFS= read -r tag; do - [[ -n "$tag" ]] && tags+=("${REGISTRY_PREFIX}/${IMAGE_NAME}:${tag}") - done <<< "${IMAGE_EXTRA_TAGS}" - - tag_args=() - for tag in "${tags[@]}"; do - tag_args+=("--tag" "${tag}") - done - - docker buildx imagetools create "${tag_args[@]}" "${source_images[@]}" - - if [[ "${COSIGN}" == "true" ]]; then - # All tags for the manifest point to the same digest, get digest from the first tag - digest=$(skopeo inspect --raw --no-tags "docker://${tags[0]}" | skopeo manifest-digest /dev/stdin) - cosign sign --yes "${REGISTRY_PREFIX}/${IMAGE_NAME}@${digest}" - fi From 9d5fd38dbfeac1f6ddd81eb8b19c6dbdaadf4136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 4 Mar 2026 09:59:31 +0100 Subject: [PATCH 04/23] Change underscores to dashes in workflow inputs --- .github/workflows/builder.yml | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index cee66d54..cf7473be 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -76,19 +76,19 @@ jobs: uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} - registry_prefix: ${{ needs.init.outputs.registry_prefix }} - image_name: base - image_tag: ${{ matrix.alpine_version }} - image_extra_tags: | + registry-prefix: ${{ needs.init.outputs.registry_prefix }} + image-name: base + image-tag: ${{ matrix.alpine_version }} + image-extra-tags: | ${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && 'latest' || '' }} context: alpine version: ${{ needs.init.outputs.version }} - build_args: | + build-args: | ALPINE_VERSION=${{ matrix.alpine_version }} push: ${{ needs.init.outputs.push == 'true' }} - cache_scope: alpine-${{ matrix.alpine_version }} - cache_image_tag: ${{ matrix.alpine_version }} + cache-scope: alpine-${{ matrix.alpine_version }} + cache-image-tag: ${{ matrix.alpine_version }} build_debian: name: Debian ${{ matrix.debian_version }} @@ -100,19 +100,19 @@ jobs: uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} - registry_prefix: ${{ needs.init.outputs.registry_prefix }} - image_name: debian - image_tag: ${{ matrix.debian_version }} - image_extra_tags: | + registry-prefix: ${{ needs.init.outputs.registry_prefix }} + image-name: debian + image-tag: ${{ matrix.debian_version }} + image-extra-tags: | ${{ matrix.debian_version }}-${{ needs.init.outputs.version }} ${{ matrix.debian_version == needs.init.outputs.debian_latest && 'latest' || '' }} context: debian version: ${{ needs.init.outputs.version }} - build_args: | + build-args: | DEBIAN_VERSION=${{ matrix.debian_version }} push: ${{ needs.init.outputs.push == 'true' }} - cache_scope: debian-${{ matrix.debian_version }} - cache_image_tag: ${{ matrix.debian_version }} + cache-scope: debian-${{ matrix.debian_version }} + cache-image-tag: ${{ matrix.debian_version }} build_ubuntu: name: Ubuntu ${{ matrix.ubuntu_version }} @@ -124,19 +124,19 @@ jobs: uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} - registry_prefix: ${{ needs.init.outputs.registry_prefix }} - image_name: ubuntu - image_tag: ${{ matrix.ubuntu_version }} - image_extra_tags: | + registry-prefix: ${{ needs.init.outputs.registry_prefix }} + image-name: ubuntu + image-tag: ${{ matrix.ubuntu_version }} + image-extra-tags: | ${{ matrix.ubuntu_version }}-${{ needs.init.outputs.version }} ${{ matrix.ubuntu_version == needs.init.outputs.ubuntu_latest && 'latest' || '' }} context: ubuntu version: ${{ needs.init.outputs.version }} - build_args: | + build-args: | UBUNTU_VERSION=${{ matrix.ubuntu_version }} push: ${{ needs.init.outputs.push == 'true' }} - cache_scope: ubuntu-${{ matrix.ubuntu_version }} - cache_image_tag: ${{ matrix.ubuntu_version }} + cache-scope: ubuntu-${{ matrix.ubuntu_version }} + cache-image-tag: ${{ matrix.ubuntu_version }} build_python: name: Python ${{ matrix.python_version }} (Alpine ${{ matrix.alpine_version }}) @@ -149,19 +149,19 @@ jobs: uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} - registry_prefix: ${{ needs.init.outputs.registry_prefix }} - image_name: base-python - image_tag: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} - image_extra_tags: | + registry-prefix: ${{ needs.init.outputs.registry_prefix }} + image-name: base-python + image-tag: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} + image-extra-tags: | ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && matrix.python_version == needs.init.outputs.python_latest && 'latest' || '' }} context: python/${{ matrix.python_version }} version: ${{ needs.init.outputs.version }} - build_args: | + build-args: | BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base BASE_VERSION=${{ matrix.alpine_version }} push: ${{ needs.init.outputs.push == 'true' }} - cache_scope: python-${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} - verify_base: ghcr.io/${{ github.repository_owner }}/base:${{ matrix.alpine_version }} - cosign_base_identity: "https://github.com/${{ github.repository }}/.*" - cache_image_tag: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} + cache-scope: python-${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} + verify-base: ghcr.io/${{ github.repository_owner }}/base:${{ matrix.alpine_version }} + cosign-base-identity: "https://github.com/${{ github.repository }}/.*" + cache-image-tag: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} From faee5d2034528547698a171c49f2215f920ffe16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 4 Mar 2026 11:22:44 +0100 Subject: [PATCH 05/23] Cleanup args in Python Dockerfiles --- python/3.12/Dockerfile | 2 +- python/3.13/Dockerfile | 2 +- python/3.14/Dockerfile | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/python/3.12/Dockerfile b/python/3.12/Dockerfile index 1e98e796..4ba6b4fe 100644 --- a/python/3.12/Dockerfile +++ b/python/3.12/Dockerfile @@ -129,7 +129,7 @@ ARG BUILD_VERSION=0.0.0-local LABEL \ io.hass.type="base" \ - io.hass.base.name="python" \ + io.hass.base.name="${BUILD_NAME}" \ io.hass.base.version="${BUILD_VERSION}" \ io.hass.base.arch="${BUILD_ARCH}" \ io.hass.base.image="${BUILD_FROM}" \ diff --git a/python/3.13/Dockerfile b/python/3.13/Dockerfile index f968159b..ecdad525 100644 --- a/python/3.13/Dockerfile +++ b/python/3.13/Dockerfile @@ -125,7 +125,7 @@ ARG BUILD_VERSION=0.0.0-local LABEL \ io.hass.type="base" \ - io.hass.base.name="python" \ + io.hass.base.name="${BUILD_NAME}" \ io.hass.base.version="${BUILD_VERSION}" \ io.hass.base.arch="${BUILD_ARCH}" \ io.hass.base.image="${BUILD_FROM}" \ diff --git a/python/3.14/Dockerfile b/python/3.14/Dockerfile index 9cac69ee..e368f87c 100644 --- a/python/3.14/Dockerfile +++ b/python/3.14/Dockerfile @@ -117,8 +117,6 @@ RUN set -ex; \ pip3 install --no-cache-dir --upgrade pip=="${PIP_VERSION}"; \ pip --version -ARG BASE_IMAGE=ghcr.io/home-assistant/base -ARG BASE_VERSION=3.23 ARG BUILD_ARCH=amd64 ARG BUILD_DATE="1970-01-01 00:00:00+00:00" ARG BUILD_FROM @@ -128,7 +126,7 @@ ARG BUILD_VERSION=0.0.0-local LABEL \ io.hass.type="base" \ - io.hass.base.name="python" \ + io.hass.base.name="${BUILD_NAME}" \ io.hass.base.version="${BUILD_VERSION}" \ io.hass.base.arch="${BUILD_ARCH}" \ io.hass.base.image="${BUILD_FROM}" \ From ade08574268b77977c7e6d97a26e92de93c723e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 4 Mar 2026 12:27:09 +0100 Subject: [PATCH 06/23] Add Supported platforms section to readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 43d05ca1..117b274d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,12 @@ Using these images as a base for other Docker projects is, however, not recommen The image include [S6-Overlay](https://github.com/just-containers/s6-overlay), [Bashio](https://github.com/hassio-addons/bashio) and [TempIO](https://github.com/home-assistant/tempio). +## Supported architectures + +Images are built for all platforms officially supported by Home Assistant, which are `amd64` and `arm64`. + +Beginning with the 2026.03.0 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. + ## Base images We support version that are not EOL: https://alpinelinux.org/releases/ From 8470f0bd6b20de76c50cc9a1626b685c6e88f399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 4 Mar 2026 12:30:40 +0100 Subject: [PATCH 07/23] Clarify Alpine base for Python in readme example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 117b274d..5328032f 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ docker buildx build \ ubuntu/ ``` -Python 3.14 image, using the official Alpine 3.23 base image from GHCR: +Python 3.14 image, using the Home Assistant Alpine 3.23 base image from GHCR: ```bash docker buildx build \ From 58e2b38701e99e9cfa5c595d90eac3d470435d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 4 Mar 2026 12:32:12 +0100 Subject: [PATCH 08/23] Explain permissions --- .github/workflows/builder.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index cf7473be..507f10c6 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -18,7 +18,9 @@ env: permissions: contents: read + # id-token is required for Cosign in the builder workflow id-token: write # zizmor: ignore[excessive-permissions] + # write permissions are required to push built images to GHCR in the builder workflow packages: write # zizmor: ignore[excessive-permissions] jobs: From e6375ba142c73b3e8a6e6bd098ac8a510aff0ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Thu, 5 Mar 2026 12:02:13 +0100 Subject: [PATCH 09/23] Update to match latest inputs, sort workflow inputs --- .github/workflows/builder.yml | 78 +++++++++++++++++------------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 507f10c6..3ec1f162 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -78,19 +78,19 @@ jobs: uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} - registry-prefix: ${{ needs.init.outputs.registry_prefix }} + build-args: | + ALPINE_VERSION=${{ matrix.alpine_version }} + cache-gha-scope: alpine-${{ matrix.alpine_version }} + cache-image-tag: ${{ matrix.alpine_version }} + context: alpine image-name: base - image-tag: ${{ matrix.alpine_version }} - image-extra-tags: | + image-tags: | + ${{ matrix.alpine_version }} ${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && 'latest' || '' }} - context: alpine - version: ${{ needs.init.outputs.version }} - build-args: | - ALPINE_VERSION=${{ matrix.alpine_version }} push: ${{ needs.init.outputs.push == 'true' }} - cache-scope: alpine-${{ matrix.alpine_version }} - cache-image-tag: ${{ matrix.alpine_version }} + registry-prefix: ${{ needs.init.outputs.registry_prefix }} + version: ${{ needs.init.outputs.version }} build_debian: name: Debian ${{ matrix.debian_version }} @@ -102,19 +102,19 @@ jobs: uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} - registry-prefix: ${{ needs.init.outputs.registry_prefix }} + build-args: | + DEBIAN_VERSION=${{ matrix.debian_version }} + cache-gha-scope: debian-${{ matrix.debian_version }} + cache-image-tag: ${{ matrix.debian_version }} + context: debian image-name: debian - image-tag: ${{ matrix.debian_version }} - image-extra-tags: | + image-tags: | + ${{ matrix.debian_version }} ${{ matrix.debian_version }}-${{ needs.init.outputs.version }} ${{ matrix.debian_version == needs.init.outputs.debian_latest && 'latest' || '' }} - context: debian - version: ${{ needs.init.outputs.version }} - build-args: | - DEBIAN_VERSION=${{ matrix.debian_version }} push: ${{ needs.init.outputs.push == 'true' }} - cache-scope: debian-${{ matrix.debian_version }} - cache-image-tag: ${{ matrix.debian_version }} + registry-prefix: ${{ needs.init.outputs.registry_prefix }} + version: ${{ needs.init.outputs.version }} build_ubuntu: name: Ubuntu ${{ matrix.ubuntu_version }} @@ -126,19 +126,19 @@ jobs: uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} - registry-prefix: ${{ needs.init.outputs.registry_prefix }} + build-args: | + UBUNTU_VERSION=${{ matrix.ubuntu_version }} + cache-gha-scope: ubuntu-${{ matrix.ubuntu_version }} + cache-image-tag: ${{ matrix.ubuntu_version }} + context: ubuntu image-name: ubuntu - image-tag: ${{ matrix.ubuntu_version }} - image-extra-tags: | + image-tags: | + ${{ matrix.ubuntu_version }} ${{ matrix.ubuntu_version }}-${{ needs.init.outputs.version }} ${{ matrix.ubuntu_version == needs.init.outputs.ubuntu_latest && 'latest' || '' }} - context: ubuntu - version: ${{ needs.init.outputs.version }} - build-args: | - UBUNTU_VERSION=${{ matrix.ubuntu_version }} push: ${{ needs.init.outputs.push == 'true' }} - cache-scope: ubuntu-${{ matrix.ubuntu_version }} - cache-image-tag: ${{ matrix.ubuntu_version }} + registry-prefix: ${{ needs.init.outputs.registry_prefix }} + version: ${{ needs.init.outputs.version }} build_python: name: Python ${{ matrix.python_version }} (Alpine ${{ matrix.alpine_version }}) @@ -151,19 +151,19 @@ jobs: uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder with: architectures: ${{ needs.init.outputs.architectures }} - registry-prefix: ${{ needs.init.outputs.registry_prefix }} - image-name: base-python - image-tag: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} - image-extra-tags: | - ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} - ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && matrix.python_version == needs.init.outputs.python_latest && 'latest' || '' }} - context: python/${{ matrix.python_version }} - version: ${{ needs.init.outputs.version }} build-args: | BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base BASE_VERSION=${{ matrix.alpine_version }} - push: ${{ needs.init.outputs.push == 'true' }} - cache-scope: python-${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} - verify-base: ghcr.io/${{ github.repository_owner }}/base:${{ matrix.alpine_version }} - cosign-base-identity: "https://github.com/${{ github.repository }}/.*" + cache-gha-scope: python-${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} cache-image-tag: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} + context: python/${{ matrix.python_version }} + cosign-base-identity: "https://github.com/${{ github.repository }}/.*" + cosign-base-verify: ghcr.io/${{ github.repository_owner }}/base:${{ matrix.alpine_version }} + image-name: base-python + image-tags: | + ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} + ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} + ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && matrix.python_version == needs.init.outputs.python_latest && 'latest' || '' }} + push: ${{ needs.init.outputs.push == 'true' }} + registry-prefix: ${{ needs.init.outputs.registry_prefix }} + version: ${{ needs.init.outputs.version }} From bcf14cfcf9552c17e5870cf83a7435dd1da7ac42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Thu, 5 Mar 2026 14:35:37 +0100 Subject: [PATCH 10/23] Use local reusable workflow again 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. --- .github/workflows/build-base-image.yml | 164 +++++++++++++++++++++++++ .github/workflows/builder.yml | 14 +-- 2 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/build-base-image.yml diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml new file mode 100644 index 00000000..c6650b93 --- /dev/null +++ b/.github/workflows/build-base-image.yml @@ -0,0 +1,164 @@ +name: Reusable workflow for single multi-arch image build + +on: + workflow_call: + inputs: + architectures: + description: Architectures to build (JSON array, e.g., '["amd64", "aarch64"]') + required: true + type: string + build-args: + description: Additional build arguments (key=value format, one per line) + required: false + default: "" + type: string + cache-gha: + description: Whether to use GitHub Actions cache for build caching + required: false + default: true + type: boolean + cache-gha-scope: + description: Scope for build cache sharing (defaults to architecture, set if building multiple images from a single repo) + required: false + default: "" + type: string + cache-image-tag: + description: Tag of the image containing BuildKit inline cache metadata + required: false + default: "latest" + type: string + context: + description: Build context path (usually the directory with Dockerfile) + required: true + type: string + cosign: + description: Whether to sign images with Cosign + required: false + default: true + type: boolean + cosign-base-identity: + description: Certificate identity regexp for verifying the base (FROM) image + required: false + default: "" + type: string + cosign-base-issuer: + description: Certificate OIDC issuer regexp for base image verification (defaults to cosign-issuer) + required: false + default: "" + type: string + cosign-base-verify: + description: Base image reference to verify with cosign before building + required: false + default: "" + type: string + cosign-identity: + description: Certificate identity regexp for verifying cache images (defaults to current repo pattern) + required: false + default: "" + type: string + cosign-issuer: + description: Certificate OIDC issuer regexp for all cosign verification + required: false + default: "https://token.actions.githubusercontent.com" + type: string + file: + description: Dockerfile path (defaults to "Dockerfile" in the context directory) + required: false + default: "" + type: string + image-name: + description: Image name without a tag (e.g., "base-python") + required: true + type: string + image-tags: + description: Image tags, one per line + required: true + type: string + multi-arch: + description: Prefix per-arch image names with architecture (required for multiple architectures) + required: false + default: true + type: boolean + push: + description: Whether to push images to registry + required: false + default: false + type: boolean + version: + description: Image version label + required: true + type: string + +jobs: + prepare: + name: Prepare build matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.prepare.outputs.matrix }} + steps: + - name: Prepare multi-arch matrix + id: prepare + uses: home-assistant/builder/actions/prepare-multi-arch-matrix@gha-builder + with: + architectures: ${{ inputs.architectures }} + image-name: ${{ inputs.image-name }} + multi-arch: ${{ inputs.multi-arch }} + + build: + name: Build ${{ matrix.arch }} image + needs: prepare + runs-on: ${{ matrix.os }} + permissions: + contents: read + id-token: write + packages: write + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }} + steps: + - name: Checkout the repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Build image + id: build + uses: home-assistant/builder/actions/build-image@gha-builder + with: + arch: ${{ matrix.arch }} + build-args: ${{ inputs.build-args }} + cache-gha: ${{ inputs.cache-gha }} + cache-gha-scope: ${{ inputs.cache-gha-scope }} + cache-image-tag: ${{ inputs.cache-image-tag }} + container-registry-password: ${{ secrets.GITHUB_TOKEN }} + context: ${{ inputs.context }} + cosign: ${{ inputs.cosign }} + cosign-base-identity: ${{ inputs.cosign-base-identity }} + cosign-base-issuer: ${{ inputs.cosign-base-issuer }} + cosign-base-verify: ${{ inputs.cosign-base-verify }} + cosign-identity: ${{ inputs.cosign-identity }} + cosign-issuer: ${{ inputs.cosign-issuer }} + file: ${{ inputs.file }} + image: ${{ matrix.image }} + image-tags: ${{ inputs.image-tags }} + push: ${{ inputs.push }} + version: ${{ inputs.version }} + + manifest: + name: Publish multi-arch manifest + if: inputs.push && inputs.multi-arch + needs: [prepare, build] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + packages: write + steps: + - name: Publish multi-arch manifest + uses: home-assistant/builder/actions/publish-multi-arch-manifest@gha-builder + with: + architectures: ${{ inputs.architectures }} + container-registry-password: ${{ secrets.GITHUB_TOKEN }} + cosign: ${{ inputs.cosign }} + image-name: ${{ inputs.image-name }} + image-tags: ${{ inputs.image-tags }} diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 3ec1f162..13f12d9b 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -18,7 +18,7 @@ env: permissions: contents: read - # id-token is required for Cosign in the builder workflow + # id-token is required for Cosign in the build-base-image workflow id-token: write # zizmor: ignore[excessive-permissions] # write permissions are required to push built images to GHCR in the builder workflow packages: write # zizmor: ignore[excessive-permissions] @@ -75,7 +75,7 @@ jobs: fail-fast: false matrix: alpine_version: ["3.21", "3.22", "3.23"] - uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder + uses: ./.github/workflows/build-base-image.yml with: architectures: ${{ needs.init.outputs.architectures }} build-args: | @@ -89,7 +89,6 @@ jobs: ${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && 'latest' || '' }} push: ${{ needs.init.outputs.push == 'true' }} - registry-prefix: ${{ needs.init.outputs.registry_prefix }} version: ${{ needs.init.outputs.version }} build_debian: @@ -99,7 +98,7 @@ jobs: fail-fast: false matrix: debian_version: ["bookworm", "trixie"] - uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder + uses: ./.github/workflows/build-base-image.yml with: architectures: ${{ needs.init.outputs.architectures }} build-args: | @@ -113,7 +112,6 @@ jobs: ${{ matrix.debian_version }}-${{ needs.init.outputs.version }} ${{ matrix.debian_version == needs.init.outputs.debian_latest && 'latest' || '' }} push: ${{ needs.init.outputs.push == 'true' }} - registry-prefix: ${{ needs.init.outputs.registry_prefix }} version: ${{ needs.init.outputs.version }} build_ubuntu: @@ -123,7 +121,7 @@ jobs: fail-fast: false matrix: ubuntu_version: ["22.04", "24.04"] - uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder + uses: ./.github/workflows/build-base-image.yml with: architectures: ${{ needs.init.outputs.architectures }} build-args: | @@ -137,7 +135,6 @@ jobs: ${{ matrix.ubuntu_version }}-${{ needs.init.outputs.version }} ${{ matrix.ubuntu_version == needs.init.outputs.ubuntu_latest && 'latest' || '' }} push: ${{ needs.init.outputs.push == 'true' }} - registry-prefix: ${{ needs.init.outputs.registry_prefix }} version: ${{ needs.init.outputs.version }} build_python: @@ -148,7 +145,7 @@ jobs: matrix: alpine_version: ["3.21", "3.22", "3.23"] python_version: ["3.12", "3.13", "3.14"] - uses: home-assistant/builder/.github/workflows/builder.yml@gha-builder + uses: ./.github/workflows/build-base-image.yml with: architectures: ${{ needs.init.outputs.architectures }} build-args: | @@ -165,5 +162,4 @@ jobs: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && matrix.python_version == needs.init.outputs.python_latest && 'latest' || '' }} push: ${{ needs.init.outputs.push == 'true' }} - registry-prefix: ${{ needs.init.outputs.registry_prefix }} version: ${{ needs.init.outputs.version }} From 94b9b2baa57f9e0c5059399c761bd179c7e2aa43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 10 Mar 2026 09:20:19 +0100 Subject: [PATCH 11/23] Update for latest builder changes --- .github/workflows/build-base-image.yml | 9 +++++++++ .github/workflows/builder.yml | 8 ++++++++ alpine/Dockerfile | 17 +---------------- debian/Dockerfile | 17 +---------------- python/3.12/Dockerfile | 17 +---------------- python/3.13/Dockerfile | 17 +---------------- python/3.14/Dockerfile | 17 +---------------- ubuntu/Dockerfile | 17 +---------------- 8 files changed, 23 insertions(+), 96 deletions(-) diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml index c6650b93..e7752fa8 100644 --- a/.github/workflows/build-base-image.yml +++ b/.github/workflows/build-base-image.yml @@ -74,6 +74,11 @@ on: description: Image tags, one per line required: true type: string + labels: + description: Additional OCI labels (key=value format, one per line) + required: false + default: "" + type: string multi-arch: description: Prefix per-arch image names with architecture (required for multiple architectures) required: false @@ -141,6 +146,10 @@ jobs: file: ${{ inputs.file }} image: ${{ matrix.image }} image-tags: ${{ inputs.image-tags }} + labels: | + io.hass.base.arch=${{ matrix.arch }} + io.hass.base.version=${{ inputs.version }} + ${{ inputs.labels }} push: ${{ inputs.push }} version: ${{ inputs.version }} diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 13f12d9b..34545e82 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -88,6 +88,8 @@ jobs: ${{ matrix.alpine_version }} ${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && 'latest' || '' }} + labels: | + io.hass.base.image=alpine:${{ matrix.alpine_version }} push: ${{ needs.init.outputs.push == 'true' }} version: ${{ needs.init.outputs.version }} @@ -111,6 +113,8 @@ jobs: ${{ matrix.debian_version }} ${{ matrix.debian_version }}-${{ needs.init.outputs.version }} ${{ matrix.debian_version == needs.init.outputs.debian_latest && 'latest' || '' }} + labels: | + io.hass.base.image=debian:${{ matrix.debian_version }}-slim push: ${{ needs.init.outputs.push == 'true' }} version: ${{ needs.init.outputs.version }} @@ -134,6 +138,8 @@ jobs: ${{ matrix.ubuntu_version }} ${{ matrix.ubuntu_version }}-${{ needs.init.outputs.version }} ${{ matrix.ubuntu_version == needs.init.outputs.ubuntu_latest && 'latest' || '' }} + labels: | + io.hass.base.image=ubuntu:${{ matrix.ubuntu_version }} push: ${{ needs.init.outputs.push == 'true' }} version: ${{ needs.init.outputs.version }} @@ -161,5 +167,7 @@ jobs: ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }} ${{ matrix.python_version }}-alpine${{ matrix.alpine_version }}-${{ needs.init.outputs.version }} ${{ matrix.alpine_version == needs.init.outputs.alpine_latest && matrix.python_version == needs.init.outputs.python_latest && 'latest' || '' }} + labels: | + io.hass.base.image=ghcr.io/${{ github.repository_owner }}/base:${{ matrix.alpine_version }} push: ${{ needs.init.outputs.push == 'true' }} version: ${{ needs.init.outputs.version }} diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 84ff9871..da844944 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -92,21 +92,6 @@ COPY rootfs / WORKDIR / ENTRYPOINT ["/init"] -ARG BUILD_ARCH=amd64 -ARG BUILD_DATE="1970-01-01 00:00:00+00:00" -ARG BUILD_FROM -ARG BUILD_NAME="alpine" -ARG BUILD_REPOSITORY -ARG BUILD_VERSION=0.0.0-local - LABEL \ io.hass.type="base" \ - io.hass.base.name="${BUILD_NAME}" \ - io.hass.base.version="${BUILD_VERSION}" \ - io.hass.base.arch="${BUILD_ARCH}" \ - io.hass.base.image="${BUILD_FROM}" \ - io.hass.arch="${BUILD_ARCH}" \ - io.hass.version="${BUILD_VERSION}" \ - org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.version="${BUILD_VERSION}" \ - org.opencontainers.image.source="${BUILD_REPOSITORY}" + io.hass.base.name="alpine" diff --git a/debian/Dockerfile b/debian/Dockerfile index e94ad8f0..c3e920fc 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -75,21 +75,6 @@ RUN \ WORKDIR / ENTRYPOINT ["/init"] -ARG BUILD_ARCH=amd64 -ARG BUILD_DATE="1970-01-01 00:00:00+00:00" -ARG BUILD_FROM -ARG BUILD_NAME="debian" -ARG BUILD_REPOSITORY -ARG BUILD_VERSION=0.0.0-local - LABEL \ io.hass.type="base" \ - io.hass.base.name="${BUILD_NAME}" \ - io.hass.base.version="${BUILD_VERSION}" \ - io.hass.base.arch="${BUILD_ARCH}" \ - io.hass.base.image="${BUILD_FROM}" \ - io.hass.arch="${BUILD_ARCH}" \ - io.hass.version="${BUILD_VERSION}" \ - org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.version="${BUILD_VERSION}" \ - org.opencontainers.image.source="${BUILD_REPOSITORY}" + io.hass.base.name="debian" diff --git a/python/3.12/Dockerfile b/python/3.12/Dockerfile index 4ba6b4fe..3a9bf494 100644 --- a/python/3.12/Dockerfile +++ b/python/3.12/Dockerfile @@ -120,21 +120,6 @@ RUN set -ex; \ pip3 install --no-cache-dir --upgrade pip=="${PIP_VERSION}"; \ pip --version -ARG BUILD_ARCH=amd64 -ARG BUILD_DATE="1970-01-01 00:00:00+00:00" -ARG BUILD_FROM -ARG BUILD_NAME="python" -ARG BUILD_REPOSITORY -ARG BUILD_VERSION=0.0.0-local - LABEL \ io.hass.type="base" \ - io.hass.base.name="${BUILD_NAME}" \ - io.hass.base.version="${BUILD_VERSION}" \ - io.hass.base.arch="${BUILD_ARCH}" \ - io.hass.base.image="${BUILD_FROM}" \ - io.hass.arch="${BUILD_ARCH}" \ - io.hass.version="${BUILD_VERSION}" \ - org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.version="${BUILD_VERSION}" \ - org.opencontainers.image.source="${BUILD_REPOSITORY}" + io.hass.base.name="python" diff --git a/python/3.13/Dockerfile b/python/3.13/Dockerfile index ecdad525..9c7a5ac5 100644 --- a/python/3.13/Dockerfile +++ b/python/3.13/Dockerfile @@ -116,21 +116,6 @@ RUN set -ex; \ pip3 install --no-cache-dir --upgrade pip=="${PIP_VERSION}"; \ pip --version -ARG BUILD_ARCH=amd64 -ARG BUILD_DATE="1970-01-01 00:00:00+00:00" -ARG BUILD_FROM -ARG BUILD_NAME="python" -ARG BUILD_REPOSITORY -ARG BUILD_VERSION=0.0.0-local - LABEL \ io.hass.type="base" \ - io.hass.base.name="${BUILD_NAME}" \ - io.hass.base.version="${BUILD_VERSION}" \ - io.hass.base.arch="${BUILD_ARCH}" \ - io.hass.base.image="${BUILD_FROM}" \ - io.hass.arch="${BUILD_ARCH}" \ - io.hass.version="${BUILD_VERSION}" \ - org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.version="${BUILD_VERSION}" \ - org.opencontainers.image.source="${BUILD_REPOSITORY}" + io.hass.base.name="python" diff --git a/python/3.14/Dockerfile b/python/3.14/Dockerfile index e368f87c..5e5daa6a 100644 --- a/python/3.14/Dockerfile +++ b/python/3.14/Dockerfile @@ -117,21 +117,6 @@ RUN set -ex; \ pip3 install --no-cache-dir --upgrade pip=="${PIP_VERSION}"; \ pip --version -ARG BUILD_ARCH=amd64 -ARG BUILD_DATE="1970-01-01 00:00:00+00:00" -ARG BUILD_FROM -ARG BUILD_NAME="python" -ARG BUILD_REPOSITORY -ARG BUILD_VERSION=0.0.0-local - LABEL \ io.hass.type="base" \ - io.hass.base.name="${BUILD_NAME}" \ - io.hass.base.version="${BUILD_VERSION}" \ - io.hass.base.arch="${BUILD_ARCH}" \ - io.hass.base.image="${BUILD_FROM}" \ - io.hass.arch="${BUILD_ARCH}" \ - io.hass.version="${BUILD_VERSION}" \ - org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.version="${BUILD_VERSION}" \ - org.opencontainers.image.source="${BUILD_REPOSITORY}" + io.hass.base.name="python" diff --git a/ubuntu/Dockerfile b/ubuntu/Dockerfile index e881448d..82ec2889 100644 --- a/ubuntu/Dockerfile +++ b/ubuntu/Dockerfile @@ -73,21 +73,6 @@ RUN \ WORKDIR / ENTRYPOINT ["/init"] -ARG BUILD_ARCH=amd64 -ARG BUILD_DATE="1970-01-01 00:00:00+00:00" -ARG BUILD_FROM -ARG BUILD_NAME="ubuntu" -ARG BUILD_REPOSITORY -ARG BUILD_VERSION=0.0.0-local - LABEL \ io.hass.type="base" \ - io.hass.base.name="${BUILD_NAME}" \ - io.hass.base.version="${BUILD_VERSION}" \ - io.hass.base.arch="${BUILD_ARCH}" \ - io.hass.base.image="${BUILD_FROM}" \ - io.hass.arch="${BUILD_ARCH}" \ - io.hass.version="${BUILD_VERSION}" \ - org.opencontainers.image.created="${BUILD_DATE}" \ - org.opencontainers.image.version="${BUILD_VERSION}" \ - org.opencontainers.image.source="${BUILD_REPOSITORY}" + io.hass.base.name="ubuntu" From 9019660b2e456b5c03b22f537a4aa38a3dea22c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Mon, 16 Mar 2026 16:37:49 +0100 Subject: [PATCH 12/23] Remove dropped multi-arch flag --- .github/workflows/build-base-image.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml index e7752fa8..06c29512 100644 --- a/.github/workflows/build-base-image.yml +++ b/.github/workflows/build-base-image.yml @@ -79,11 +79,6 @@ on: required: false default: "" type: string - multi-arch: - description: Prefix per-arch image names with architecture (required for multiple architectures) - required: false - default: true - type: boolean push: description: Whether to push images to registry required: false @@ -107,7 +102,6 @@ jobs: with: architectures: ${{ inputs.architectures }} image-name: ${{ inputs.image-name }} - multi-arch: ${{ inputs.multi-arch }} build: name: Build ${{ matrix.arch }} image @@ -155,7 +149,7 @@ jobs: manifest: name: Publish multi-arch manifest - if: inputs.push && inputs.multi-arch + if: inputs.push needs: [prepare, build] runs-on: ubuntu-latest permissions: From bbe119ba2fb576a6d238824052e36fc73ed0ce05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 08:57:44 +0100 Subject: [PATCH 13/23] Update S6 overlay, pip and Python to match master branch --- alpine/Dockerfile | 2 +- debian/Dockerfile | 2 +- python/3.12/Dockerfile | 4 ++-- python/3.13/Dockerfile | 2 +- python/3.14/Dockerfile | 2 +- ubuntu/Dockerfile | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/alpine/Dockerfile b/alpine/Dockerfile index da844944..17f66828 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -21,7 +21,7 @@ WORKDIR /usr/src ARG TARGETARCH ARG BASHIO_VERSION=0.17.5 ARG TEMPIO_VERSION=2024.11.2 -ARG S6_OVERLAY_VERSION=3.1.6.2 +ARG S6_OVERLAY_VERSION=3.2.2.0 ARG JEMALLOC_VERSION=5.3.0 RUN \ diff --git a/debian/Dockerfile b/debian/Dockerfile index c3e920fc..7ff96d27 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -22,7 +22,7 @@ WORKDIR /usr/src ARG TARGETARCH ARG BASHIO_VERSION=0.17.5 ARG TEMPIO_VERSION=2024.11.2 -ARG S6_OVERLAY_VERSION=3.1.6.2 +ARG S6_OVERLAY_VERSION=3.2.2.0 RUN \ set -x \ diff --git a/python/3.12/Dockerfile b/python/3.12/Dockerfile index 5202700c..d3552b95 100644 --- a/python/3.12/Dockerfile +++ b/python/3.12/Dockerfile @@ -7,7 +7,7 @@ ARG BASE_IMAGE ARG BASE_VERSION ARG BUILD_FROM -ARG PYTHON_VERSION=3.12.12 +ARG PYTHON_VERSION=3.12.13 ARG CERT_IDENTITY=thomas@python.org ARG CERT_OIDC_ISSUER=https://accounts.google.com @@ -111,7 +111,7 @@ RUN \ && ln -s python3 python \ && ln -s python3-config python-config -ARG PIP_VERSION=25.3 +ARG PIP_VERSION=26.0.1 RUN set -ex; \ python -m ensurepip --upgrade --default-pip; \ diff --git a/python/3.13/Dockerfile b/python/3.13/Dockerfile index 87f9a17b..fc8690fa 100644 --- a/python/3.13/Dockerfile +++ b/python/3.13/Dockerfile @@ -107,7 +107,7 @@ RUN \ && ln -s python3 python \ && ln -s python3-config python-config -ARG PIP_VERSION=25.3 +ARG PIP_VERSION=26.0.1 RUN set -ex; \ python -m ensurepip --upgrade --default-pip; \ diff --git a/python/3.14/Dockerfile b/python/3.14/Dockerfile index 5121ff94..11def3f4 100644 --- a/python/3.14/Dockerfile +++ b/python/3.14/Dockerfile @@ -108,7 +108,7 @@ RUN \ && ln -s python3 python \ && ln -s python3-config python-config -ARG PIP_VERSION=25.3 +ARG PIP_VERSION=26.0.1 RUN set -ex; \ python -m ensurepip --upgrade --default-pip; \ diff --git a/ubuntu/Dockerfile b/ubuntu/Dockerfile index 82ec2889..0da6f73c 100644 --- a/ubuntu/Dockerfile +++ b/ubuntu/Dockerfile @@ -21,7 +21,7 @@ WORKDIR /usr/src ARG TARGETARCH ARG BASHIO_VERSION=0.17.5 ARG TEMPIO_VERSION=2024.11.2 -ARG S6_OVERLAY_VERSION=3.1.6.2 +ARG S6_OVERLAY_VERSION=3.2.2.0 RUN \ set -x \ From f4b29ae408ccf831e1fd5e41140417b8577ae6aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 11:30:51 +0100 Subject: [PATCH 14/23] Pin builder actions to release SHA --- .github/workflows/build-base-image.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml index 06c29512..cefca520 100644 --- a/.github/workflows/build-base-image.yml +++ b/.github/workflows/build-base-image.yml @@ -98,7 +98,7 @@ jobs: steps: - name: Prepare multi-arch matrix id: prepare - uses: home-assistant/builder/actions/prepare-multi-arch-matrix@gha-builder + uses: home-assistant/builder/actions/prepare-multi-arch-matrix@dd86b0a3ee041ecc3f6512c0bfbc73a0cc724ce6 # 2026.03.0 with: architectures: ${{ inputs.architectures }} image-name: ${{ inputs.image-name }} @@ -122,7 +122,7 @@ jobs: - name: Build image id: build - uses: home-assistant/builder/actions/build-image@gha-builder + uses: home-assistant/builder/actions/build-image@dd86b0a3ee041ecc3f6512c0bfbc73a0cc724ce6 # 2026.03.0 with: arch: ${{ matrix.arch }} build-args: ${{ inputs.build-args }} @@ -158,7 +158,7 @@ jobs: packages: write steps: - name: Publish multi-arch manifest - uses: home-assistant/builder/actions/publish-multi-arch-manifest@gha-builder + uses: home-assistant/builder/actions/publish-multi-arch-manifest@dd86b0a3ee041ecc3f6512c0bfbc73a0cc724ce6 # 2026.03.0 with: architectures: ${{ inputs.architectures }} container-registry-password: ${{ secrets.GITHUB_TOKEN }} From 5ac7e035454b524c8d381536434b684bd8eaa5dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 11:42:57 +0100 Subject: [PATCH 15/23] Remove redundant contents permissions for manifest job --- .github/workflows/build-base-image.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml index cefca520..5b196953 100644 --- a/.github/workflows/build-base-image.yml +++ b/.github/workflows/build-base-image.yml @@ -153,7 +153,6 @@ jobs: needs: [prepare, build] runs-on: ubuntu-latest permissions: - contents: read id-token: write packages: write steps: From fb16c31b8d03a0337c89287b8f1c5b4e62a6a7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 11:46:02 +0100 Subject: [PATCH 16/23] Scope permissions to jobs --- .github/workflows/builder.yml | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 34545e82..ec6d3e9a 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -16,13 +16,6 @@ env: UBUNTU_LATEST: "24.04" PYTHON_LATEST: "3.14" -permissions: - contents: read - # id-token is required for Cosign in the build-base-image workflow - id-token: write # zizmor: ignore[excessive-permissions] - # write permissions are required to push built images to GHCR in the builder workflow - packages: write # zizmor: ignore[excessive-permissions] - jobs: init: name: Initialize build @@ -75,6 +68,10 @@ jobs: fail-fast: false matrix: alpine_version: ["3.21", "3.22", "3.23"] + permissions: + contents: read + id-token: write # For cosign signing + packages: write # For pushing to registry uses: ./.github/workflows/build-base-image.yml with: architectures: ${{ needs.init.outputs.architectures }} @@ -100,6 +97,10 @@ jobs: fail-fast: false matrix: debian_version: ["bookworm", "trixie"] + permissions: + contents: read + id-token: write # For cosign signing + packages: write # For pushing to registry uses: ./.github/workflows/build-base-image.yml with: architectures: ${{ needs.init.outputs.architectures }} @@ -125,6 +126,10 @@ jobs: fail-fast: false matrix: ubuntu_version: ["22.04", "24.04"] + permissions: + contents: read + id-token: write # For cosign signing + packages: write # For pushing to registry uses: ./.github/workflows/build-base-image.yml with: architectures: ${{ needs.init.outputs.architectures }} @@ -151,6 +156,10 @@ jobs: matrix: alpine_version: ["3.21", "3.22", "3.23"] python_version: ["3.12", "3.13", "3.14"] + permissions: + contents: read + id-token: write # For cosign signing + packages: write # For pushing to registry uses: ./.github/workflows/build-base-image.yml with: architectures: ${{ needs.init.outputs.architectures }} From 262d5b44c3dc7a8fde7582c9fe80a4c117db3932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 12:04:10 +0100 Subject: [PATCH 17/23] Expect TARGETARCH to be set by BuildKit --- alpine/Dockerfile | 6 +----- debian/Dockerfile | 6 +----- ubuntu/Dockerfile | 6 +----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 17f66828..870b551b 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -27,11 +27,7 @@ ARG JEMALLOC_VERSION=5.3.0 RUN \ set -x \ && if [ -z "${TARGETARCH}" ]; then \ - case "$(uname -m)" in \ - x86_64) TARGETARCH="amd64" ;; \ - aarch64|arm64) TARGETARCH="arm64" ;; \ - *) echo "Unsupported architecture: $(uname -m)" && exit 1 ;; \ - esac; \ + echo "TARGETARCH is not set, please use Docker BuildKit for the build." && exit 1; \ fi \ && case "${TARGETARCH}" in \ amd64) TEMPIO_ARCH="amd64"; S6_ARCH="x86_64" ;; \ diff --git a/debian/Dockerfile b/debian/Dockerfile index 7ff96d27..7606a811 100644 --- a/debian/Dockerfile +++ b/debian/Dockerfile @@ -27,11 +27,7 @@ ARG S6_OVERLAY_VERSION=3.2.2.0 RUN \ set -x \ && if [ -z "${TARGETARCH}" ]; then \ - case "$(uname -m)" in \ - x86_64) TARGETARCH="amd64" ;; \ - aarch64|arm64) TARGETARCH="arm64" ;; \ - *) echo "Unsupported architecture: $(uname -m)" && exit 1 ;; \ - esac; \ + echo "TARGETARCH is not set, please use Docker BuildKit for the build." && exit 1; \ fi \ && case "${TARGETARCH}" in \ amd64) TEMPIO_ARCH="amd64"; S6_ARCH="x86_64" ;; \ diff --git a/ubuntu/Dockerfile b/ubuntu/Dockerfile index 0da6f73c..915fa839 100644 --- a/ubuntu/Dockerfile +++ b/ubuntu/Dockerfile @@ -26,11 +26,7 @@ ARG S6_OVERLAY_VERSION=3.2.2.0 RUN \ set -x \ && if [ -z "${TARGETARCH}" ]; then \ - case "$(uname -m)" in \ - x86_64) TARGETARCH="amd64" ;; \ - aarch64|arm64) TARGETARCH="arm64" ;; \ - *) echo "Unsupported architecture: $(uname -m)" && exit 1 ;; \ - esac; \ + echo "TARGETARCH is not set, please use Docker BuildKit for the build." && exit 1; \ fi \ && case "${TARGETARCH}" in \ amd64) TEMPIO_ARCH="amd64"; S6_ARCH="x86_64" ;; \ From 95d2de9d4c80f2078237e20b74f7254c9b92a86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 12:44:25 +0100 Subject: [PATCH 18/23] Use builder actions 2026.03.1 --- .github/workflows/build-base-image.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml index 5b196953..3a76e101 100644 --- a/.github/workflows/build-base-image.yml +++ b/.github/workflows/build-base-image.yml @@ -98,7 +98,7 @@ jobs: steps: - name: Prepare multi-arch matrix id: prepare - uses: home-assistant/builder/actions/prepare-multi-arch-matrix@dd86b0a3ee041ecc3f6512c0bfbc73a0cc724ce6 # 2026.03.0 + uses: home-assistant/builder/actions/prepare-multi-arch-matrix@537e6bc898a14b5d7201237c3fcd4999f9d3e661 # 2026.03.1 with: architectures: ${{ inputs.architectures }} image-name: ${{ inputs.image-name }} @@ -122,7 +122,7 @@ jobs: - name: Build image id: build - uses: home-assistant/builder/actions/build-image@dd86b0a3ee041ecc3f6512c0bfbc73a0cc724ce6 # 2026.03.0 + uses: home-assistant/builder/actions/build-image@537e6bc898a14b5d7201237c3fcd4999f9d3e661 # 2026.03.1 with: arch: ${{ matrix.arch }} build-args: ${{ inputs.build-args }} @@ -157,7 +157,7 @@ jobs: packages: write steps: - name: Publish multi-arch manifest - uses: home-assistant/builder/actions/publish-multi-arch-manifest@dd86b0a3ee041ecc3f6512c0bfbc73a0cc724ce6 # 2026.03.0 + uses: home-assistant/builder/actions/publish-multi-arch-manifest@537e6bc898a14b5d7201237c3fcd4999f9d3e661 # 2026.03.1 with: architectures: ${{ inputs.architectures }} container-registry-password: ${{ secrets.GITHUB_TOKEN }} From 4c10a9cc62a88f696f598a19070835db17c848b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 14:07:47 +0100 Subject: [PATCH 19/23] Update reusable workflow description --- .github/workflows/build-base-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml index 3a76e101..8f11e497 100644 --- a/.github/workflows/build-base-image.yml +++ b/.github/workflows/build-base-image.yml @@ -1,4 +1,4 @@ -name: Reusable workflow for single multi-arch image build +name: Reusable workflow building a multi-arch image on: workflow_call: From 2055d46addb59d59f19a0ab2c1d072e6ff0ab535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 14:27:31 +0100 Subject: [PATCH 20/23] Update the readme with expected release number --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5328032f..28ef304a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The image include [S6-Overlay](https://github.com/just-containers/s6-overlay), [ Images are built for all platforms officially supported by Home Assistant, which are `amd64` and `arm64`. -Beginning with the 2026.03.0 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 for these platforms. The old architecture-prefixed images (`aarch64-*`, `amd64-*`) are still available but preferably the multi-arch images should be used. ## Base images From a35150d5083432f4036fdb13e4ce315a7960c0dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 15:27:31 +0100 Subject: [PATCH 21/23] Fix image names for debian/ubuntu --- .github/workflows/builder.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index ec6d3e9a..f4d1cc22 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -109,7 +109,7 @@ jobs: cache-gha-scope: debian-${{ matrix.debian_version }} cache-image-tag: ${{ matrix.debian_version }} context: debian - image-name: debian + image-name: base-debian image-tags: | ${{ matrix.debian_version }} ${{ matrix.debian_version }}-${{ needs.init.outputs.version }} @@ -138,7 +138,7 @@ jobs: cache-gha-scope: ubuntu-${{ matrix.ubuntu_version }} cache-image-tag: ${{ matrix.ubuntu_version }} context: ubuntu - image-name: ubuntu + image-name: base-ubuntu image-tags: | ${{ matrix.ubuntu_version }} ${{ matrix.ubuntu_version }}-${{ needs.init.outputs.version }} From ce7b18bab9530863fda6c6c74e44a1566e2d1825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 15:31:16 +0100 Subject: [PATCH 22/23] Fix image names in examples, add paragraph about multi-platform builds --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 28ef304a..fd037c07 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,11 @@ We support the latest 3 release with the latest 3 Alpine version. Docker BuildKit (`docker buildx`) can be used for building the images locally without any extra tooling. Following are examples of building the images for a single (host) architecture. + +For a multi-platform build or cross-compilation, use the `--platform` flag with the appropriate target platform. See the official Docker documentation on [multi-platform builds](https://docs.docker.com/build/building/multi-platform/) for more details. + +### Examples + Alpine base using the default version from the Dockerfile: ```bash @@ -75,7 +80,7 @@ Debian base: ```bash docker buildx build \ --build-arg DEBIAN_VERSION=trixie - -t debian:trixie \ + -t base-debian:trixie \ debian/ ``` @@ -84,7 +89,7 @@ Ubuntu base: ```bash docker buildx build \ --build-arg UBUNTU_VERSION=24.04 \ - -t ubuntu:24.04 \ + -t base-ubuntu:24.04 \ ubuntu/ ``` From 54d26c622dceb4c99a81be30dfed663747bd5309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Tue, 17 Mar 2026 17:27:59 +0100 Subject: [PATCH 23/23] Bump builder actions to 2026.03.2 --- .github/workflows/build-base-image.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-base-image.yml b/.github/workflows/build-base-image.yml index 8f11e497..3a617206 100644 --- a/.github/workflows/build-base-image.yml +++ b/.github/workflows/build-base-image.yml @@ -98,7 +98,7 @@ jobs: steps: - name: Prepare multi-arch matrix id: prepare - uses: home-assistant/builder/actions/prepare-multi-arch-matrix@537e6bc898a14b5d7201237c3fcd4999f9d3e661 # 2026.03.1 + uses: home-assistant/builder/actions/prepare-multi-arch-matrix@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2 with: architectures: ${{ inputs.architectures }} image-name: ${{ inputs.image-name }} @@ -122,7 +122,7 @@ jobs: - name: Build image id: build - uses: home-assistant/builder/actions/build-image@537e6bc898a14b5d7201237c3fcd4999f9d3e661 # 2026.03.1 + uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2 with: arch: ${{ matrix.arch }} build-args: ${{ inputs.build-args }} @@ -157,7 +157,7 @@ jobs: packages: write steps: - name: Publish multi-arch manifest - uses: home-assistant/builder/actions/publish-multi-arch-manifest@537e6bc898a14b5d7201237c3fcd4999f9d3e661 # 2026.03.1 + uses: home-assistant/builder/actions/publish-multi-arch-manifest@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2 with: architectures: ${{ inputs.architectures }} container-registry-password: ${{ secrets.GITHUB_TOKEN }}