From 95811507a688fabb5f7c2e62e90522d04a0016f5 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 6 May 2026 15:28:13 +0200 Subject: [PATCH 1/2] Push images by digest to skip per-arch tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the per-arch tag scaffolding (`develop-amd64`, `develop-arm64`, `develop-amd64-slim`, `develop-arm64-slim`, plus the versioned variants) with buildx push-by-digest. Each per-arch image is uploaded to Docker Hub addressed only by content digest; the digest is then handed to `docker buildx imagetools create` to assemble the final multi-arch manifest. Net effect on Docker Hub: only the multi-arch tags (`develop`, `develop-slim`, ``, `-slim`, `latest`, `latest-slim`) are ever published — no per-arch tags are created at any point. The base image build now uses a multi-output buildx invocation (`type=docker` for daemon load + `type=image,push-by-digest=true,name-canonical=true,push=true` for the registry push). slim-toolkit still operates on the daemon image, then a small `Dockerfile.passthrough` (`FROM \${SRC}`) is built through the docker driver builder so its `--build-context docker-image://` reference can resolve from the host daemon, exporting by digest only. Digests cross job boundaries via `actions/upload-artifact` + `actions/download-artifact`. PR builds are unchanged in shape: build + scan + slim run, nothing is pushed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/postgresql.yml | 133 +++++++++++++++++++++---------- Dockerfile.passthrough | 2 + 2 files changed, 92 insertions(+), 43 deletions(-) create mode 100644 Dockerfile.passthrough diff --git a/.github/workflows/postgresql.yml b/.github/workflows/postgresql.yml index 388494a..e948db6 100644 --- a/.github/workflows/postgresql.yml +++ b/.github/workflows/postgresql.yml @@ -121,30 +121,38 @@ jobs: fi fi - if [[ "$IS_VERSIONED" == "true" ]]; then - BASE_TAG="${IMAGE_NAME}:${VERSION}-${{ matrix.arch }}" - SLIM_TAG="${IMAGE_NAME}:${VERSION}-${{ matrix.arch }}-slim" - elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then - BASE_TAG="${IMAGE_NAME}:develop-${{ matrix.arch }}" - SLIM_TAG="${IMAGE_NAME}:develop-${{ matrix.arch }}-slim" - else - BASE_TAG="${IMAGE_NAME}:pr-${{ github.event.pull_request.number || github.run_number }}-${{ matrix.arch }}" - SLIM_TAG="${IMAGE_NAME}:pr-${{ github.event.pull_request.number || github.run_number }}-${{ matrix.arch }}-slim" - fi - echo "version=$VERSION" >> "$GITHUB_OUTPUT" echo "is_versioned=$IS_VERSIONED" >> "$GITHUB_OUTPUT" - echo "base_tag=$BASE_TAG" >> "$GITHUB_OUTPUT" - echo "slim_tag=$SLIM_TAG" >> "$GITHUB_OUTPUT" - - name: Build base image + - name: Build base image (PR — load only) + if: github.event_name == 'pull_request' + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + platforms: ${{ matrix.platform }} + provenance: false + sbom: false + outputs: type=docker,name=local-ci-image:latest + build-args: | + GIT_COMMIT=${{ github.sha }} + cache-from: type=gha,scope=postgresql-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=postgresql-${{ matrix.arch }} + no-cache-filters: | + trimmed + trimmed-all + + - name: Build base image (load + push by digest) + id: build_base + if: github.event_name != 'pull_request' uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 with: context: . platforms: ${{ matrix.platform }} - load: true - push: false - tags: ${{ steps.meta.outputs.base_tag }} + provenance: false + sbom: false + outputs: | + type=docker,name=local-ci-image:latest + type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true build-args: | GIT_COMMIT=${{ github.sha }} cache-from: type=gha,scope=postgresql-${{ matrix.arch }} @@ -157,7 +165,7 @@ jobs: uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0 id: anchore-scan with: - image: ${{ steps.meta.outputs.base_tag }} + image: local-ci-image:latest fail-build: false severity-cutoff: critical @@ -177,14 +185,42 @@ jobs: shell: bash run: | chmod +x ./slim-image.sh - ./slim-image.sh "${{ steps.meta.outputs.base_tag }}" "${{ steps.meta.outputs.slim_tag }}" "${{ matrix.arch }}" + ./slim-image.sh local-ci-image:latest local-ci-image-slim:latest "${{ matrix.arch }}" - - name: Push arch images + - name: Push slim image by digest + id: push_slim + if: github.event_name != 'pull_request' + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + builder: default + context: . + file: ./Dockerfile.passthrough + platforms: ${{ matrix.platform }} + provenance: false + sbom: false + build-args: | + SRC=local-ci-image-slim:latest + build-contexts: | + local-ci-image-slim:latest=docker-image://local-ci-image-slim:latest + outputs: | + type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + + - name: Save digests if: github.event_name != 'pull_request' shell: bash run: | - docker push "${{ steps.meta.outputs.base_tag }}" - docker push "${{ steps.meta.outputs.slim_tag }}" + mkdir -p /tmp/digests + echo "${{ steps.build_base.outputs.digest }}" > "/tmp/digests/base-${{ matrix.arch }}" + echo "${{ steps.push_slim.outputs.digest }}" > "/tmp/digests/slim-${{ matrix.arch }}" + + - name: Upload digests + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: digests-${{ matrix.arch }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 create_manifests: name: Create manifests @@ -193,40 +229,51 @@ jobs: runs-on: ubuntu-latest steps: + - name: Set up Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + - name: Log in to Docker Hub uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets._TEMP_DOCKERHUB_USER }} password: ${{ secrets._TEMP_DOCKERHUB_PASSWORD }} + - name: Download digests + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + - name: Create manifest tags shell: bash run: | + set -euo pipefail + VERSION="${{ needs.build_arch_images.outputs.version }}" IS_VERSIONED="${{ needs.build_arch_images.outputs.is_versioned }}" + BASE_AMD64="${IMAGE_NAME}@$(cat /tmp/digests/base-amd64)" + BASE_ARM64="${IMAGE_NAME}@$(cat /tmp/digests/base-arm64)" + SLIM_AMD64="${IMAGE_NAME}@$(cat /tmp/digests/slim-amd64)" + SLIM_ARM64="${IMAGE_NAME}@$(cat /tmp/digests/slim-arm64)" + if [[ "$IS_VERSIONED" == "true" ]]; then - TAG="${IMAGE_NAME}:${VERSION}" - TAG_LATEST="${IMAGE_NAME}:latest" - TAG_AMD64="${IMAGE_NAME}:${VERSION}-amd64" - TAG_ARM64="${IMAGE_NAME}:${VERSION}-arm64" - - TAG_SLIM="${IMAGE_NAME}:${VERSION}-slim" - TAG_SLIM_LATEST="${IMAGE_NAME}:latest-slim" - TAG_SLIM_AMD64="${IMAGE_NAME}:${VERSION}-amd64-slim" - TAG_SLIM_ARM64="${IMAGE_NAME}:${VERSION}-arm64-slim" - - docker buildx imagetools create -t "$TAG" -t "$TAG_LATEST" "$TAG_AMD64" "$TAG_ARM64" - docker buildx imagetools create -t "$TAG_SLIM" -t "$TAG_SLIM_LATEST" "$TAG_SLIM_AMD64" "$TAG_SLIM_ARM64" + docker buildx imagetools create \ + -t "${IMAGE_NAME}:${VERSION}" \ + -t "${IMAGE_NAME}:latest" \ + "$BASE_AMD64" "$BASE_ARM64" + + docker buildx imagetools create \ + -t "${IMAGE_NAME}:${VERSION}-slim" \ + -t "${IMAGE_NAME}:latest-slim" \ + "$SLIM_AMD64" "$SLIM_ARM64" else - TAG="${IMAGE_NAME}:develop" - TAG_AMD64="${IMAGE_NAME}:develop-amd64" - TAG_ARM64="${IMAGE_NAME}:develop-arm64" - - TAG_SLIM="${IMAGE_NAME}:develop-slim" - TAG_SLIM_AMD64="${IMAGE_NAME}:develop-amd64-slim" - TAG_SLIM_ARM64="${IMAGE_NAME}:develop-arm64-slim" + docker buildx imagetools create \ + -t "${IMAGE_NAME}:develop" \ + "$BASE_AMD64" "$BASE_ARM64" - docker buildx imagetools create -t "$TAG" "$TAG_AMD64" "$TAG_ARM64" - docker buildx imagetools create -t "$TAG_SLIM" "$TAG_SLIM_AMD64" "$TAG_SLIM_ARM64" + docker buildx imagetools create \ + -t "${IMAGE_NAME}:develop-slim" \ + "$SLIM_AMD64" "$SLIM_ARM64" fi diff --git a/Dockerfile.passthrough b/Dockerfile.passthrough new file mode 100644 index 0000000..5272f83 --- /dev/null +++ b/Dockerfile.passthrough @@ -0,0 +1,2 @@ +ARG SRC +FROM ${SRC} From 3ee86b56e7caafadb9f90852dec558db1059f686 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 6 May 2026 16:32:12 +0200 Subject: [PATCH 2/2] Document Dockerfile.passthrough purpose --- Dockerfile.passthrough | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Dockerfile.passthrough b/Dockerfile.passthrough index 5272f83..bc7a137 100644 --- a/Dockerfile.passthrough +++ b/Dockerfile.passthrough @@ -1,2 +1,9 @@ +# Used by .github/workflows/postgresql.yml to push the slimmed image to +# Docker Hub by digest only (no per-arch tag). Buildx's push-by-digest +# exporter requires a buildx-driven build, so this passthrough Dockerfile +# wraps the daemon-loaded slim image (passed via SRC + --build-context +# docker-image://) so it can be re-emitted by digest. Not used by any +# local build. + ARG SRC FROM ${SRC}