From 550716a75332235582b41d919c94c45aeffce00d Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Wed, 26 Nov 2025 13:24:45 +0100 Subject: [PATCH] Optimize docker container publish job (#157076) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/builder.yml | 144 ++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 1f4ebd942b8..9100f1bb976 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -15,6 +15,7 @@ env: UV_HTTP_TIMEOUT: 60 UV_SYSTEM_PYTHON: "true" BASE_IMAGE_VERSION: "2025.11.0" + ARCHITECTURES: '["amd64", "aarch64"]' jobs: init: @@ -25,6 +26,7 @@ jobs: version: ${{ steps.version.outputs.version }} channel: ${{ steps.version.outputs.channel }} publish: ${{ steps.version.outputs.publish }} + architectures: ${{ env.ARCHITECTURES }} steps: - name: Checkout the repository uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 @@ -85,7 +87,7 @@ jobs: strategy: fail-fast: false matrix: - arch: ["amd64", "aarch64"] + arch: ${{ fromJson(needs.init.outputs.architectures) }} include: - arch: amd64 os: ubuntu-latest @@ -227,6 +229,7 @@ jobs: file: ./Dockerfile platforms: ${{ steps.vars.outputs.platform }} push: true + provenance: false cache-from: ${{ steps.cache.outcome == 'success' && steps.vars.outputs.cache_image || '' }} build-args: | BUILD_FROM=${{ steps.vars.outputs.base_image }} @@ -350,9 +353,6 @@ jobs: matrix: registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"] steps: - - name: Checkout the repository - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - - name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 with: @@ -373,81 +373,87 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build Meta Image + - name: Verify architecture image signatures shell: bash run: | - export DOCKER_CLI_EXPERIMENTAL=enabled + ARCHS=$(echo '${{ needs.init.outputs.architectures }}' | jq -r '.[]') + for arch in $ARCHS; do + echo "Verifying ${arch} image signature..." + cosign verify \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + --certificate-identity-regexp https://github.com/home-assistant/core/.* \ + "ghcr.io/home-assistant/${arch}-homeassistant:${{ needs.init.outputs.version }}" + done + echo "✓ All images verified successfully" - function create_manifest() { - local tag_l=${1} - local tag_r=${2} - local registry=${{ matrix.registry }} + # Generate all Docker tags based on version string + # Version format: YYYY.MM.PATCH, YYYY.MM.PATCHbN (beta), or YYYY.MM.PATCH.devYYYYMMDDHHMM (dev) + # Examples: + # 2025.12.1 (stable) -> tags: 2025.12.1, 2025.12, stable, latest, beta, rc + # 2025.12.0b3 (beta) -> tags: 2025.12.0b3, beta, rc + # 2025.12.0.dev202511250240 -> tags: 2025.12.0.dev202511250240, dev + - name: Generate Docker metadata + id: meta + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + with: + images: ${{ matrix.registry }}/home-assistant + sep-tags: "," + tags: | + type=raw,value=${{ needs.init.outputs.version }},priority=9999 + type=raw,value=dev,enable=${{ contains(needs.init.outputs.version, 'd') }} + type=raw,value=beta,enable=${{ !contains(needs.init.outputs.version, 'd') }} + type=raw,value=rc,enable=${{ !contains(needs.init.outputs.version, 'd') }} + type=raw,value=stable,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }} + type=raw,value=latest,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }} + type=semver,pattern={{major}}.{{minor}},value=${{ needs.init.outputs.version }},enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }} - docker manifest create "${registry}/home-assistant:${tag_l}" \ - "${registry}/amd64-homeassistant:${tag_r}" \ - "${registry}/aarch64-homeassistant:${tag_r}" + - name: Push architecture images to DockerHub + if: matrix.registry == 'docker.io/homeassistant' + shell: bash + run: | + ARCHS=$(echo '${{ needs.init.outputs.architectures }}' | jq -r '.[]') + for arch in $ARCHS; do + echo "Pushing ${arch} to DockerHub..." + docker pull "ghcr.io/home-assistant/${arch}-homeassistant:${{ needs.init.outputs.version }}" + docker tag "ghcr.io/home-assistant/${arch}-homeassistant:${{ needs.init.outputs.version }}" \ + "docker.io/homeassistant/${arch}-homeassistant:${{ needs.init.outputs.version }}" + docker push "docker.io/homeassistant/${arch}-homeassistant:${{ needs.init.outputs.version }}" + cosign sign --yes "docker.io/homeassistant/${arch}-homeassistant:${{ needs.init.outputs.version }}" + done - docker manifest annotate "${registry}/home-assistant:${tag_l}" \ - "${registry}/amd64-homeassistant:${tag_r}" \ - --os linux --arch amd64 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@aa33708b10e362ff993539393ff100fa93ed6a27 # v3.7.1 - docker manifest annotate "${registry}/home-assistant:${tag_l}" \ - "${registry}/aarch64-homeassistant:${tag_r}" \ - --os linux --arch arm64 --variant=v8 + - name: Create and push multi-arch manifests + shell: bash + run: | + # Build list of architecture images dynamically + ARCHS=$(echo '${{ needs.init.outputs.architectures }}' | jq -r '.[]') + ARCH_IMAGES=() + for arch in $ARCHS; do + ARCH_IMAGES+=("${{ matrix.registry }}/${arch}-homeassistant:${{ needs.init.outputs.version }}") + done - docker manifest push --purge "${registry}/home-assistant:${tag_l}" - cosign sign --yes "${registry}/home-assistant:${tag_l}" - } + # Build list of all tags for single manifest creation + # Note: Using sep-tags=',' in metadata-action for easier parsing + TAG_ARGS=() + IFS=',' read -ra TAGS <<< "${{ steps.meta.outputs.tags }}" + for tag in "${TAGS[@]}"; do + TAG_ARGS+=("--tag" "${tag}") + done - function validate_image() { - local image=${1} - if ! cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp https://github.com/home-assistant/core/.* "${image}"; then - echo "Invalid signature!" - exit 1 - fi - } + # Create manifest with ALL tags in a single operation (much faster!) + echo "Creating multi-arch manifest with tags: ${TAGS[*]}" + docker buildx imagetools create "${TAG_ARGS[@]}" "${ARCH_IMAGES[@]}" - function push_dockerhub() { - local image=${1} - local tag=${2} + # Sign each tag separately (signing requires individual tag names) + echo "Signing all tags..." + for tag in "${TAGS[@]}"; do + echo "Signing ${tag}" + cosign sign --yes "${tag}" + done - docker tag "ghcr.io/home-assistant/${image}:${tag}" "docker.io/homeassistant/${image}:${tag}" - docker push "docker.io/homeassistant/${image}:${tag}" - cosign sign --yes "docker.io/homeassistant/${image}:${tag}" - } - - # Pull images from github container registry and verify signature - docker pull "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}" - docker pull --platform linux/arm64 "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}" - - validate_image "ghcr.io/home-assistant/amd64-homeassistant:${{ needs.init.outputs.version }}" - validate_image "ghcr.io/home-assistant/aarch64-homeassistant:${{ needs.init.outputs.version }}" - - if [[ "${{ matrix.registry }}" == "docker.io/homeassistant" ]]; then - # Upload images to dockerhub - push_dockerhub "amd64-homeassistant" "${{ needs.init.outputs.version }}" - push_dockerhub "aarch64-homeassistant" "${{ needs.init.outputs.version }}" - fi - - # Create version tag - create_manifest "${{ needs.init.outputs.version }}" "${{ needs.init.outputs.version }}" - - # Create general tags - if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then - create_manifest "dev" "${{ needs.init.outputs.version }}" - elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then - create_manifest "beta" "${{ needs.init.outputs.version }}" - create_manifest "rc" "${{ needs.init.outputs.version }}" - else - create_manifest "stable" "${{ needs.init.outputs.version }}" - create_manifest "latest" "${{ needs.init.outputs.version }}" - create_manifest "beta" "${{ needs.init.outputs.version }}" - create_manifest "rc" "${{ needs.init.outputs.version }}" - - # Create series version tag (e.g. 2021.6) - v="${{ needs.init.outputs.version }}" - create_manifest "${v%.*}" "${{ needs.init.outputs.version }}" - fi + echo "All manifests created and signed successfully" build_python: name: Build PyPi package