Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
24bbd9f
Add reusable workflow for native BuildKit build in GHA
sairon Mar 4, 2026
75583a2
Use dashes instead of underscores in inputs
sairon Mar 4, 2026
677f4d3
Move actions to actions folder
sairon Mar 4, 2026
7a7fab5
Improve (multi-)arch validation, fix cache-from tag
sairon Mar 4, 2026
f1b88d0
Address review comments - remove platform, adjust output argument
sairon Mar 4, 2026
89b3247
Make gha cache configurable
sairon Mar 4, 2026
2335ceb
Add 'load' input and default to false
sairon Mar 4, 2026
28a77db
Don't set any output if image is not pushed nor loaded
sairon Mar 4, 2026
433b20d
Change builder workflow name
sairon Mar 4, 2026
78b43ea
Specify required and defaults in cosign-verify
sairon Mar 5, 2026
e93d246
Docker registry -> container registry, context description update
sairon Mar 5, 2026
88a0820
Sort inputs alphabetically, rename to keep semantic clustering
sairon Mar 5, 2026
ea75d5e
Merge image tags into a single argument
sairon Mar 5, 2026
8d2380f
Fix container-(username|password) -> container-registry-(username|pas…
sairon Mar 5, 2026
a5d42ec
Drop builder workflow, create prepare-multi-arch-matrix and publish-m…
sairon Mar 5, 2026
52b5620
Set default registry-prefix
sairon Mar 5, 2026
e5d272b
Print matrix at the end of prepare-multi-arch-matrix
sairon Mar 5, 2026
c711632
Set dynamic arch/repo/version labels in build-image action, add label…
sairon Mar 10, 2026
48c5edc
Reintroduce BUILD_VERSION
sairon Mar 10, 2026
4a7d1db
Bump to docker/login-action v4.0.0 (as in #274)
sairon Mar 10, 2026
69243fc
Use imagetools metadata-file as source for digest when signing manifest
sairon Mar 10, 2026
eee43c8
Adjust build-image description
sairon Mar 10, 2026
2426beb
Set same params for output type=image and type=docker
sairon Mar 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 243 additions & 0 deletions .github/workflows/builder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we combine these two arguments into one like the builder action does?
Two ways to specify tags:

  • A string value
  • A list of values

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

invalid_arches=$(jq -r '[.[] | select(. != "amd64" and . != "aarch64")] | unique | join(", ")' <<< "${ARCHITECTURES}")
if [[ -n "${invalid_arches}" ]]; then
echo "::error::Unsupported architecture(s): ${invalid_arches}. Supported values: amd64, aarch64"
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"}
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: home-assistant/builder/actions/build-image@gha-builder
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is our strategy wrt to versioning this action?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short-term I wanted to keep making releases in the builder repo and using the tag as the reference. However, I'd like to split all our actions to separate repos with separate releases, sha-pinning and such. But it's a topic for another discussion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed elsewhere, to avoid creating too many repos we can keep the builder related actions in this/a single git repository, and release them all at once. sha-pinning etc. should work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this would be the way in the end - forgot to update the comment.

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
Loading