2020 push :
2121 branches :
2222 - main
23- - release/*
2423 - " pull-request/[0-9]+"
2524 tags :
2625 - " v[0-9]*.[0-9]*.[0-9]*"
@@ -510,6 +509,43 @@ jobs:
510509 tag_latest : false
511510 secrets : inherit
512511
512+ # Combine the per-arch nvmetal-carbide images into a single multi-arch manifest
513+ # at the bare tag, so the carbide control plane can be scheduled on both amd64
514+ # and arm64 nodes. The per-arch images are still pushed individually
515+ # (nvmetal-carbide = amd64, nvmetal-carbide-aarch64 = arm64). Sources are
516+ # referenced by digest so re-runs are idempotent. Push refs only.
517+ merge-manifests-nvmetal-carbide :
518+ needs :
519+ - prepare
520+ - build-release-container-x86_64
521+ - build-release-container-aarch64
522+ if : ${{ !cancelled() && github.event_name != 'schedule' && !contains(github.ref, 'pull-request/') && needs.build-release-container-x86_64.result == 'success' && needs.build-release-container-aarch64.result == 'success' }}
523+ runs-on : linux-amd64-cpu4
524+ steps :
525+ - name : Checkout code
526+ uses : actions/checkout@v4
527+
528+ - name : Set up Docker Buildx
529+ uses : docker/setup-buildx-action@v3
530+
531+ - name : Login to NVCR
532+ uses : ./.github/actions/docker-auth
533+ with :
534+ username : ${{ secrets.NVCR_USERNAME }}
535+ token : ${{ secrets.NVCR_TOKEN }}
536+
537+ - name : Create and push multi-arch manifest
538+ uses : nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08
539+ with :
540+ max_attempts : 3
541+ timeout_minutes : 10
542+ retry_wait_seconds : 20
543+ command : |
544+ docker buildx imagetools create -t \
545+ nvcr.io/0837451325059433/carbide-dev/nvmetal-carbide:${{ needs.prepare.outputs.version }} \
546+ nvcr.io/0837451325059433/carbide-dev/nvmetal-carbide@${{ needs.build-release-container-x86_64.outputs.image_digest }} \
547+ nvcr.io/0837451325059433/carbide-dev/nvmetal-carbide-aarch64@${{ needs.build-release-container-aarch64.outputs.image_digest }}
548+
513549 test-release-container-services :
514550 if : ${{ always() && github.event_name != 'schedule' && needs.prepare.result == 'success' }}
515551 needs :
@@ -734,7 +770,12 @@ jobs:
734770 dockerfile_path : dev/docker/Dockerfile.release-artifacts-x86_64
735771 image_name : nvcr.io/0837451325059433/carbide-dev/boot-artifacts-x86_64
736772 image_tag : ${{ needs.prepare.outputs.version }}
737- platforms : linux/amd64
773+ # Multi-arch on push so this x86_64 boot-artifacts carrier can run as an
774+ # init container on both amd64 and arm64 control-plane nodes; the payload
775+ # it ships is unchanged (x86_64 boot blobs). The Dockerfile has no RUN
776+ # steps (FROM alpine + COPY only), so no QEMU is needed. Single-arch on PR
777+ # so the image loads to the daemon for the Grype scan.
778+ platforms : ${{ !contains(github.ref, 'pull-request/') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
738779 runner : linux-amd64-cpu4
739780 push : ${{ !contains(github.ref, 'pull-request/') }}
740781 load : true
@@ -760,7 +801,12 @@ jobs:
760801 dockerfile_path : dev/docker/Dockerfile.release-artifacts-aarch64
761802 image_name : nvcr.io/0837451325059433/carbide-dev/boot-artifacts-aarch64
762803 image_tag : ${{ needs.prepare.outputs.version }}
763- platforms : linux/amd64 # Note: aarch64 artifacts packaged in x86_64 image
804+ # Container arch is independent of the payload: this carrier always ships
805+ # the aarch64 boot blobs as files. Build it multi-arch on push so it can
806+ # run as an init container on both amd64 and arm64 control-plane nodes. The
807+ # Dockerfile has no RUN steps (FROM alpine + COPY only), so no QEMU is
808+ # needed. Single-arch on PR so the image loads to the daemon for Grype.
809+ platforms : ${{ !contains(github.ref, 'pull-request/') && 'linux/amd64,linux/arm64' || 'linux/amd64' }}
764810 runner : linux-amd64-cpu4
765811 push : ${{ !contains(github.ref, 'pull-request/') }}
766812 load : true
@@ -868,6 +914,65 @@ jobs:
868914 scan : true
869915 secrets : inherit
870916
917+ # arm64 container build of the machine-validation config carrier, so it can run
918+ # as an init container on arm64 control-plane nodes. FROM the aarch64 runtime
919+ # base; pushed as machine_validation-aarch64 and merged into the bare
920+ # machine_validation tag below. (The bundled validation runner payload staying
921+ # arm64-capable is tracked separately — this only makes the carrier runnable on
922+ # arm64 nodes.)
923+ build-release-machine-validation-artifacts-arm-host :
924+ needs :
925+ - prepare
926+ - build-runtime-container-aarch64
927+ if : ${{ !cancelled() && github.event_name != 'schedule' && needs.prepare.result == 'success' && contains('success,skipped', needs.build-runtime-container-aarch64.result) }}
928+ uses : ./.github/workflows/docker-build.yml
929+ with :
930+ dockerfile_path : dev/docker/Dockerfile.machine-validation-config-aarch64
931+ image_name : nvcr.io/0837451325059433/carbide-dev/machine_validation-aarch64
932+ image_tag : ${{ needs.prepare.outputs.version }}
933+ build_args : ' {"CI_COMMIT_SHORT_SHA":"${{ needs.prepare.outputs.short_sha }}","CONTAINER_RUNTIME_AARCH64":"nvcr.io/0837451325059433/carbide-dev/runtime-container-aarch64:${{ needs.prepare.outputs.runtime_container_aarch64_version }}"}'
934+ platforms : linux/arm64
935+ runner : linux-arm64-cpu4
936+ push : ${{ !contains(github.ref, 'pull-request/') }}
937+ load : true # load to daemon so the Grype scan can read it on PR builds
938+ scan : true
939+ secrets : inherit
940+
941+ # Combine machine_validation (amd64) and machine_validation-aarch64 into a
942+ # single multi-arch manifest at the bare machine_validation tag. Sources are
943+ # referenced by digest so re-runs are idempotent. Push refs only.
944+ merge-manifests-machine-validation :
945+ needs :
946+ - prepare
947+ - build-release-machine-validation-artifacts-x86-host
948+ - build-release-machine-validation-artifacts-arm-host
949+ if : ${{ !cancelled() && github.event_name != 'schedule' && !contains(github.ref, 'pull-request/') && needs.build-release-machine-validation-artifacts-x86-host.result == 'success' && needs.build-release-machine-validation-artifacts-arm-host.result == 'success' }}
950+ runs-on : linux-amd64-cpu4
951+ steps :
952+ - name : Checkout code
953+ uses : actions/checkout@v4
954+
955+ - name : Set up Docker Buildx
956+ uses : docker/setup-buildx-action@v3
957+
958+ - name : Login to NVCR
959+ uses : ./.github/actions/docker-auth
960+ with :
961+ username : ${{ secrets.NVCR_USERNAME }}
962+ token : ${{ secrets.NVCR_TOKEN }}
963+
964+ - name : Create and push multi-arch manifest
965+ uses : nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08
966+ with :
967+ max_attempts : 3
968+ timeout_minutes : 10
969+ retry_wait_seconds : 20
970+ command : |
971+ docker buildx imagetools create -t \
972+ nvcr.io/0837451325059433/carbide-dev/machine_validation:${{ needs.prepare.outputs.version }} \
973+ nvcr.io/0837451325059433/carbide-dev/machine_validation@${{ needs.build-release-machine-validation-artifacts-x86-host.outputs.image_digest }} \
974+ nvcr.io/0837451325059433/carbide-dev/machine_validation-aarch64@${{ needs.build-release-machine-validation-artifacts-arm-host.outputs.image_digest }}
975+
871976 proto-police :
872977 needs :
873978 - prepare
@@ -1407,7 +1512,7 @@ jobs:
14071512 # ============================================================================
14081513
14091514 notify-build-status :
1410- if : ${{ always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && github.event_name != 'schedule' }}
1515+ if : ${{ always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/tags/') ) && github.event_name != 'schedule' }}
14111516 needs :
14121517 - prepare
14131518 - build-container-x86_64
0 commit comments