From f7fe3f2a34ed9cbed3afb8eb14730e018e13c0a3 Mon Sep 17 00:00:00 2001 From: Gabriele Bartolini Date: Sat, 19 Sep 2026 09:46:32 +0200 Subject: [PATCH 1/4] fix: build PostGIS independently so trunk breakage can't block the pipeline PostGIS periodically fails to build against PostgreSQL trunk. Since it was built in the same `docker buildx bake` invocation as minimal/standard (see blocking minimal/standard image publishing and, transitively, E2E testing, since call-reusable-e2e needs build-pg. - docker-bake.hcl: move `postgis` out of the "default" matrix into its own standalone target, so a plain `docker buildx bake` (or `bake --push`) still only builds minimal/standard, while `docker buildx bake postgis` builds it explicitly. - reusable-build.yml: generalize the `minimal_tags`/`standard_tags` inputs into a single `targets` input (one ":" entry per line), so the same reusable workflow can build an arbitrary set of targets. Also pass the parsed target names to `docker/bake-action`'s `targets` input, since it only builds bake's "default" group otherwise. Add a `best_effort` input that sets `continue-on-error` on this workflow's own build/merge jobs -- GitHub Actions doesn't allow `continue-on-error` directly on a job that calls a reusable workflow via `uses:`, so the tolerance has to live inside the reusable workflow itself. - build.yml, build-commitfest.yml, continuous-delivery.yml: add a `build-postgis` job, parallel to (and independent of) `build-pg`, that calls reusable-build.yml with `targets: postgis:...` and `best_effort: true`. Nothing depends on `build-postgis`, so a PostGIS failure no longer affects minimal/standard or E2E. - README.md: document building the `postgis` target explicitly. Closes #158 Assisted-by: Claude Signed-off-by: Gabriele Bartolini --- .github/workflows/build-commitfest.yml | 29 +++++++++- .github/workflows/build.yml | 29 +++++++++- .github/workflows/continuous-delivery.yml | 27 ++++++++- .github/workflows/reusable-build.yml | 63 ++++++++++++++------- .github/workflows/reusable-e2e.yml | 2 +- README.md | 11 +++- docker-bake.hcl | 69 +++++++++++++++++++++-- 7 files changed, 194 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build-commitfest.yml b/.github/workflows/build-commitfest.yml index 7039295..b4f1de5 100644 --- a/.github/workflows/build-commitfest.yml +++ b/.github/workflows/build-commitfest.yml @@ -26,6 +26,7 @@ jobs: pg_branch: ${{ env.BRANCH }} minimal_tag: ${{ env.registry }}/postgresql-trunk:${{ env.PG_MAJOR }}-minimal-${{ env.TAG }} standard_tag: ${{ env.registry }}/postgresql-trunk:${{ env.PG_MAJOR }}-standard-${{ env.TAG }} + postgis_tag: ${{ env.registry }}/postgresql-trunk:${{ env.PG_MAJOR }}-postgis-${{ env.TAG }} steps: - name: Checkout Code uses: actions/checkout@v7 @@ -69,23 +70,45 @@ jobs: pg_branch: ${{ needs.prepare.outputs.pg_branch }} pg_major: ${{ needs.prepare.outputs.pg_major }} revision: ${{ github.sha }} - minimal_tags: ${{ needs.prepare.outputs.minimal_tag }} - standard_tags: ${{ needs.prepare.outputs.standard_tag }} + targets: | + minimal:${{ needs.prepare.outputs.minimal_tag }} + standard:${{ needs.prepare.outputs.standard_tag }} + + # PostGIS periodically fails to build against PostgreSQL trunk. Kept as its + # own best-effort job so a break there doesn't fail this run or block the + # minimal/standard images above. See #158. + build-postgis: + name: Build PostGIS image for the patch (best-effort) + needs: prepare + uses: ./.github/workflows/reusable-build.yml + permissions: + contents: read + packages: write + with: + pg_repo: https://github.com/postgresql-cfbot/postgresql.git + pg_branch: ${{ needs.prepare.outputs.pg_branch }} + pg_major: ${{ needs.prepare.outputs.pg_major }} + revision: ${{ github.sha }} + targets: | + postgis:${{ needs.prepare.outputs.postgis_tag }} + best_effort: true generate-summary: name: Commitfest Image Build summary runs-on: ubuntu-26.04 needs: - build-pg + - build-postgis steps: - name: Output summary env: INPUT_PATCH_ID: ${{ github.event.inputs.patch_id }} BUILD_PG_IMAGES: ${{ needs.build-pg.outputs.images }} + BUILD_POSTGIS_IMAGES: ${{ needs.build-postgis.outputs.images }} run: | commitFestPatchID="${INPUT_PATCH_ID}" commitFestURL="https://commitfest.postgresql.org/patch/${commitFestPatchID}" - images="${BUILD_PG_IMAGES}" + images="${BUILD_PG_IMAGES} ${BUILD_POSTGIS_IMAGES}" images_list="$(echo $images | tr ' ' '\n' | sed 's/^/https:\/\//')" minimalImage="$(echo $images | tr ' ' '\n' | grep minimal)" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9c8f5b4..f549461 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,7 @@ jobs: pg_major: ${{ env.PG_MAJOR }} minimal_tag: ${{ env.registry }}/postgresql-trunk:${{ env.PG_MAJOR }}-minimal-${{ github.run_number }} standard_tag: ${{ env.registry }}/postgresql-trunk:${{ env.PG_MAJOR }}-standard-${{ github.run_number }} + postgis_tag: ${{ env.registry }}/postgresql-trunk:${{ env.PG_MAJOR }}-postgis-${{ github.run_number }} steps: - name: Checkout Code uses: actions/checkout@v7 @@ -64,8 +65,28 @@ jobs: pg_branch: ${{ github.event.inputs.pg_branch }} pg_major: ${{ needs.prepare.outputs.pg_major }} revision: ${{ github.sha }} - minimal_tags: ${{ needs.prepare.outputs.minimal_tag }} - standard_tags: ${{ needs.prepare.outputs.standard_tag }} + targets: | + minimal:${{ needs.prepare.outputs.minimal_tag }} + standard:${{ needs.prepare.outputs.standard_tag }} + + # PostGIS periodically fails to build against PostgreSQL trunk. Kept as its + # own best-effort job so a break there doesn't fail this run or block the + # minimal/standard images above. See #158. + build-postgis: + name: Build PostGIS image from sources (best-effort) + needs: prepare + uses: ./.github/workflows/reusable-build.yml + permissions: + contents: read + packages: write + with: + pg_repo: ${{ github.event.inputs.pg_repo }} + pg_branch: ${{ github.event.inputs.pg_branch }} + pg_major: ${{ needs.prepare.outputs.pg_major }} + revision: ${{ github.sha }} + targets: | + postgis:${{ needs.prepare.outputs.postgis_tag }} + best_effort: true generate-summary: name: PostgreSQL Image Build summary @@ -73,14 +94,16 @@ jobs: needs: - prepare - build-pg + - build-postgis steps: - name: Output summary env: BUILD_PG_MAJOR: ${{ needs.prepare.outputs.pg_major }} BUILD_PG_IMAGES: ${{ needs.build-pg.outputs.images }} + BUILD_POSTGIS_IMAGES: ${{ needs.build-postgis.outputs.images }} run: | pg_major="${BUILD_PG_MAJOR}" - images="${BUILD_PG_IMAGES}" + images="${BUILD_PG_IMAGES} ${BUILD_POSTGIS_IMAGES}" images_list="$(echo $images | tr ' ' '\n' | sed 's/^/https:\/\//')" minimalImage="$(echo $images | tr ' ' '\n' | grep minimal)" diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml index 147741a..cd2931c 100644 --- a/.github/workflows/continuous-delivery.yml +++ b/.github/workflows/continuous-delivery.yml @@ -37,6 +37,7 @@ jobs: barman_plugin: ${{ env.BARMAN_PLUGIN }} minimal_tags: ${{ env.MINIMAL_TAGS }} standard_tags: ${{ env.STANDARD_TAGS }} + postgis_tags: ${{ env.POSTGIS_TAGS }} steps: - name: Checkout Code uses: actions/checkout@v7 @@ -74,7 +75,7 @@ jobs: run: | registry="ghcr.io/${{ github.repository_owner }}/postgresql-trunk" timestamp="$(date -u +%Y%m%d%H%M)" - for tgt in MINIMAL STANDARD; do + for tgt in MINIMAL STANDARD POSTGIS; do lower="${tgt,,}" echo "${tgt}_TAGS=${registry}:${PG_MAJOR}-${lower}-${DISTRO},${registry}:${PG_MAJOR}-${timestamp}-${lower}-${DISTRO}" >> $GITHUB_ENV done @@ -89,12 +90,32 @@ jobs: with: pg_major: ${{ needs.prepare.outputs.pg_major }} revision: ${{ github.sha }} - minimal_tags: ${{ needs.prepare.outputs.minimal_tags }} - standard_tags: ${{ needs.prepare.outputs.standard_tags }} + targets: | + minimal:${{ needs.prepare.outputs.minimal_tags }} + standard:${{ needs.prepare.outputs.standard_tags }} # On pull_request runs, only build each arch to validate the Dockerfile # compiles — don't push anything or publish a multi-arch manifest. push: ${{ github.event_name != 'pull_request' }} + # PostGIS periodically fails to build against PostgreSQL trunk. It's built + # as its own job, independent from build-pg, so a break there (best_effort + # keeps it from failing this run) doesn't block minimal/standard image + # publishing or the E2E job below, which only depends on build-pg. See #158. + build-postgis: + name: Build the Trunk of PostgreSQL (PostGIS, best-effort) + needs: prepare + uses: ./.github/workflows/reusable-build.yml + permissions: + contents: read + packages: write + with: + pg_major: ${{ needs.prepare.outputs.pg_major }} + revision: ${{ github.sha }} + targets: | + postgis:${{ needs.prepare.outputs.postgis_tags }} + push: ${{ github.event_name != 'pull_request' }} + best_effort: true + select-pg-image: name: Select the image built for E2E if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 6d00993..62acc10 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -1,17 +1,21 @@ name: Reusable Multi-Arch PostgreSQL Build -# Builds the minimal/standard images for linux/amd64 and linux/arm64 +# Builds one or more docker-bake.hcl targets for linux/amd64 and linux/arm64 # natively (one arch per job, no QEMU emulation) and publishes a multi-arch # manifest list for each requested tag by merging the two per-arch images # with `docker buildx imagetools create`. # -# PostGIS is currently excluded from the default docker-bake.hcl matrix -# (see #158), so it isn't wired through here either. -# # Compiling PostgreSQL from source is CPU-bound, so cross-building linux/arm64 # under QEMU emulation on an amd64 runner would be dramatically slower than a # native build. This workflow instead relies on GitHub-hosted native arm64 # runners (`ubuntu-24.04-arm`) for the arm64 leg. +# +# `best_effort: true` lets a caller build a target (e.g. postgis, which +# periodically fails against PostgreSQL trunk -- see #158) without a failure +# here failing the calling workflow run. This has to live inside this +# workflow's own jobs, rather than as `continue-on-error` on the caller's job, +# because GitHub Actions doesn't allow `continue-on-error` on a job that calls +# a reusable workflow via `uses:`. on: workflow_call: @@ -34,12 +38,8 @@ on: description: "Value used for the org.opencontainers.image.revision label/annotation" required: true type: string - minimal_tags: - description: "Comma-separated list of final (arch-less) tags for the minimal image" - required: true - type: string - standard_tags: - description: "Comma-separated list of final (arch-less) tags for the standard image" + targets: + description: "One ':' entry per line, e.g. 'minimal:tag1,tag2'" required: true type: string push: @@ -47,6 +47,11 @@ on: required: false type: boolean default: true + best_effort: + description: "When true, a build/publish failure doesn't fail this workflow (and so doesn't fail a caller job that just `uses:` it -- GitHub Actions doesn't allow `continue-on-error` on a job that calls a reusable workflow). Use for targets that are allowed to be broken, like postgis (#158)." + required: false + type: boolean + default: false outputs: images: description: "Newline-separated list of the final multi-arch images that were built and pushed (empty when push=false)" @@ -62,6 +67,7 @@ permissions: {} jobs: build: name: Build (linux/${{ matrix.arch }}) + continue-on-error: ${{ inputs.best_effort }} strategy: fail-fast: false matrix: @@ -71,7 +77,7 @@ jobs: runs-on: >- ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' - || (github.repository_owner == 'cloudnative-pg' && 'ubuntu-latest-16-cores' || 'ubuntu-24.04') + || (github.repository_owner == 'cloudnative-pg' && 'ubuntu-latest-16-cores' || 'ubuntu-26.04') }} permissions: contents: read @@ -90,30 +96,42 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - # For each image flavor, each tag is appended with - # an "-$arch" suffix (e.g. ...:19-minimal-trixie-arm64). + # For each image flavor, each tag is appended with an "-$arch" suffix + # (e.g. ...:19-minimal-trixie-arm64). `docker/bake-action` only builds the + # "default" group (docker-bake.hcl's `target "default"` matrix) unless + # told otherwise, so also collect the bake target names to build + # explicitly -- this is what lets a caller build just "postgis", which is + # intentionally kept out of the "default" group (see docker-bake.hcl). - name: Compute bake overrides id: overrides env: ARCH: ${{ matrix.arch }} PG_REPO: ${{ inputs.pg_repo }} PG_BRANCH: ${{ inputs.pg_branch }} - MINIMAL_TAGS: ${{ inputs.minimal_tags }} - STANDARD_TAGS: ${{ inputs.standard_tags }} + TARGETS: ${{ inputs.targets }} run: | + { + echo "targets<> "$GITHUB_OUTPUT" { echo "set<> "$GITHUB_OUTPUT" @@ -124,6 +142,7 @@ jobs: revision: ${{ inputs.revision }} pgMajor: ${{ inputs.pg_major }} with: + targets: ${{ steps.overrides.outputs.targets }} set: ${{ steps.overrides.outputs.set }} push: ${{ inputs.push }} @@ -131,6 +150,7 @@ jobs: name: Publish multi-arch manifests if: inputs.push needs: build + continue-on-error: ${{ inputs.best_effort }} runs-on: ubuntu-26.04 permissions: contents: read @@ -151,11 +171,12 @@ jobs: - name: Create and push multi-arch manifests id: merge env: - MINIMAL_TAGS: ${{ inputs.minimal_tags }} - STANDARD_TAGS: ${{ inputs.standard_tags }} + TARGETS: ${{ inputs.targets }} run: | images=() - for tags in "$MINIMAL_TAGS" "$STANDARD_TAGS"; do + while IFS= read -r target_tags; do + [[ -z "$target_tags" ]] && continue + tags="${target_tags#*:}" IFS=',' read -ra tag_list <<< "$tags" for tag in "${tag_list[@]}"; do docker buildx imagetools create \ @@ -164,7 +185,7 @@ jobs: "$tag-arm64" images+=("$tag") done - done + done <<< "$TARGETS" { echo 'images< Date: Wed, 30 Sep 2026 09:57:54 +0800 Subject: [PATCH 2/4] ci: bump the arm runner to ubuntu-26.04-arm Signed-off-by: Tao Li --- .github/workflows/reusable-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index 62acc10..b0aa668 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -8,7 +8,7 @@ name: Reusable Multi-Arch PostgreSQL Build # Compiling PostgreSQL from source is CPU-bound, so cross-building linux/arm64 # under QEMU emulation on an amd64 runner would be dramatically slower than a # native build. This workflow instead relies on GitHub-hosted native arm64 -# runners (`ubuntu-24.04-arm`) for the arm64 leg. +# runners (`ubuntu-26.04-arm`) for the arm64 leg. # # `best_effort: true` lets a caller build a target (e.g. postgis, which # periodically fails against PostgreSQL trunk -- see #158) without a failure @@ -76,7 +76,7 @@ jobs: - arm64 runs-on: >- ${{ - matrix.arch == 'arm64' && 'ubuntu-24.04-arm' + matrix.arch == 'arm64' && 'ubuntu-26.04-arm' || (github.repository_owner == 'cloudnative-pg' && 'ubuntu-latest-16-cores' || 'ubuntu-26.04') }} permissions: From ca2788ed33c142b7cc8f349e172c042545ae9345 Mon Sep 17 00:00:00 2001 From: Tao Li Date: Wed, 30 Sep 2026 10:58:17 +0800 Subject: [PATCH 3/4] ci: report failure if continue-on-error Signed-off-by: Tao Li --- .github/workflows/reusable-build.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index b0aa668..6295495 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -146,6 +146,18 @@ jobs: set: ${{ steps.overrides.outputs.set }} push: ${{ inputs.push }} + # continue-on-error reports this job as successful even when it failed, so + # surface the failure explicitly (annotation + run summary). + - name: Flag ignored best-effort build failure + if: failure() && inputs.best_effort + env: + ARCH: ${{ matrix.arch }} + TARGETS: ${{ inputs.targets }} + run: | + msg="Best-effort build failed for linux/${ARCH} (ignored, run stays green): ${TARGETS//$'\n'/ }" + echo "::warning title=Best-effort build failed::${msg}" + echo "### :warning: ${msg}" >> "$GITHUB_STEP_SUMMARY" + merge: name: Publish multi-arch manifests if: inputs.push @@ -191,3 +203,12 @@ jobs: printf '%s\n' "${images[@]}" echo 'EOF' } >> "$GITHUB_OUTPUT" + + - name: Flag ignored best-effort publish failure + if: failure() && inputs.best_effort + env: + TARGETS: ${{ inputs.targets }} + run: | + msg="Best-effort multi-arch manifest publish failed (ignored, run stays green): ${TARGETS//$'\n'/ }" + echo "::warning title=Best-effort publish failed::${msg}" + echo "### :warning: ${msg}" >> "$GITHUB_STEP_SUMMARY" From 0753a0492f110d494f46816314d0dd724c303849 Mon Sep 17 00:00:00 2001 From: Tao Li Date: Wed, 30 Sep 2026 11:38:10 +0800 Subject: [PATCH 4/4] ci: use default group for postgres build target `postgis` and target `default` only different in tgt, remove the duplicate and keep only one target definitation. use default group for postgres target, so `docker buildx bake --push` can build the postgres minimal and standard image without any change. `docker buildx bake postgis --push` can still bake postgis image only. Signed-off-by: Tao Li --- docker-bake.hcl | 79 ++++++++----------------------------------------- 1 file changed, 12 insertions(+), 67 deletions(-) diff --git a/docker-bake.hcl b/docker-bake.hcl index 3452aa1..080cf67 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -53,77 +53,22 @@ description = "PostgreSQL Trunk Containers for CloudNativePG operator" authors = "The CloudNativePG Contributors" url = "https://github.com/cloudnative-pg/postgres-trunk-containers" -target "default" { - matrix = { - tgt = [ - "minimal", - "standard" - ] - pgMajor = ["${pgMajor}"] - base = ["debian:trixie-slim"] - } - - platforms = platforms - - dockerfile = "Dockerfile" - name = "${tgt}" - tags = [ - "${fullname}:${pgMajor}-${tgt}-${distroVersion(base)}", - "${fullname}:${pgMajor}-${formatdate("YYYYMMDDhhmm", now)}-${tgt}-${distroVersion(base)}" - ] - context = "." - target = "${tgt}" - args = { - PG_MAJOR = "${pgMajor}" - BASE = "${base}" - } +// Targets built by a plain `docker buildx bake` (the "default" group). +postgresTgt = ["minimal", "standard"] +// PostGIS periodically fails to build against PostgreSQL trunk, so it's kept +// out of the "default" group: CI builds it as an independent, best-effort +// step (`docker buildx bake postgis`) so a break doesn't block +// minimal/standard or E2E -- see reusable-build.yml and +// https://github.com/cloudnative-pg/postgres-trunk-containers/issues/158 +postgisTgt = ["postgis"] - output = [ - "type=image,registry.insecure=${insecure}", - ] - attest = [ - "type=provenance,mode=max", - "type=sbom" - ] - annotations = [ - "index,manifest:org.opencontainers.image.created=${now}", - "index,manifest:org.opencontainers.image.url=${url}", - "index,manifest:org.opencontainers.image.source=${url}", - "index,manifest:org.opencontainers.image.version=${pgMajor}", - "index,manifest:org.opencontainers.image.revision=${revision}", - "index,manifest:org.opencontainers.image.vendor=${authors}", - "index,manifest:org.opencontainers.image.title=CloudNativePG PostgreSQL ${pgMajor} ${tgt}", - "index,manifest:org.opencontainers.image.description=A ${tgt} PostgreSQL ${pgMajor} container image", - "index,manifest:org.opencontainers.image.documentation=${url}", - "index,manifest:org.opencontainers.image.authors=${authors}", - "index,manifest:org.opencontainers.image.licenses=Apache-2.0", - "index,manifest:org.opencontainers.image.base.name=docker.io/library/${tag(base)}", - ] - labels = { - "org.opencontainers.image.created" = "${now}", - "org.opencontainers.image.url" = "${url}", - "org.opencontainers.image.source" = "${url}", - "org.opencontainers.image.version" = "${pgMajor}", - "org.opencontainers.image.revision" = "${revision}", - "org.opencontainers.image.vendor" = "${authors}", - "org.opencontainers.image.title" = "CloudNativePG PostgreSQL ${pgMajor} ${tgt}", - "org.opencontainers.image.description" = "A ${tgt} PostgreSQL ${pgMajor} container image", - "org.opencontainers.image.documentation" = "${url}", - "org.opencontainers.image.authors" = "${authors}", - "org.opencontainers.image.licenses" = "Apache-2.0" - "org.opencontainers.image.base.name" = "docker.io/library/debian:${tag(base)}" - } +group "default" { + targets = postgresTgt } -// PostGIS periodically fails to build against PostgreSQL trunk. It's kept as -// a standalone target outside the "default" group (a plain `docker buildx -// bake` or `bake --push` only builds minimal/standard) so CI can build it as -// an independent, best-effort step that doesn't block minimal/standard or -// E2E when it breaks -- see reusable-build.yml and -// https://github.com/cloudnative-pg/postgres-trunk-containers/issues/158 -target "postgis" { +target "image" { matrix = { - tgt = ["postgis"] + tgt = concat(postgresTgt, postgisTgt) pgMajor = ["${pgMajor}"] base = ["debian:trixie-slim"] }