@@ -39,6 +39,10 @@ inputs:
3939 docker-build-target :
4040 description : " Sets the target stage to build"
4141 required : false
42+ docker-build-platforms :
43+ description : " Sets the target platforms for build"
44+ required : false
45+ default : ' linux/amd64'
4246 docker-build-provenance :
4347 description : " Generate provenance attestation for the build"
4448 required : false
@@ -151,6 +155,34 @@ runs:
151155 echo "tag=$TAG" >> $GITHUB_OUTPUT
152156 echo "tag_list=$TAG_LIST" >> $GITHUB_OUTPUT
153157
158+ - name : Verify Architecture Match
159+ shell : bash
160+ if : steps.preparation.outputs.build == 'true'
161+ run : |
162+ RUNNER_ARCH="${{ runner.arch }}" # X64 (AMD64) or ARM64
163+ TARGET_PLATFORMS="${{ inputs.docker-build-platforms }}"
164+
165+ echo "Runner CPU Architecture: $RUNNER_ARCH"
166+ echo "Requested Build Platforms: $TARGET_PLATFORMS"
167+
168+ # Check for AMD64 mismatch (Runner is X64, but user requests ONLY arm64, OR user requests multi-arch which requires emulation)
169+ if [[ "$RUNNER_ARCH" == "X64" ]]; then
170+ if [[ "$TARGET_PLATFORMS" == *"linux/arm64"* ]]; then
171+ echo "::error::Runner is X64 (Intel/AMD) but build includes 'linux/arm64'. This requires emulation. Aborting strictly."
172+ exit 1
173+ fi
174+ fi
175+
176+ # Check for ARM64 mismatch
177+ if [[ "$RUNNER_ARCH" == "ARM64" ]]; then
178+ if [[ "$TARGET_PLATFORMS" == *"linux/amd64"* ]]; then
179+ echo "::error::Runner is ARM64 (Apple Silicon/Graviton) but build includes 'linux/amd64'. This requires emulation. Aborting strictly."
180+ exit 1
181+ fi
182+ fi
183+
184+ echo "Architecture match verified for native build ✅"
185+
154186 - name : Set up Docker Buildx
155187 if : inputs.docker-username != '' && inputs.docker-password != ''
156188 uses : docker/setup-buildx-action@v3
@@ -177,7 +209,7 @@ runs:
177209 tags : ${{ steps.preparation.outputs.tag_list }}
178210 secrets : ${{ inputs.docker-build-secrets }}
179211 secret-files : ${{ inputs.docker-build-secret-files }}
180- platforms : linux/amd64
212+ platforms : ${{ inputs.docker-build-platforms }}
181213 cache-from : type=gha
182214 cache-to : type=gha,mode=max
183215 provenance : ${{ inputs.docker-build-provenance }}
@@ -189,27 +221,38 @@ runs:
189221 shell : bash
190222 run : |
191223 CHECK_EXISTING_TAGS="master-${GITHUB_SHA::8} main-${GITHUB_SHA::8}"
192- CONTENT_TYPE="application/vnd.docker.distribution.manifest.v2+json"
224+ # Accept both single-arch manifests and multi-arch manifest lists/indexes
225+ ACCEPT_HEADER="application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json"
193226
194227 echo "CHECK_EXISTING_TAGS: ${CHECK_EXISTING_TAGS}"
195228 echo "RELEASE_TAG: ${RELEASE_TAG:1}"
196229 echo "Check if an image already exists for ${{ inputs.docker-image }}:main|master-${GITHUB_SHA::8} 🐋 ⬇"
197230
198231 foundImage=false
232+ DETECTED_CONTENT_TYPE=""
233+ DIGEST=""
199234
200235 end=$((SECONDS+300))
201236 while [ $SECONDS -lt $end ]; do
202237
203238 MANIFEST=""
204239 for tag in $CHECK_EXISTING_TAGS; do
205- MANIFEST=$(curl -H "Accept: ${CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${tag}")
240+ # Dump headers to file to extract Content-Type and Digest later
241+ MANIFEST=$(curl -s -D headers.txt -H "Accept: ${ACCEPT_HEADER}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${tag}")
206242
207243 if [[ $MANIFEST == *"errors"* ]]; then
208244 echo "No image found for ${{ inputs.docker-image }}:${tag} 🚫"
209245 continue
210246 else
211247 echo "Image found for ${{ inputs.docker-image }}:${tag} 🐋 ⬇"
212248 foundImage=true
249+
250+ # Extract the Content-Type returned by registry
251+ DETECTED_CONTENT_TYPE=$(grep -i "^Content-Type:" headers.txt | cut -d' ' -f2 | tr -d '\r')
252+
253+ # Extract the correct digest from headers (works for lists and single images)
254+ DIGEST=$(grep -i "^Docker-Content-Digest:" headers.txt | cut -d' ' -f2 | tr -d '\r')
255+
213256 break 2
214257 fi
215258 done
@@ -223,11 +266,12 @@ runs:
223266 fi
224267
225268 echo "Retagging image with release version and :latest tags for ${{ inputs.docker-image }} 🏷"
226- curl --fail-with-body -X PUT -H "Content-Type: ${CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' -d "${MANIFEST}" "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${{ steps.preparation.outputs.tag }}"
227- curl --fail-with-body -X PUT -H "Content-Type: ${CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' -d "${MANIFEST}" "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${{ steps.preparation.outputs.latest }}"
269+ echo "Using Content-Type: ${DETECTED_CONTENT_TYPE}"
270+
271+ # Use the detected Content-Type to PUT the manifest back
272+ curl --fail-with-body -X PUT -H "Content-Type: ${DETECTED_CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' -d "${MANIFEST}" "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${{ steps.preparation.outputs.tag }}"
273+ curl --fail-with-body -X PUT -H "Content-Type: ${DETECTED_CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' -d "${MANIFEST}" "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${{ steps.preparation.outputs.latest }}"
228274
229- # Get the digest of the image
230- DIGEST=$(echo $MANIFEST | jq .config.digest | tr -d '"')
231275 echo "digest=$DIGEST" >> $GITHUB_OUTPUT
232276
233277 - name : Checkout GitOps Repository
0 commit comments