diff --git a/.github/workflows/docker-parallel-multiarch-build.yml b/.github/workflows/docker-parallel-multiarch-build.yml new file mode 100644 index 0000000..a7876a9 --- /dev/null +++ b/.github/workflows/docker-parallel-multiarch-build.yml @@ -0,0 +1,242 @@ +name: Build and push docker container (parallelized multi-arch build with native arm builders) + +on: + workflow_call: + inputs: + context: + default: "." + required: false + type: string + file: + required: false + type: string + + title: + required: true + type: string + description: + required: false + type: string + + app-version: + default: '0.1.0' + required: false + type: string + revision: + default: 1 + required: false + type: number + + platforms: + default: 'linux/amd64,linux/arm64' + required: false + type: string + buildkit-mount-caches: + description: "JSON object: `{id1: target1, id2: target2}`." + required: false + type: string + buildkit-mount-cache-ids-append-platform: + description: "If true, the workflow will append `-{{ platform }}` to the end of cache mount id (eg. `-linux/amd64`)." + default: false + type: boolean + + registry: + default: ghcr.io + required: false + type: string + image-name: + required: true + type: string + secrets: + registry-username: + required: true + registry-password: + required: true + +jobs: + vars: + name: Preprocess variables + runs-on: ubuntu-24.04 + steps: + - name: Preprocess variables + id: vars + run: | + platforms="${{ inputs.platforms }}" + { + echo "version=${{ inputs.app-version }}-r${{ inputs.revision }}" + echo "platforms<<9743a66f914cc249efca164485a19c5c" + echo "[\"${platforms//,/\",\"}\"]" + echo "9743a66f914cc249efca164485a19c5c" + } >> "$GITHUB_OUTPUT" + outputs: + version: ${{ steps.vars.outputs.version }} + platforms: ${{ steps.vars.outputs.platforms }} + + build: + name: Build container for ${{ matrix.platform }} + runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }} + needs: vars + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + strategy: + fail-fast: false + matrix: + platform: ${{ fromJSON(needs.vars.outputs.platforms) }} + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # Workaround: https://github.com/docker/build-push-action/issues/461 + - name: Setup Docker buildx + uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 + + - name: Log into registry ${{ inputs.registry }} + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ${{ inputs.registry }} + username: ${{ secrets.registry-username }} + password: ${{ secrets.registry-password }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: ${{ inputs.registry }}/${{ inputs.image-name }} + labels: | + org.opencontainers.image.title=${{ inputs.title }} + org.opencontainers.image.description=${{ inputs.description }} + + - name: Prepare variables + id: vars + run: | + platform='${{ matrix.platform }}' + image='${{ steps.meta.outputs.tags }}' + + [ '${{ inputs.buildkit-mount-cache-ids-append-platform }}' = 'true' ] && id_suffix='-${{ matrix.platform }}' + + { + echo "platform-pair=${platform//\//-}" + echo "platform-pair-un=${platform//\//_}" + echo "image=${image%%:*}" + + echo "cache-map<<9743a66f914cc249efca164485a19c5c" + jq --arg 'id_suffix' "$id_suffix" 'to_entries | map({(.key): {target: .value, id: "\(.key)\($id_suffix)"}}) | add' << 9743a66f914cc249efca164485a19c5c + ${{ inputs.buildkit-mount-caches }} + 9743a66f914cc249efca164485a19c5c + echo "9743a66f914cc249efca164485a19c5c" + + echo "cache-paths<<9743a66f914cc249efca164485a19c5c" + jq -r 'to_entries | .[].key' << 9743a66f914cc249efca164485a19c5c + ${{ inputs.buildkit-mount-caches }} + 9743a66f914cc249efca164485a19c5c + echo "9743a66f914cc249efca164485a19c5c" + } >> $GITHUB_OUTPUT + + cat $GITHUB_OUTPUT + + - name: Setup buildkit mount cache + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + if: steps.vars.outputs.cache-paths + with: + path: | + ${{ steps.vars.outputs.cache-paths }} + key: buildkit-mount-${{ inputs.title }}-${{ steps.vars.outputs.platform-pair }}-${{ github.sha }} + restore-keys: | + buildkit-mount-${{ inputs.title }}-${{ steps.vars.outputs.platform-pair }}- + + - name: Inject buildkit mount cache into docker + uses: reproducible-containers/buildkit-cache-dance@5b6db76d1da5c8b307d5d2e0706d266521b710de # v3.1.2 + if: steps.vars.outputs.cache-paths + with: + cache-map: | + ${{ steps.vars.outputs.cache-map }} + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 + with: + context: ${{ inputs.context }} + file: ${{ inputs.file }} + push: true + platforms: ${{ matrix.platform }} + outputs: type=image,"name=${{ steps.vars.outputs.image }}",push-by-digest=true,name-canonical=true,push=true + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=buildkit-layer-${{ inputs.title }}-${{ steps.vars.outputs.platform-pair }} + cache-to: type=gha,mode=max,scope=buildkit-layer-${{ inputs.title }}-${{ steps.vars.outputs.platform-pair }} + + # Workaround for https://github.com/actions/runner/pull/2477 + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build-and-push.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest as an artifact + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: digests-${{ inputs.title }}-${{ steps.vars.outputs.platform-pair }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + outputs: + image: ${{ steps.vars.outputs.image }} + # https://github.com/actions/runner/pull/2477 :< + # ${{ steps.vars.outputs.platform-pair-un }}-digest: ${{ steps.build-and-push.outputs.digest }} + + merge: + name: Merge images to multi-arch image + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + path: ${{ runner.temp }}/digests + pattern: digests-${{ inputs.title }}-* + merge-multiple: true + + # Workaround: https://github.com/docker/build-push-action/issues/461 + - name: Setup Docker buildx + uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 + + - name: Log into registry ${{ inputs.registry }} + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ${{ inputs.registry }} + username: ${{ secrets.registry-username }} + password: ${{ secrets.registry-password }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + with: + images: ${{ inputs.registry }}/${{ inputs.image-name }} + labels: | + org.opencontainers.image.title=${{ inputs.title }} + org.opencontainers.image.description=${{ inputs.description }} + tags: | + type=ref,event=branch,priority=1990 + type=ref,event=pr,priority=2999 + type=raw,value=latest,enable={{is_default_branch}} + type=raw,enable={{is_default_branch}},value=${{ needs.vars.outputs.version }},priority=1999 + type=raw,enable={{is_default_branch}},value=${{ inputs.app-version }},priority=1998 + type=semver,enable={{is_default_branch}},value=${{ inputs.app-version }},pattern={{major}}.{{minor}},priority=1997 + type=semver,enable={{is_default_branch}},value=${{ inputs.app-version }},pattern={{major}},priority=1996 + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ needs.build.outputs.image }}@sha256:%s ' *) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index a22b2c2..602d5a0 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -8,13 +8,10 @@ on: types: [opened, reopened, synchronize] workflow_dispatch: -env: - REGISTRY: ghcr.io - jobs: list_containers: name: List containers to build - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: matrix: ${{ steps.set-matrix.outputs.MATRIX }} steps: @@ -46,7 +43,6 @@ jobs: build: name: "Build container: ${{ matrix.container }}" - runs-on: ubuntu-latest needs: list_containers permissions: contents: read @@ -59,68 +55,16 @@ jobs: fail-fast: false matrix: include: ${{ fromJSON(needs.list_containers.outputs.matrix) }} - - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Generate properties - id: props - run: | - PLATFORMS_DEFAULT="linux/amd64,linux/arm64" - APP_VERSION_DEFAULT="0.1.0" - REVISION_DEFAULT="1" - - PLATFORMS="${{ matrix.platforms }}" - APP_VERSION="${{ matrix.app_version }}" - REVISION="${{ matrix.revision }}" - - platforms="${PLATFORMS:-"${PLATFORMS_DEFAULT}"}" - app_version="${APP_VERSION:-"${APP_VERSION_DEFAULT}"}" - revision="${REVISION:-"${REVISION_DEFAULT}"}" - { - echo "platforms=$platforms" - echo "app_version=$app_version" - echo "revision=$revision" - echo "version=${app_version}-r${revision}" - } >> "$GITHUB_OUTPUT" - - # Workaround: https://github.com/docker/build-push-action/issues/461 - - name: Setup Docker buildx - uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 - - - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 - with: - images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ matrix.container }} - labels: | - org.opencontainers.image.title=${{ matrix.container }} - org.opencontainers.image.description=${{ matrix.description }} - tags: | - type=ref,event=branch,priority=1990 - type=ref,event=pr,priority=2999 - type=raw,value=latest,enable={{is_default_branch}} - type=raw,enable={{is_default_branch}},value=${{ steps.props.outputs.version }},priority=1999 - type=raw,enable={{is_default_branch}},value=${{ steps.props.outputs.app_version }},priority=1998 - type=semver,enable={{is_default_branch}},value=${{ steps.props.outputs.app_version }},pattern={{major}}.{{minor}},priority=1997 - type=semver,enable={{is_default_branch}},value=${{ steps.props.outputs.app_version }},pattern={{major}},priority=1996 - - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 - with: - context: containers/${{ matrix.container }} - push: true - platforms: ${{ steps.props.outputs.platforms }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + uses: ./.github/workflows/docker-parallel-multiarch-build.yml + with: + context: containers/${{ matrix.container }} + title: ${{ matrix.container }} + description: ${{ matrix.description }} + app-version: ${{ matrix.app_version }} + revision: ${{ fromJSON(matrix.revision) }} + platforms: ${{ matrix.platforms }} + registry: ghcr.io + image-name: ${{ github.repository }}/${{ matrix.container }} + secrets: + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }}