From 51c8aaf4df55c8bdeda8fc54181ec8360269799e Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Thu, 14 May 2026 16:39:22 -0500 Subject: [PATCH 01/18] ci: add compressed image size cap to GitHub Actions and GitLab CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fail the build when the compressed download size of a built image exceeds a configurable high-water mark, to catch regressions like an accidental Spack-built LLVM being pulled in as a compiler. New script: - .ci/check_image_size IMAGE_REF LIMIT_GIB: inspects a pushed single-arch manifest, sums layers[].size, fails if over limit CI variables added (both GitHub Actions and GitLab CI): SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB = 1 SIZE_LIMIT_EIC_CI_GIB = 8 SIZE_LIMIT_EIC_XL_GIB = 10 Limits calibrated at 2025-05 master sizes with ~15% headroom: debian_stable_base: 0.76 GiB → 1 GiB eic_ci: 6.93 GiB → 8 GiB eic_xl: 8.68 GiB → 10 GiB To update the high-water mark after an intentional size increase: run .ci/query_image_sizes locally, add 15%, round up to next GiB, then update SIZE_LIMIT_*_GIB in both CI files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .ci/check_image_size | 50 ++++++++++++++++++++++++++++++++ .github/workflows/build-push.yml | 25 ++++++++++++++++ .gitlab-ci.yml | 24 +++++++++++++++ 3 files changed, 99 insertions(+) create mode 100755 .ci/check_image_size diff --git a/.ci/check_image_size b/.ci/check_image_size new file mode 100755 index 000000000..62b3704a5 --- /dev/null +++ b/.ci/check_image_size @@ -0,0 +1,50 @@ +#!/bin/sh +# check_image_size IMAGE_REFERENCE LIMIT_GIB +# +# Inspects a single-arch OCI/Docker image manifest that has already been pushed +# to a registry and fails with a clear message if the total compressed layer +# size exceeds LIMIT_GIB gibibytes. +# +# Requires: docker (with buildx), jq, bc +# Usage: +# .ci/check_image_size ghcr.io/eic/eic_xl@sha256:abc... 10 +# .ci/check_image_size ghcr.io/eic/eic_xl:master 10 +# +# The caller is responsible for being logged in to the registry before calling +# this script. + +set -e + +IMAGE_REF="${1:?Usage: $0 IMAGE_REFERENCE LIMIT_GIB}" +LIMIT_GIB="${2:?Usage: $0 IMAGE_REFERENCE LIMIT_GIB}" + +echo "Checking compressed image size for ${IMAGE_REF} (limit: ${LIMIT_GIB} GiB) ..." + +MANIFEST=$(docker buildx imagetools inspect --raw "${IMAGE_REF}") +SIZE_BYTES=$(printf '%s' "${MANIFEST}" | jq '[.layers[].size] | add // 0') + +if [ -z "${SIZE_BYTES}" ] || [ "${SIZE_BYTES}" = "null" ] || [ "${SIZE_BYTES}" -eq 0 ]; then + echo "ERROR: Could not determine image size from manifest (got: ${SIZE_BYTES})." + echo " Manifest snippet: $(printf '%s' "${MANIFEST}" | head -c 500)" + exit 1 +fi + +SIZE_GIB=$(echo "scale=2; ${SIZE_BYTES} / 1073741824" | bc) +LIMIT_BYTES=$(echo "${LIMIT_GIB} * 1073741824 / 1" | bc) + +echo " Compressed size : ${SIZE_GIB} GiB (${SIZE_BYTES} bytes)" +echo " Limit : ${LIMIT_GIB} GiB (${LIMIT_BYTES} bytes)" + +if [ "${SIZE_BYTES}" -gt "${LIMIT_BYTES}" ]; then + echo "" + echo "ERROR: Image ${IMAGE_REF} exceeds the size cap!" + echo " ${SIZE_GIB} GiB > ${LIMIT_GIB} GiB" + echo "" + echo "To update the high-water mark after an intentional size increase:" + echo " 1. Run .ci/query_image_sizes to get current sizes of published images" + echo " 2. Add ~15% buffer, round up to the next whole GiB" + echo " 3. Update SIZE_LIMIT_*_GIB in .github/workflows/build-push.yml and .gitlab-ci.yml" + exit 1 +fi + +echo " OK: size is within the ${LIMIT_GIB} GiB limit." diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 95a51404d..3157f66ef 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -56,6 +56,15 @@ env: ## Internal tag used for the CI INTERNAL_TAG: pipeline-${{ github.run_id }} + ## Compressed image size caps (GiB). Fail the build if a final image exceeds + ## these limits to catch regressions early (e.g. accidental Spack-built LLVM). + ## To update: run .ci/query_image_sizes against the master tag, add ~15% + ## headroom, round up, and update here and in .gitlab-ci.yml variables. + ## Last calibrated: 2025-05 (debian_stable_base=0.76, eic_ci=6.93, eic_xl=8.68 GiB) + SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: 1 + SIZE_LIMIT_EIC_CI_GIB: 8 + SIZE_LIMIT_EIC_XL_GIB: 10 + jobs: env: ## The env context is not available in matrix, only in steps, @@ -249,6 +258,11 @@ jobs: run: | mkdir -p /tmp/digests echo "${{ steps.meta.outputs.tags }}@${{ steps.build.outputs.digest }}" > /tmp/digests/${{ matrix.BUILD_IMAGE }}-${{ matrix.arch }}.digest + - name: Check image size + run: | + .ci/check_image_size \ + "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}@${{ steps.build.outputs.digest }}" \ + "${{ env.SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB }}" - name: Upload digest as artifact uses: actions/upload-artifact@v7 with: @@ -570,6 +584,17 @@ jobs: run: | mkdir -p /tmp/digests echo "${{ steps.meta.outputs.tags }}@${{ steps.build.outputs.digest }}" > /tmp/digests/${{ matrix.arch }}.digest + - name: Check image size + # Only check final images; concretization-only targets (cuda, tf) don't produce a full image + if: ${{ matrix.target == 'final' }} + run: | + SIZE_LIMIT=$(case "${{ matrix.ENV }}" in + xl) echo "${{ env.SIZE_LIMIT_EIC_XL_GIB }}" ;; + *) echo "${{ env.SIZE_LIMIT_EIC_CI_GIB }}" ;; + esac) + .ci/check_image_size \ + "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }}@${{ steps.build.outputs.digest }}" \ + "${SIZE_LIMIT}" - name: Upload digest as artifact uses: actions/upload-artifact@v7 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d7d701bd4..adb70ecc0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,15 @@ variables: ## Number of jobs to start during container builds JOBS: 32 + ## Compressed image size caps (GiB). Fail the build if a final image exceeds + ## these limits to catch regressions early (e.g. accidental Spack-built LLVM). + ## To update: run .ci/query_image_sizes against the master tag, add ~15% + ## headroom, round up, and update here and in .github/workflows/build-push.yml env. + ## Last calibrated: 2025-05 (debian_stable_base=0.76, eic_ci=6.93, eic_xl=8.68 GiB) + SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: "1" + SIZE_LIMIT_EIC_CI_GIB: "8" + SIZE_LIMIT_EIC_XL_GIB: "10" + ## is this nightly or not? NIGHTLY: "" ## Add to tag @@ -355,6 +364,9 @@ base: --provenance false containers/debian 2>&1 | tee build.log + - .ci/check_image_size + "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}:${INTERNAL_TAG}" + "${SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB}" eic: parallel: @@ -503,6 +515,18 @@ eic: --provenance false containers/eic 2>&1 | tee build.log + - # Check compressed image size; skip for concretization-only targets (cuda, tf) + case "${ENV}" in + cuda|tf) echo "Skipping size check for concretization-only env ${ENV}." ;; + xl) + .ci/check_image_size + "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}${ENV}:${INTERNAL_TAG}-${BUILD_TYPE}" + "${SIZE_LIMIT_EIC_XL_GIB}" ;; + *) + .ci/check_image_size + "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}${ENV}:${INTERNAL_TAG}-${BUILD_TYPE}" + "${SIZE_LIMIT_EIC_CI_GIB}" ;; + esac user_spack_environment: stage: benchmarks From 690d2affecc6e10d523efe551f4a67a2b66751bc Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Thu, 14 May 2026 16:58:18 -0500 Subject: [PATCH 02/18] fix(ci): handle OCI Image Index in check_image_size docker/build-push-action v4+ enables provenance attestations by default, which causes even single-platform builds to be pushed as an OCI Image Index (mediaType *index*/*manifest.list*) wrapping the real Image Manifest. The previous script called .layers[] directly on the index, which has no layers field, causing: jq: error: Cannot iterate over null (null) Fix: detect an OCI Image Index, pick the first platform manifest entry (excluding attestation entries that have artifactType set), re-fetch that manifest by digest, then proceed with the existing layer-size check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .ci/check_image_size | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.ci/check_image_size b/.ci/check_image_size index 62b3704a5..46eb38fe2 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -21,6 +21,27 @@ LIMIT_GIB="${2:?Usage: $0 IMAGE_REFERENCE LIMIT_GIB}" echo "Checking compressed image size for ${IMAGE_REF} (limit: ${LIMIT_GIB} GiB) ..." MANIFEST=$(docker buildx imagetools inspect --raw "${IMAGE_REF}") + +# docker/build-push-action (v4+) wraps even single-platform pushes in an OCI +# Image Index when provenance attestations are enabled (the default). Detect +# that case and resolve to the real per-platform Image Manifest before summing +# layer sizes. +MEDIA_TYPE=$(printf '%s' "${MANIFEST}" | jq -r '.mediaType // ""') +case "${MEDIA_TYPE}" in + *index* | *manifest.list*) + DIGEST=$(printf '%s' "${MANIFEST}" | jq -r ' + .manifests[] + | select(.artifactType == null and .platform != null) + | .digest' | head -1) + if [ -z "${DIGEST}" ]; then + echo "ERROR: OCI Image Index contains no platform manifest." + exit 1 + fi + IMAGE_BASE="${IMAGE_REF%@*}" + MANIFEST=$(docker buildx imagetools inspect --raw "${IMAGE_BASE}@${DIGEST}") + ;; +esac + SIZE_BYTES=$(printf '%s' "${MANIFEST}" | jq '[.layers[].size] | add // 0') if [ -z "${SIZE_BYTES}" ] || [ "${SIZE_BYTES}" = "null" ] || [ "${SIZE_BYTES}" -eq 0 ]; then From 97074363fbb2ca894accd0e86ba5c8cf58b0f913 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Thu, 14 May 2026 17:29:58 -0500 Subject: [PATCH 03/18] ci: skip size check for images without a configured limit Replace hardcoded limit variable references with a dynamic lookup derived from the image name: LIMIT_VAR="SIZE_LIMIT_$(echo "${FULLNAME}" | tr lower upper)_GIB" If no variable is defined for an image the check is skipped with a message. This fixes the current incorrect behaviour where: - cuda_devel and cuda_runtime were checked against the debian_stable_base 1 GiB limit - eic_cvmfs, eic_dbg, eic_jl, eic_prod, eic_ci_without_acts, eic_dev_cuda were checked against the eic_ci 8 GiB limit The three calibrated limits are unchanged: SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB = 1 SIZE_LIMIT_EIC_CI_GIB = 8 SIZE_LIMIT_EIC_XL_GIB = 10 To add a limit for a new image, add SIZE_LIMIT__GIB to the env: section of both CI files. The GitLab CI cuda|tf special-case is removed as those images are now skipped naturally (no variable set). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-push.yml | 30 ++++++++++++++++++---------- .gitlab-ci.yml | 34 ++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 3157f66ef..0a569724f 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -260,9 +260,16 @@ jobs: echo "${{ steps.meta.outputs.tags }}@${{ steps.build.outputs.digest }}" > /tmp/digests/${{ matrix.BUILD_IMAGE }}-${{ matrix.arch }}.digest - name: Check image size run: | - .ci/check_image_size \ - "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}@${{ steps.build.outputs.digest }}" \ - "${{ env.SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB }}" + FULLNAME="${{ matrix.BUILD_IMAGE }}" + LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" + LIMIT="${!LIMIT_VAR}" + if [ -n "${LIMIT}" ]; then + .ci/check_image_size \ + "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${FULLNAME}@${{ steps.build.outputs.digest }}" \ + "${LIMIT}" + else + echo "No size limit configured for ${FULLNAME}, skipping check." + fi - name: Upload digest as artifact uses: actions/upload-artifact@v7 with: @@ -588,13 +595,16 @@ jobs: # Only check final images; concretization-only targets (cuda, tf) don't produce a full image if: ${{ matrix.target == 'final' }} run: | - SIZE_LIMIT=$(case "${{ matrix.ENV }}" in - xl) echo "${{ env.SIZE_LIMIT_EIC_XL_GIB }}" ;; - *) echo "${{ env.SIZE_LIMIT_EIC_CI_GIB }}" ;; - esac) - .ci/check_image_size \ - "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }}@${{ steps.build.outputs.digest }}" \ - "${SIZE_LIMIT}" + FULLNAME="${{ matrix.BUILD_IMAGE }}${{ matrix.ENV }}" + LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" + LIMIT="${!LIMIT_VAR}" + if [ -n "${LIMIT}" ]; then + .ci/check_image_size \ + "${{ env.GH_REGISTRY }}/${{ env.GH_REGISTRY_USER }}/${FULLNAME}@${{ steps.build.outputs.digest }}" \ + "${LIMIT}" + else + echo "No size limit configured for ${FULLNAME}, skipping check." + fi - name: Upload digest as artifact uses: actions/upload-artifact@v7 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index adb70ecc0..431aca3d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -364,9 +364,15 @@ base: --provenance false containers/debian 2>&1 | tee build.log - - .ci/check_image_size - "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}:${INTERNAL_TAG}" - "${SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB}" + - LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${BUILD_IMAGE}" | tr '[:lower:]' '[:upper:]')_GIB" ; + LIMIT="${!LIMIT_VAR}" ; + if [ -n "${LIMIT}" ]; then + .ci/check_image_size + "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}:${INTERNAL_TAG}" + "${LIMIT}" ; + else + echo "No size limit configured for ${BUILD_IMAGE}, skipping check." ; + fi eic: parallel: @@ -515,18 +521,16 @@ eic: --provenance false containers/eic 2>&1 | tee build.log - - # Check compressed image size; skip for concretization-only targets (cuda, tf) - case "${ENV}" in - cuda|tf) echo "Skipping size check for concretization-only env ${ENV}." ;; - xl) - .ci/check_image_size - "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}${ENV}:${INTERNAL_TAG}-${BUILD_TYPE}" - "${SIZE_LIMIT_EIC_XL_GIB}" ;; - *) - .ci/check_image_size - "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}${ENV}:${INTERNAL_TAG}-${BUILD_TYPE}" - "${SIZE_LIMIT_EIC_CI_GIB}" ;; - esac + - FULLNAME="${BUILD_IMAGE}${ENV}" ; + LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" ; + LIMIT="${!LIMIT_VAR}" ; + if [ -n "${LIMIT}" ]; then + .ci/check_image_size + "${CI_REGISTRY}/${CI_PROJECT_PATH}/${FULLNAME}:${INTERNAL_TAG}-${BUILD_TYPE}" + "${LIMIT}" ; + else + echo "No size limit configured for ${FULLNAME}, skipping check." ; + fi user_spack_environment: stage: benchmarks From ccd794edc0597a46bbbcfffd28512b2564cef596 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Thu, 14 May 2026 19:54:16 -0500 Subject: [PATCH 04/18] fix(ci): use eval for POSIX sh indirect variable expansion in GitLab CI ${!VAR} is bash-specific and fails under /bin/sh (ash/busybox on Alpine). Replace with POSIX-compatible eval: eval "LIMIT=\${LIMIT_VAR:-}" Fixes 'bad substitution' error in base and eic check_image_size steps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 431aca3d7..5221d18fb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -365,7 +365,7 @@ base: containers/debian 2>&1 | tee build.log - LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${BUILD_IMAGE}" | tr '[:lower:]' '[:upper:]')_GIB" ; - LIMIT="${!LIMIT_VAR}" ; + eval "LIMIT=\${${LIMIT_VAR}:-}" ; if [ -n "${LIMIT}" ]; then .ci/check_image_size "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}:${INTERNAL_TAG}" @@ -523,7 +523,7 @@ eic: 2>&1 | tee build.log - FULLNAME="${BUILD_IMAGE}${ENV}" ; LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" ; - LIMIT="${!LIMIT_VAR}" ; + eval "LIMIT=\${${LIMIT_VAR}:-}" ; if [ -n "${LIMIT}" ]; then .ci/check_image_size "${CI_REGISTRY}/${CI_PROJECT_PATH}/${FULLNAME}:${INTERNAL_TAG}-${BUILD_TYPE}" From b9fbba698f0d1b8bd0ab227a41b66c0a1c64b949 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 08:26:10 -0500 Subject: [PATCH 05/18] fix: rm refs to query_image_sizes.sh Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .ci/check_image_size | 3 ++- .github/workflows/build-push.yml | 5 +++-- .gitlab-ci.yml | 5 +++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.ci/check_image_size b/.ci/check_image_size index 46eb38fe2..c4debd497 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -62,7 +62,8 @@ if [ "${SIZE_BYTES}" -gt "${LIMIT_BYTES}" ]; then echo " ${SIZE_GIB} GiB > ${LIMIT_GIB} GiB" echo "" echo "To update the high-water mark after an intentional size increase:" - echo " 1. Run .ci/query_image_sizes to get current sizes of published images" + echo " 1. Inspect the published image size with:" + echo " docker buildx imagetools inspect --raw \"${IMAGE_REF}\" | jq '[.layers[].size] | add // 0'" echo " 2. Add ~15% buffer, round up to the next whole GiB" echo " 3. Update SIZE_LIMIT_*_GIB in .github/workflows/build-push.yml and .gitlab-ci.yml" exit 1 diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 0a569724f..70a460b73 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -58,8 +58,9 @@ env: ## Compressed image size caps (GiB). Fail the build if a final image exceeds ## these limits to catch regressions early (e.g. accidental Spack-built LLVM). - ## To update: run .ci/query_image_sizes against the master tag, add ~15% - ## headroom, round up, and update here and in .gitlab-ci.yml variables. + ## To update: measure the compressed sizes of the published master images, + ## add ~15% headroom, round up, and update here and in .gitlab-ci.yml + ## variables. ## Last calibrated: 2025-05 (debian_stable_base=0.76, eic_ci=6.93, eic_xl=8.68 GiB) SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: 1 SIZE_LIMIT_EIC_CI_GIB: 8 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5221d18fb..e7d2c34f7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,8 +31,9 @@ variables: ## Compressed image size caps (GiB). Fail the build if a final image exceeds ## these limits to catch regressions early (e.g. accidental Spack-built LLVM). - ## To update: run .ci/query_image_sizes against the master tag, add ~15% - ## headroom, round up, and update here and in .github/workflows/build-push.yml env. + ## To update: measure the current master-tag compressed image sizes using + ## registry or CI tooling, add ~15% headroom, round up, and update here and + ## in .github/workflows/build-push.yml env. ## Last calibrated: 2025-05 (debian_stable_base=0.76, eic_ci=6.93, eic_xl=8.68 GiB) SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: "1" SIZE_LIMIT_EIC_CI_GIB: "8" From f6ef2310979ab8cae82b28e85605016e6bdaed2c Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 08:29:42 -0500 Subject: [PATCH 06/18] fix: print to two decimals Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .ci/check_image_size | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.ci/check_image_size b/.ci/check_image_size index c4debd497..fa6abfe89 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -50,7 +50,8 @@ if [ -z "${SIZE_BYTES}" ] || [ "${SIZE_BYTES}" = "null" ] || [ "${SIZE_BYTES}" - exit 1 fi -SIZE_GIB=$(echo "scale=2; ${SIZE_BYTES} / 1073741824" | bc) +SIZE_CENTI_GIB=$(echo "(${SIZE_BYTES} * 100 + 1073741824 - 1) / 1073741824" | bc) +SIZE_GIB="$(echo "${SIZE_CENTI_GIB} / 100" | bc).$(printf '%02d' "$(echo "${SIZE_CENTI_GIB} % 100" | bc)")" LIMIT_BYTES=$(echo "${LIMIT_GIB} * 1073741824 / 1" | bc) echo " Compressed size : ${SIZE_GIB} GiB (${SIZE_BYTES} bytes)" From d1b79e7c1382315fcf8025a5802537302f28d4f2 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 08:34:19 -0500 Subject: [PATCH 07/18] fix: apk add bc, git, and jq in .build Added necessary packages for build process. --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e7d2c34f7..becc4a5cf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -248,6 +248,7 @@ status:pending: - docker-new before_script: - !reference [.docker, before_script] + - apk add bc git jq - if [ "$SKIP_BINFMT" != "true" ]; then mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc ; for arch in aarch64 ; do From 34ce2f7881c21a2d3b439475524f94dc2f08473a Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 08:37:32 -0500 Subject: [PATCH 08/18] fix: add .ci/check_image_size to shellcheck --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dca79b662..fb2d78cb2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,4 +32,4 @@ repos: - id: shellcheck types: [shell] args: [--severity=warning, -x] - files: (^[^/]+\.sh$|^\.ci/resolve_git_ref$) + files: (^[^/]+\.sh$|^\.ci/resolve_git_ref$|^\.ci/check_image_size$) From 0897177e574cb511aece474bdcee3e32309afcc9 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 08:57:55 -0500 Subject: [PATCH 09/18] fix: clarify docs on multi-arch Clarified the description of the image manifest check. --- .ci/check_image_size | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.ci/check_image_size b/.ci/check_image_size index fa6abfe89..969c29688 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -1,7 +1,8 @@ #!/bin/sh # check_image_size IMAGE_REFERENCE LIMIT_GIB # -# Inspects a single-arch OCI/Docker image manifest that has already been pushed +# Inspects a single-arch OCI/Docker image manifest (or the first image +# manifest in a multi-arch OCI/Docker image) that has already been pushed # to a registry and fails with a clear message if the total compressed layer # size exceeds LIMIT_GIB gibibytes. # From 0d014b2523f2e9d44898230487d923bd3ead61f7 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 09:18:26 -0500 Subject: [PATCH 10/18] fix: suggest using script outside CI too Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .ci/check_image_size | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/check_image_size b/.ci/check_image_size index 969c29688..f08432a9b 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -64,8 +64,8 @@ if [ "${SIZE_BYTES}" -gt "${LIMIT_BYTES}" ]; then echo " ${SIZE_GIB} GiB > ${LIMIT_GIB} GiB" echo "" echo "To update the high-water mark after an intentional size increase:" - echo " 1. Inspect the published image size with:" - echo " docker buildx imagetools inspect --raw \"${IMAGE_REF}\" | jq '[.layers[].size] | add // 0'" + echo " 1. Re-run this script to inspect the published image size:" + echo " .ci/check_image_size \"${IMAGE_REF}\" \"${LIMIT_GIB}\"" echo " 2. Add ~15% buffer, round up to the next whole GiB" echo " 3. Update SIZE_LIMIT_*_GIB in .github/workflows/build-push.yml and .gitlab-ci.yml" exit 1 From db034dc5a7a9d9f9dcf2a732d4f239fe2d9e7a55 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 09:19:02 -0500 Subject: [PATCH 11/18] fix: skip provenance layers with unknown platform Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .ci/check_image_size | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.ci/check_image_size b/.ci/check_image_size index f08432a9b..ce10d560a 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -32,7 +32,14 @@ case "${MEDIA_TYPE}" in *index* | *manifest.list*) DIGEST=$(printf '%s' "${MANIFEST}" | jq -r ' .manifests[] - | select(.artifactType == null and .platform != null) + | select( + .artifactType == null + and .platform != null + and (.platform.os // "") != "" + and (.platform.architecture // "") != "" + and (.platform.os // "") != "unknown" + and (.platform.architecture // "") != "unknown" + ) | .digest' | head -1) if [ -z "${DIGEST}" ]; then echo "ERROR: OCI Image Index contains no platform manifest." From 139555f06e2878d6dffa7e2f91cc6038d47f7356 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 10:01:38 -0500 Subject: [PATCH 12/18] fix: update size calibration --- .github/workflows/build-push.yml | 7 +++---- .gitlab-ci.yml | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 70a460b73..90e171fce 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -61,10 +61,9 @@ env: ## To update: measure the compressed sizes of the published master images, ## add ~15% headroom, round up, and update here and in .gitlab-ci.yml ## variables. - ## Last calibrated: 2025-05 (debian_stable_base=0.76, eic_ci=6.93, eic_xl=8.68 GiB) - SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: 1 - SIZE_LIMIT_EIC_CI_GIB: 8 - SIZE_LIMIT_EIC_XL_GIB: 10 + SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: 0.9 + SIZE_LIMIT_EIC_CI_GIB: 3 + SIZE_LIMIT_EIC_XL_GIB: 4.5 jobs: env: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index becc4a5cf..569f8699d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,10 +34,9 @@ variables: ## To update: measure the current master-tag compressed image sizes using ## registry or CI tooling, add ~15% headroom, round up, and update here and ## in .github/workflows/build-push.yml env. - ## Last calibrated: 2025-05 (debian_stable_base=0.76, eic_ci=6.93, eic_xl=8.68 GiB) - SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: "1" - SIZE_LIMIT_EIC_CI_GIB: "8" - SIZE_LIMIT_EIC_XL_GIB: "10" + SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: "0.8" + SIZE_LIMIT_EIC_CI_GIB: "3" + SIZE_LIMIT_EIC_XL_GIB: "4.5" ## is this nightly or not? NIGHTLY: "" From d018dd2d858cf6e1497c27fdc1adb61113e9eecb Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 10:03:03 -0500 Subject: [PATCH 13/18] fix: more provenance layer handling Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .ci/check_image_size | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/check_image_size b/.ci/check_image_size index ce10d560a..6ed104aa0 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -34,6 +34,8 @@ case "${MEDIA_TYPE}" in .manifests[] | select( .artifactType == null + and ((.annotations["vnd.docker.reference.type"] // "") != "attestation-manifest") + and ((.annotations["vnd.docker.reference.digest"] // "") == "") and .platform != null and (.platform.os // "") != "" and (.platform.architecture // "") != "" From a987ba551958aa1f12db3115db5c27d1587d7834 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 10:27:11 -0500 Subject: [PATCH 14/18] fix: use consistent base size; make rounding optional Co-authored-by: Wouter Deconinck --- .ci/check_image_size | 2 +- .github/workflows/build-push.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/check_image_size b/.ci/check_image_size index 6ed104aa0..5386682b6 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -75,7 +75,7 @@ if [ "${SIZE_BYTES}" -gt "${LIMIT_BYTES}" ]; then echo "To update the high-water mark after an intentional size increase:" echo " 1. Re-run this script to inspect the published image size:" echo " .ci/check_image_size \"${IMAGE_REF}\" \"${LIMIT_GIB}\"" - echo " 2. Add ~15% buffer, round up to the next whole GiB" + echo " 2. Add ~15% buffer, and optionally round up to the next whole GiB" echo " 3. Update SIZE_LIMIT_*_GIB in .github/workflows/build-push.yml and .gitlab-ci.yml" exit 1 fi diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 90e171fce..e3a2d3b57 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -61,7 +61,7 @@ env: ## To update: measure the compressed sizes of the published master images, ## add ~15% headroom, round up, and update here and in .gitlab-ci.yml ## variables. - SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: 0.9 + SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: 0.8 SIZE_LIMIT_EIC_CI_GIB: 3 SIZE_LIMIT_EIC_XL_GIB: 4.5 From 007354cbc4f3ee0af47546d16a075a73ffdb2ac7 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 10:35:34 -0500 Subject: [PATCH 15/18] fix: use linux line endings --- .ci/check_image_size | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/check_image_size b/.ci/check_image_size index 5386682b6..b76d32565 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -75,7 +75,7 @@ if [ "${SIZE_BYTES}" -gt "${LIMIT_BYTES}" ]; then echo "To update the high-water mark after an intentional size increase:" echo " 1. Re-run this script to inspect the published image size:" echo " .ci/check_image_size \"${IMAGE_REF}\" \"${LIMIT_GIB}\"" - echo " 2. Add ~15% buffer, and optionally round up to the next whole GiB" + echo " 2. Add ~15% buffer, and optionally round up to the next whole GiB" echo " 3. Update SIZE_LIMIT_*_GIB in .github/workflows/build-push.yml and .gitlab-ci.yml" exit 1 fi From 041e4959da959cdb512b75dd6d0f4225d0c8d915 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Sat, 16 May 2026 10:39:28 -0500 Subject: [PATCH 16/18] fix: use linux line endings --- .github/workflows/build-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index e3a2d3b57..7f0492de5 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -61,7 +61,7 @@ env: ## To update: measure the compressed sizes of the published master images, ## add ~15% headroom, round up, and update here and in .gitlab-ci.yml ## variables. - SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: 0.8 + SIZE_LIMIT_DEBIAN_STABLE_BASE_GIB: 0.8 SIZE_LIMIT_EIC_CI_GIB: 3 SIZE_LIMIT_EIC_XL_GIB: 4.5 From 0234a8e7ba9f79930ffc318b29bd8510ef06e664 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Fri, 29 May 2026 18:23:12 -0500 Subject: [PATCH 17/18] fix: strip :tag before appending @digest in check_image_size When IMAGE_REF is a tag (e.g. ghcr.io/eic/eic_xl:master) and the manifest resolves to an OCI index, IMAGE_BASE was constructed as 'image:tag' and then '@digest' was appended, yielding the invalid form 'image:tag@sha256:...'. Strip :tag from the last path segment only (preserving registry host:port) before building the per-platform reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .ci/check_image_size | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.ci/check_image_size b/.ci/check_image_size index b76d32565..2935d13c4 100755 --- a/.ci/check_image_size +++ b/.ci/check_image_size @@ -47,7 +47,15 @@ case "${MEDIA_TYPE}" in echo "ERROR: OCI Image Index contains no platform manifest." exit 1 fi - IMAGE_BASE="${IMAGE_REF%@*}" + IMAGE_BASE="${IMAGE_REF%%@*}" + # Strip :tag from the last path segment only (preserves registry host:port) + _dir="${IMAGE_BASE%/*}" + _name="${IMAGE_BASE##*/}" + if [ "${_dir}" != "${IMAGE_BASE}" ]; then + IMAGE_BASE="${_dir}/${_name%%:*}" + else + IMAGE_BASE="${_name%%:*}" + fi MANIFEST=$(docker buildx imagetools inspect --raw "${IMAGE_BASE}@${DIGEST}") ;; esac From 25e667de7838bf4f99f6e89642ff100f82320dd9 Mon Sep 17 00:00:00 2001 From: Wouter Deconinck Date: Fri, 29 May 2026 18:23:16 -0500 Subject: [PATCH 18/18] fix: replace eval with printenv for indirect variable lookup eval for LIMIT_VAR -> LIMIT indirection is fragile and unnecessary. Use printenv instead, which works in Alpine's /bin/sh without requiring bash indirect expansion (${!var}). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 569f8699d..90560433e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -366,7 +366,7 @@ base: containers/debian 2>&1 | tee build.log - LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${BUILD_IMAGE}" | tr '[:lower:]' '[:upper:]')_GIB" ; - eval "LIMIT=\${${LIMIT_VAR}:-}" ; + LIMIT=$(printenv "${LIMIT_VAR}" || true) ; if [ -n "${LIMIT}" ]; then .ci/check_image_size "${CI_REGISTRY}/${CI_PROJECT_PATH}/${BUILD_IMAGE}:${INTERNAL_TAG}" @@ -524,7 +524,7 @@ eic: 2>&1 | tee build.log - FULLNAME="${BUILD_IMAGE}${ENV}" ; LIMIT_VAR="SIZE_LIMIT_$(printf '%s' "${FULLNAME}" | tr '[:lower:]' '[:upper:]')_GIB" ; - eval "LIMIT=\${${LIMIT_VAR}:-}" ; + LIMIT=$(printenv "${LIMIT_VAR}" || true) ; if [ -n "${LIMIT}" ]; then .ci/check_image_size "${CI_REGISTRY}/${CI_PROJECT_PATH}/${FULLNAME}:${INTERNAL_TAG}-${BUILD_TYPE}"