diff --git a/.github/workflows/_publish_image_reusable.yml b/.github/workflows/_publish_image_reusable.yml index 2424136..af5e567 100644 --- a/.github/workflows/_publish_image_reusable.yml +++ b/.github/workflows/_publish_image_reusable.yml @@ -19,6 +19,11 @@ on: description: "source branch name" required: true type: string + validation_only: + description: "build without image or cache writes" + required: false + default: false + type: boolean build_matrix_json: description: "build matrix JSON array" required: true @@ -74,6 +79,7 @@ jobs: source_sha: ${{ steps.prepare.outputs.source_sha }} checkout_ref: ${{ steps.prepare.outputs.checkout_ref }} version_tag: ${{ steps.prepare.outputs.version_tag }} + publish_images: ${{ steps.prepare.outputs.publish_images }} steps: - name: Resolve mode and source ref id: prepare @@ -82,6 +88,7 @@ jobs: REPOSITORY_URL: ${{ inputs.repository_url }} BRANCH: ${{ inputs.branch }} ENABLE_HASH_GATE: ${{ inputs.enable_hash_gate }} + VALIDATION_ONLY: ${{ inputs.validation_only }} LAST_HASH_VALUE: ${{ inputs.last_hash_value }} run: | set -euo pipefail @@ -98,6 +105,10 @@ jobs: fi checkout_ref="$source_sha" + publish_images="true" + if [ "$VALIDATION_ONLY" = "true" ]; then + publish_images="false" + fi if [ "$MODE" = "latest" ]; then version_tag="latest" @@ -118,6 +129,7 @@ jobs: echo "source_sha=$source_sha" echo "checkout_ref=$checkout_ref" echo "version_tag=$version_tag" + echo "publish_images=$publish_images" echo "need_update=$need_update" } >> "$GITHUB_OUTPUT" @@ -142,6 +154,7 @@ jobs: PLATFORMS_LATEST: ${{ matrix.platforms_latest }} PLATFORMS_RELEASE: ${{ matrix.platforms_release }} REGISTRY_CACHE_IMAGE: ${{ matrix.registry_cache_image || '' }} + PUBLISH_IMAGES: ${{ needs.prepare.outputs.publish_images }} run: | set -euo pipefail @@ -172,6 +185,11 @@ jobs: cache_backend="gha" fi + if [ "$PUBLISH_IMAGES" != "true" ]; then + cache_to_min="" + cache_to_max="" + fi + { echo "image_url=$image_url" echo "platforms=$platforms" @@ -188,6 +206,8 @@ jobs: echo "| Image | Platforms | Cache backend | Cache reference |" echo "| --- | --- | --- | --- |" echo "| \`${image_url}\` | \`${platforms}\` | \`${cache_backend}\` | \`${REGISTRY_CACHE_IMAGE:-$cache_scope}\` |" + echo + echo "- Publish images: \`${PUBLISH_IMAGES}\`" } >> "$GITHUB_STEP_SUMMARY" - name: Checkout source (${{ matrix.module }}) @@ -262,7 +282,7 @@ jobs: context: ${{ matrix.context }} file: ${{ matrix.dockerfile }} platforms: ${{ steps.params.outputs.platforms }} - push: true + push: ${{ needs.prepare.outputs.publish_images == 'true' }} tags: ${{ steps.params.outputs.image_url }} cache-from: ${{ steps.params.outputs.cache_from }} cache-to: ${{ steps.params.outputs.cache_to_max }} @@ -275,14 +295,14 @@ jobs: context: ${{ matrix.context }} file: ${{ matrix.dockerfile }} platforms: ${{ steps.params.outputs.platforms }} - push: true + push: ${{ needs.prepare.outputs.publish_images == 'true' }} tags: ${{ steps.params.outputs.image_url }} cache-from: ${{ steps.params.outputs.cache_from }} cache-to: ${{ steps.params.outputs.cache_to_max }} update_latest_hash: needs: [prepare, publish] - if: ${{ inputs.mode == 'latest' && inputs.enable_hash_gate && needs.prepare.outputs.need_update == 'true' && needs.publish.result == 'success' }} + if: ${{ inputs.mode == 'latest' && !inputs.validation_only && inputs.enable_hash_gate && needs.prepare.outputs.need_update == 'true' && needs.publish.result == 'success' }} runs-on: ubuntu-latest steps: - name: Validate hash update inputs diff --git a/.github/workflows/_publish_pd_store_server_reusable.yml b/.github/workflows/_publish_pd_store_server_reusable.yml index 3e1f02c..dbceb26 100644 --- a/.github/workflows/_publish_pd_store_server_reusable.yml +++ b/.github/workflows/_publish_pd_store_server_reusable.yml @@ -71,6 +71,8 @@ jobs: need_update: ${{ steps.prepare.outputs.need_update }} source_sha: ${{ steps.prepare.outputs.source_sha }} version_tag: ${{ steps.prepare.outputs.version_tag }} + cache_channel: ${{ steps.prepare.outputs.cache_channel }} + publish_images: ${{ steps.prepare.outputs.publish_images }} build_matrix_json: ${{ steps.prepare.outputs.build_matrix_json }} steps: - name: Resolve mode and source ref @@ -96,7 +98,24 @@ jobs: fi if [ "$MODE" = "latest" ]; then - version_tag="latest" + if [ "$BRANCH" = "master" ]; then + version_tag="latest" + publish_images="true" + else + safe_branch="$( + printf '%s' "$BRANCH" \ + | tr '[:upper:]' '[:lower:]' \ + | sed -E 's/[^a-z0-9_.-]+/-/g; s/^[.-]+//; s/[.-]+$//' \ + | cut -c1-110 + )" + if [ -z "$safe_branch" ]; then + echo "Failed to derive an image tag from branch: $BRANCH" + exit 1 + fi + version_tag="branch-${safe_branch}" + publish_images="false" + fi + cache_channel="latest" need_update="true" if [ "$ENABLE_HASH_GATE" = "true" ] && [ "$source_sha" = "$LAST_HASH_VALUE" ]; then need_update="false" @@ -108,11 +127,15 @@ jobs: exit 1 fi need_update="true" + cache_channel="release" + publish_images="true" fi { echo "source_sha=$source_sha" echo "version_tag=$version_tag" + echo "cache_channel=$cache_channel" + echo "publish_images=$publish_images" echo "need_update=$need_update" echo "build_matrix_json<> "$GITHUB_STEP_SUMMARY" integration_precheck: @@ -211,8 +240,8 @@ jobs: platforms: linux/amd64 load: true tags: hg-ci/pd:precheck - cache-from: type=registry,ref=hugegraph/pd:buildcache-${{ inputs.mode }} - cache-to: type=registry,ref=hugegraph/pd:buildcache-${{ inputs.mode }},mode=max + cache-from: type=registry,ref=hugegraph/pd:buildcache-${{ needs.prepare.outputs.cache_channel }} + cache-to: ${{ needs.prepare.outputs.publish_images == 'true' && format('type=registry,ref=hugegraph/pd:buildcache-{0},mode=max', needs.prepare.outputs.cache_channel) || '' }} build-args: ${{ env.MVN_ARGS }} - name: Build x86 Store image for integration check @@ -223,8 +252,8 @@ jobs: platforms: linux/amd64 load: true tags: hg-ci/store:precheck - cache-from: type=registry,ref=hugegraph/store:buildcache-${{ inputs.mode }} - cache-to: type=registry,ref=hugegraph/store:buildcache-${{ inputs.mode }},mode=max + cache-from: type=registry,ref=hugegraph/store:buildcache-${{ needs.prepare.outputs.cache_channel }} + cache-to: ${{ needs.prepare.outputs.publish_images == 'true' && format('type=registry,ref=hugegraph/store:buildcache-{0},mode=max', needs.prepare.outputs.cache_channel) || '' }} build-args: ${{ env.MVN_ARGS }} - name: Build x86 Server(hstore) image for integration check @@ -235,8 +264,8 @@ jobs: platforms: linux/amd64 load: true tags: hg-ci/server:precheck - cache-from: type=registry,ref=hugegraph/server:buildcache-${{ inputs.mode }} - cache-to: type=registry,ref=hugegraph/server:buildcache-${{ inputs.mode }},mode=max + cache-from: type=registry,ref=hugegraph/server:buildcache-${{ needs.prepare.outputs.cache_channel }} + cache-to: ${{ needs.prepare.outputs.publish_images == 'true' && format('type=registry,ref=hugegraph/server:buildcache-{0},mode=max', needs.prepare.outputs.cache_channel) || '' }} build-args: ${{ env.MVN_ARGS }} - name: Start compose stack with local images @@ -307,7 +336,7 @@ jobs: SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }} VERSION_TAG: ${{ needs.prepare.outputs.version_tag }} MVN_ARGS: ${{ inputs.mvn_args }} - MODE: ${{ inputs.mode }} + CACHE_CHANNEL: ${{ needs.prepare.outputs.cache_channel }} steps: - name: Resolve tags (${{ matrix.module }}) id: tags @@ -315,7 +344,7 @@ jobs: IMAGE_REPO: ${{ matrix.image_repo }} run: | image_amd64="${IMAGE_REPO}:${VERSION_TAG}-amd64" - module_cache_ref="${IMAGE_REPO}:buildcache-${MODE}" + module_cache_ref="${IMAGE_REPO}:buildcache-${CACHE_CHANNEL}" { echo "image_amd64=$image_amd64" @@ -350,7 +379,7 @@ jobs: load: true tags: ${{ steps.tags.outputs.image_amd64 }} cache-from: type=registry,ref=${{ steps.tags.outputs.module_cache_ref }} - cache-to: type=registry,ref=${{ steps.tags.outputs.module_cache_ref }},mode=max + cache-to: ${{ needs.prepare.outputs.publish_images == 'true' && format('type=registry,ref={0},mode=max', steps.tags.outputs.module_cache_ref) || '' }} build-args: ${{ env.MVN_ARGS }} - name: Build and push amd64 image (${{ matrix.module }}) @@ -360,10 +389,10 @@ jobs: context: . file: ${{ matrix.dockerfile }} platforms: linux/amd64 - push: true + push: ${{ needs.prepare.outputs.publish_images == 'true' }} tags: ${{ steps.tags.outputs.image_amd64 }} cache-from: type=registry,ref=${{ steps.tags.outputs.module_cache_ref }} - cache-to: type=registry,ref=${{ steps.tags.outputs.module_cache_ref }},mode=max + cache-to: ${{ needs.prepare.outputs.publish_images == 'true' && format('type=registry,ref={0},mode=max', steps.tags.outputs.module_cache_ref) || '' }} build-args: ${{ env.MVN_ARGS }} - name: Smoke test standalone server amd64 @@ -380,7 +409,7 @@ jobs: curl -fsS http://127.0.0.1:18080 >/dev/null || exit 1 - name: Push tested amd64 image (${{ matrix.module }}) - if: ${{ matrix.smoke_test }} + if: ${{ matrix.smoke_test && needs.prepare.outputs.publish_images == 'true' }} env: IMAGE_URL: ${{ steps.tags.outputs.image_amd64 }} run: | @@ -404,7 +433,7 @@ jobs: SOURCE_SHA: ${{ needs.prepare.outputs.source_sha }} VERSION_TAG: ${{ needs.prepare.outputs.version_tag }} MVN_ARGS: ${{ inputs.mvn_args }} - MODE: ${{ inputs.mode }} + CACHE_CHANNEL: ${{ needs.prepare.outputs.cache_channel }} steps: - name: Resolve tags (${{ matrix.module }}) id: tags @@ -412,7 +441,7 @@ jobs: IMAGE_REPO: ${{ matrix.image_repo }} run: | image_arm64="${IMAGE_REPO}:${VERSION_TAG}-arm64" - module_cache_ref="${IMAGE_REPO}:buildcache-${MODE}" + module_cache_ref="${IMAGE_REPO}:buildcache-${CACHE_CHANNEL}" { echo "image_arm64=$image_arm64" @@ -446,14 +475,14 @@ jobs: context: . file: ${{ matrix.dockerfile }} platforms: linux/arm64 - push: true + push: ${{ needs.prepare.outputs.publish_images == 'true' }} tags: ${{ steps.tags.outputs.image_arm64 }} cache-from: type=registry,ref=${{ steps.tags.outputs.module_cache_ref }} build-args: ${{ env.MVN_ARGS }} publish_manifest: needs: [prepare, publish_amd64, publish_arm64] - if: ${{ needs.prepare.outputs.need_update == 'true' && needs.publish_amd64.result == 'success' && needs.publish_arm64.result == 'success' }} + if: ${{ needs.prepare.outputs.need_update == 'true' && needs.prepare.outputs.publish_images == 'true' && needs.publish_amd64.result == 'success' && needs.publish_arm64.result == 'success' }} runs-on: ubuntu-latest strategy: fail-fast: false @@ -579,7 +608,7 @@ jobs: update_latest_hash: needs: [prepare, publish_manifest] - if: ${{ inputs.mode == 'latest' && inputs.enable_hash_gate && needs.prepare.outputs.need_update == 'true' && needs.publish_manifest.result == 'success' }} + if: ${{ inputs.mode == 'latest' && inputs.branch == 'master' && inputs.enable_hash_gate && needs.prepare.outputs.need_update == 'true' && needs.publish_manifest.result == 'success' }} runs-on: ubuntu-latest steps: - name: Validate hash update inputs diff --git a/.github/workflows/publish_latest_ai_image.yml b/.github/workflows/publish_latest_ai_image.yml index baae8d4..b47aff6 100644 --- a/.github/workflows/publish_latest_ai_image.yml +++ b/.github/workflows/publish_latest_ai_image.yml @@ -4,6 +4,11 @@ on: schedule: - cron: '0 23 * * *' workflow_dispatch: + inputs: + latest_source_branch: + required: false + default: 'main' + description: 'source branch; non-main branches run read-only validation' jobs: publish: @@ -12,7 +17,8 @@ jobs: mode: latest component: ai repository_url: apache/hugegraph-ai - branch: main + branch: ${{ github.event.inputs.latest_source_branch || 'main' }} + validation_only: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.latest_source_branch || 'main') != 'main' }} # IMPORTANT: # - Keep ARM enabled for normal RAG image (Dockerfile.llm). # - Keep rag-bin (Dockerfile.nk) on amd64 only due to arm incompatibility. @@ -24,6 +30,7 @@ jobs: "dockerfile": "./docker/Dockerfile.llm", "image_repo_latest": "hugegraph/rag", "image_repo_release": "hugegraph/rag", + "registry_cache_image": "hugegraph/rag:buildcache-latest", "platforms_latest": "linux/amd64,linux/arm64", "platforms_release": "linux/amd64,linux/arm64", "smoke_test": false @@ -34,12 +41,13 @@ jobs: "dockerfile": "./docker/Dockerfile.nk", "image_repo_latest": "hugegraph/rag-bin", "image_repo_release": "hugegraph/rag-bin", + "registry_cache_image": "hugegraph/rag-bin:buildcache-latest", "platforms_latest": "linux/amd64", "platforms_release": "linux/amd64", "smoke_test": false } ] - enable_hash_gate: true + enable_hash_gate: ${{ github.event_name != 'workflow_dispatch' || (github.event.inputs.latest_source_branch || 'main') == 'main' }} last_hash_value: ${{ vars.LAST_AI_HASH }} last_hash_name: LAST_AI_HASH hash_repo_owner: hugegraph diff --git a/.github/workflows/publish_latest_hubble_image.yml b/.github/workflows/publish_latest_hubble_image.yml index 3487787..eb7fff7 100644 --- a/.github/workflows/publish_latest_hubble_image.yml +++ b/.github/workflows/publish_latest_hubble_image.yml @@ -5,6 +5,10 @@ on: - cron: '0 23 * * *' workflow_dispatch: inputs: + latest_source_branch: + required: false + default: 'master' + description: 'source branch; non-master branches run read-only validation' mvn_args: required: false default: '' @@ -17,7 +21,8 @@ jobs: mode: latest component: hubble repository_url: apache/hugegraph-toolchain - branch: master + branch: ${{ github.event.inputs.latest_source_branch || 'master' }} + validation_only: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.latest_source_branch || 'master') != 'master' }} build_matrix_json: | [ { @@ -34,7 +39,7 @@ jobs: ] use_mvn_args: true mvn_args: ${{ github.event.inputs.mvn_args || '' }} - enable_hash_gate: true + enable_hash_gate: ${{ github.event_name != 'workflow_dispatch' || (github.event.inputs.latest_source_branch || 'master') == 'master' }} last_hash_value: ${{ vars.LAST_HUBBLE_HASH }} last_hash_name: LAST_HUBBLE_HASH hash_repo_owner: hugegraph diff --git a/.github/workflows/publish_latest_loader_image.yml b/.github/workflows/publish_latest_loader_image.yml index e41ea84..c95171f 100644 --- a/.github/workflows/publish_latest_loader_image.yml +++ b/.github/workflows/publish_latest_loader_image.yml @@ -5,6 +5,10 @@ on: - cron: '0 23 * * *' workflow_dispatch: inputs: + latest_source_branch: + required: false + default: 'master' + description: 'source branch; non-master branches run read-only validation' mvn_args: required: false default: '' @@ -17,7 +21,8 @@ jobs: mode: latest component: loader repository_url: apache/hugegraph-toolchain - branch: master + branch: ${{ github.event.inputs.latest_source_branch || 'master' }} + validation_only: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.latest_source_branch || 'master') != 'master' }} build_matrix_json: | [ { @@ -26,6 +31,7 @@ jobs: "dockerfile": "./hugegraph-loader/Dockerfile", "image_repo_latest": "hugegraph/loader", "image_repo_release": "hugegraph/loader", + "registry_cache_image": "hugegraph/loader:buildcache-latest", "platforms_latest": "linux/amd64,linux/arm64", "platforms_release": "linux/amd64,linux/arm64", "smoke_test": false @@ -33,7 +39,7 @@ jobs: ] use_mvn_args: true mvn_args: ${{ github.event.inputs.mvn_args || '' }} - enable_hash_gate: true + enable_hash_gate: ${{ github.event_name != 'workflow_dispatch' || (github.event.inputs.latest_source_branch || 'master') == 'master' }} last_hash_value: ${{ vars.LAST_LOADER_HASH }} last_hash_name: LAST_LOADER_HASH hash_repo_owner: hugegraph diff --git a/.github/workflows/publish_latest_pd_store_server_image.yml b/.github/workflows/publish_latest_pd_store_server_image.yml index 1ac38ff..314347a 100644 --- a/.github/workflows/publish_latest_pd_store_server_image.yml +++ b/.github/workflows/publish_latest_pd_store_server_image.yml @@ -5,6 +5,10 @@ on: - cron: '0 23 * * *' workflow_dispatch: inputs: + latest_source_branch: + required: false + default: 'master' + description: 'source branch; non-master branches run read-only validation' mvn_args: required: false default: '' @@ -29,11 +33,11 @@ jobs: with: mode: latest repository_url: apache/hugegraph - branch: master + branch: ${{ github.event.inputs.latest_source_branch || 'master' }} mvn_args: ${{ github.event.inputs.mvn_args || '' }} - strict_mode: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.strict_mode == true || github.event.inputs.strict_mode == 'true' }} + strict_mode: ${{ github.event_name != 'workflow_dispatch' || (github.event.inputs.latest_source_branch || 'master') != 'master' || github.event.inputs.strict_mode == true || github.event.inputs.strict_mode == 'true' }} wait_timeout_sec: ${{ github.event.inputs.wait_timeout_sec || '300' }} - enable_hash_gate: true + enable_hash_gate: ${{ github.event_name != 'workflow_dispatch' || (github.event.inputs.latest_source_branch || 'master') == 'master' }} last_hash_value: ${{ vars.LAST_SERVER_HASH }} last_hash_name: LAST_SERVER_HASH hash_repo_owner: hugegraph diff --git a/.github/workflows/publish_latest_vermeer_image.yml b/.github/workflows/publish_latest_vermeer_image.yml index 6de4eeb..5323325 100644 --- a/.github/workflows/publish_latest_vermeer_image.yml +++ b/.github/workflows/publish_latest_vermeer_image.yml @@ -4,6 +4,11 @@ on: schedule: - cron: '0 23 * * *' workflow_dispatch: + inputs: + latest_source_branch: + required: false + default: 'master' + description: 'source branch; non-master branches run read-only validation' jobs: publish: @@ -12,7 +17,8 @@ jobs: mode: latest component: vermeer repository_url: apache/hugegraph-computer - branch: master + branch: ${{ github.event.inputs.latest_source_branch || 'master' }} + validation_only: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.latest_source_branch || 'master') != 'master' }} build_matrix_json: | [ { @@ -21,12 +27,13 @@ jobs: "dockerfile": "./vermeer/Dockerfile", "image_repo_latest": "hugegraph/vermeer", "image_repo_release": "hugegraph/vermeer", + "registry_cache_image": "hugegraph/vermeer:buildcache-latest", "platforms_latest": "linux/amd64,linux/arm64", "platforms_release": "linux/amd64,linux/arm64", "smoke_test": false } ] - enable_hash_gate: true + enable_hash_gate: ${{ github.event_name != 'workflow_dispatch' || (github.event.inputs.latest_source_branch || 'master') == 'master' }} last_hash_value: ${{ vars.LAST_VERMEER_HASH }} last_hash_name: LAST_VERMEER_HASH hash_repo_owner: hugegraph diff --git a/.github/workflows/publish_release_ai_image.yml b/.github/workflows/publish_release_ai_image.yml index 18bc638..6eb2ed2 100644 --- a/.github/workflows/publish_release_ai_image.yml +++ b/.github/workflows/publish_release_ai_image.yml @@ -27,6 +27,7 @@ jobs: "dockerfile": "./docker/Dockerfile.llm", "image_repo_latest": "hugegraph/rag", "image_repo_release": "hugegraph/rag", + "registry_cache_image": "hugegraph/rag:buildcache-release", "platforms_latest": "linux/amd64,linux/arm64", "platforms_release": "linux/amd64,linux/arm64", "smoke_test": false @@ -37,6 +38,7 @@ jobs: "dockerfile": "./docker/Dockerfile.nk", "image_repo_latest": "hugegraph/rag-bin", "image_repo_release": "hugegraph/rag-bin", + "registry_cache_image": "hugegraph/rag-bin:buildcache-release", "platforms_latest": "linux/amd64", "platforms_release": "linux/amd64", "smoke_test": false diff --git a/.github/workflows/publish_release_loader_image.yml b/.github/workflows/publish_release_loader_image.yml index e923f37..b817e8f 100644 --- a/.github/workflows/publish_release_loader_image.yml +++ b/.github/workflows/publish_release_loader_image.yml @@ -28,6 +28,7 @@ jobs: "dockerfile": "./hugegraph-loader/Dockerfile", "image_repo_latest": "hugegraph/loader", "image_repo_release": "hugegraph/loader", + "registry_cache_image": "hugegraph/loader:buildcache-release", "platforms_latest": "linux/amd64,linux/arm64", "platforms_release": "linux/amd64,linux/arm64", "smoke_test": false diff --git a/.github/workflows/publish_release_vermeer_image.yml b/.github/workflows/publish_release_vermeer_image.yml index 43838d2..11e32f2 100644 --- a/.github/workflows/publish_release_vermeer_image.yml +++ b/.github/workflows/publish_release_vermeer_image.yml @@ -24,6 +24,7 @@ jobs: "dockerfile": "./vermeer/Dockerfile", "image_repo_latest": "hugegraph/vermeer", "image_repo_release": "hugegraph/vermeer", + "registry_cache_image": "hugegraph/vermeer:buildcache-release", "platforms_latest": "linux/amd64,linux/arm64", "platforms_release": "linux/amd64,linux/arm64", "smoke_test": false diff --git a/README.md b/README.md index 616b7c5..648de85 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,57 @@ The two publishing modes behave differently: - always publishes when invoked - derives the image tag from the release branch version +## Multi-Platform Build Performance + +BuildKit exposes automatic platform arguments such as `BUILDPLATFORM` and +`TARGETPLATFORM`. A multi-stage Dockerfile can pin an architecture-independent +build stage to the native builder while leaving the runtime stage on the target +platform: + +```dockerfile +FROM --platform=$BUILDPLATFORM maven:3.9.0-eclipse-temurin-11 AS build +RUN mvn package ... + +FROM eclipse-temurin:11-jre-jammy +COPY --from=build /pkg/dist/ /app/ +``` + +Without the explicit build platform, every unqualified `FROM` defaults to the +requested target platform. An amd64 GitHub runner therefore executes the entire +arm64 Maven, Node, compression, or packaging workload through QEMU. Pinning the +portable build stage prevents emulation while the final JRE/base image remains +architecture correct. + +This is a BuildKit-only feature. Buildx requires Docker Engine 19.03 or newer, +and the automatic platform arguments are documented in the Dockerfile frontend: + +- [Docker multi-platform build strategies](https://docs.docker.com/build/building/multi-platform/) +- [Dockerfile automatic platform arguments](https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope) + +Use this optimization only when the copied build output is portable or is +explicitly cross-compiled for `TARGETOS` / `TARGETARCH`. Native C/C++, CGO, +JNI, platform-classifier artifacts, and downloaded executables must be audited +and covered by real target-platform smoke tests. When the output is inherently +target-specific, use a native target runner instead of forcing `BUILDPLATFORM`. + +The primary performance gain comes from moving portable build work off QEMU. +Registry cache improvements are additional to the native-build or +cross-compilation gains. Keep measured timings in pull requests or dated CI +reports rather than this design document. + +### Read-Only Branch Validation + +Latest wrappers that expose `latest_source_branch` use two execution policies: + +- default branch (`master`, or `main` for AI): publish images, export registry + caches, create manifests, and update the corresponding `LAST_*_HASH` variable. +- non-default branch: force validation checks, import existing caches read-only, + build all configured platforms, and skip image pushes, cache exports, + manifests, and hash updates. + +This allows an upstream Dockerfile branch to be benchmarked before merge without +changing public images or production cache state. + ## Critical Path: PD/Store/Server `pd/store/server` is the most important publishing flow in this repository and uses a dedicated reusable workflow: