From 4b4bf29dfcc15878c2c4d3e2bba19c1f4f63b20f Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 27 Jul 2026 15:38:11 +0200 Subject: [PATCH] Store RHEL8 image layer cache in GHCR instead of the Actions cache The nightly image build has failed since 2026-07-23 with "failed to solve: not_found" while exporting the BuildKit layer cache, because the repository's GitHub Actions cache is at the 10 GB limit and entries are evicted as fast as they are written (0% cache hit rate on restore). Use a registry cache (ci-rhel8:buildcache in GHCR) instead, which has no size cap, and exclude the buildcache tag from the daily image cleanup. Make the cache effective across nightly rebuilds: key the vcpkg stage on its actual inputs (vcpkg.json, the vcpkg configuration and the vcpkg tool) with a direct manifest install instead of copying the entire build context, and exclude .git from the Docker build context since its internals differ between checkouts of the same commit and invalidated 'COPY . /src' on every run. Nightly builds now reuse the compiled vcpkg ports from the layer cache and only recompile the ResInsight warmup stage. --- .dockerignore | 8 +++++ .github/docker/Dockerfile.rhel8 | 38 +++++++++++------------ .github/workflows/build-rhel8-image.yml | 10 ++++-- .github/workflows/cleanup-rhel8-image.yml | 7 +++-- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/.dockerignore b/.dockerignore index f40b94f7b7..6ea3cf4cc2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,14 @@ # Docker contexts -- this file is the canonical list of paths excluded from # `COPY . /src` (used by .github/docker/Dockerfile.rhel8). +# Git metadata. The in-image build does not need it (the CMake submodule init +# is guarded by EXISTS .git, and the submodule working trees are already in +# the context), and its internals (index, packfiles, reflogs) differ between +# checkouts of the same commit, which would invalidate the BuildKit layer +# cache for `COPY . /src` on every nightly image build. +.git +**/.git + # Local CMake build outputs (host-side; can be tens of GB). build/ build_*/ diff --git a/.github/docker/Dockerfile.rhel8 b/.github/docker/Dockerfile.rhel8 index dc5e506ef4..1861014a71 100644 --- a/.github/docker/Dockerfile.rhel8 +++ b/.github/docker/Dockerfile.rhel8 @@ -104,41 +104,39 @@ FROM base AS vcpkg-builder ENV VCPKG_DEFAULT_BINARY_CACHE=/opt/vcpkg-cache -COPY . /src +# Copy only the inputs that determine the dependency set instead of the whole +# source tree. This keys the expensive vcpkg compile below on just these +# paths, so the layer stays valid in the BuildKit registry cache across +# ordinary source commits -- nightly rebuilds reuse the previously compiled +# vcpkg binaries instead of recompiling every port. +COPY ThirdParty/vcpkg /src/ThirdParty/vcpkg +COPY vcpkg.json /src/vcpkg.json +COPY vcpkg-configuration-rhel8.json /src/vcpkg-configuration.json -# Running the ResInsight CMake configure triggers a vcpkg manifest install, -# which compiles every dependency into VCPKG_DEFAULT_BINARY_CACHE. The same -# toolchain is used here and in the final image, so the vcpkg ABI hashes match -# and the cache is reused by CI builds. Later configure steps may fail inside -# the build container; that is tolerated as the dependencies are already built -# by then. +# A direct manifest install compiles every dependency into +# VCPKG_DEFAULT_BINARY_CACHE. The dependency set has no manifest features or +# overlay ports, and CC/CXX below match the ResInsight configure in the +# warmup stage and in CI, so the vcpkg ABI hashes match and the cache is +# reused there. x64-linux is the triplet the vcpkg CMake toolchain defaults +# to on Linux. # -# After configure, verify the cache contains a plausible number of entries. +# After the install, verify the cache contains a plausible number of entries. # vcpkg's `files` binary provider stores one .zip per package under # /<2-hex>/.zip. ResInsight depends on ~30+ packages, so a -# threshold of 20 catches the failure mode where the configure dies early +# threshold of 20 catches the failure mode where the install dies early # (e.g. after building only a handful of leaf deps) while staying robust to # small manifest churn. RUN git config --global --add safe.directory '*' \ - && cp /src/vcpkg-configuration-rhel8.json /src/vcpkg-configuration.json \ && mkdir -p "${VCPKG_DEFAULT_BINARY_CACHE}" \ && source /opt/rh/gcc-toolset-14/enable \ && ( cd /src/ThirdParty/vcpkg && ./bootstrap-vcpkg.sh ) \ && export CC=/opt/rh/gcc-toolset-14/root/usr/bin/gcc \ && export CXX=/opt/rh/gcc-toolset-14/root/usr/bin/g++ \ - && { cmake -S /src -B /tmp/cfg -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DRESINSIGHT_INCLUDE_APPLICATION_UNIT_TESTS=true \ - -DRESINSIGHT_ENABLE_UNITY_BUILD=true \ - -DRESINSIGHT_ENABLE_HDF5=false \ - -DRESINSIGHT_ENABLE_GRPC=false \ - -DRESINSIGHT_GCC_STDCPP_EXP_PATH=/opt/libstdcxx-exp/lib \ - -DCMAKE_TOOLCHAIN_FILE=ThirdParty/vcpkg/scripts/buildsystems/vcpkg.cmake \ - || true; } \ + && ( cd /src && ./ThirdParty/vcpkg/vcpkg install --triplet x64-linux ) \ && vcpkg_zip_count=$(find "${VCPKG_DEFAULT_BINARY_CACHE}" -type f -name '*.zip' | wc -l) \ && echo "vcpkg binary cache entries: ${vcpkg_zip_count}" \ && test "${vcpkg_zip_count}" -ge 20 \ - && rm -rf /tmp/cfg /src + && rm -rf /src # ----------------------------------------------------------------------------- # Stage 3: warm the ResInsight buildcache by performing a full compile at diff --git a/.github/workflows/build-rhel8-image.yml b/.github/workflows/build-rhel8-image.yml index b4dbd34ffe..c0b4169c24 100644 --- a/.github/workflows/build-rhel8-image.yml +++ b/.github/workflows/build-rhel8-image.yml @@ -12,6 +12,7 @@ on: paths: - ".github/docker/Dockerfile.rhel8" - ".github/workflows/build-rhel8-image.yml" + - ".dockerignore" - "vcpkg.json" - "vcpkg-configuration-rhel8.json" - ".gitmodules" @@ -102,5 +103,10 @@ jobs: tags: | ${{ steps.meta.outputs.image }}:latest ${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.date }} - cache-from: type=gha - cache-to: type=gha,mode=max + # Layer cache lives in GHCR instead of the GitHub Actions cache: the + # Actions cache has a 10 GB per-repo limit shared with all other + # workflows, so the multi-GB mode=max export was evicted immediately + # (0% hit rate) and the export itself started failing with + # "not_found" once the repo hit the cap. + cache-from: type=registry,ref=${{ steps.meta.outputs.image }}:buildcache + cache-to: type=registry,ref=${{ steps.meta.outputs.image }}:buildcache,mode=max,image-manifest=true diff --git a/.github/workflows/cleanup-rhel8-image.yml b/.github/workflows/cleanup-rhel8-image.yml index e456e55f84..a0882fe638 100644 --- a/.github/workflows/cleanup-rhel8-image.yml +++ b/.github/workflows/cleanup-rhel8-image.yml @@ -4,7 +4,8 @@ name: Cleanup RHEL8 CI Image Versions # # Retention rules: # * Versions tagged "latest" are always kept (the consumer workflow pulls -# `:latest`, so deleting it would break CI). +# `:latest`, so deleting it would break CI). Versions tagged "buildcache" +# are always kept too (BuildKit layer cache used by the image build). # * The most recent MIN_KEEP versions by updated_at are kept regardless of # age. This is a safety floor: if the last few nightly builds break, we # can still roll back to a working older image. @@ -95,14 +96,14 @@ jobs: echo "Cutoff (UTC) : $(date -u -d @"$cutoff_epoch" -Iseconds)" # jq pipeline: - # 1. drop versions whose tags include "latest" + # 1. drop versions whose tags include "latest" or "buildcache" # 2. sort newest-first # 3. skip the first MIN_KEEP (safety floor) # 4. keep only those older than the cutoff to_delete=$(jq -c \ --argjson keep "$MIN_KEEP" \ --argjson cutoff "$cutoff_epoch" ' - [ .[] | select((.metadata.container.tags // []) | index("latest") | not) ] + [ .[] | select((.metadata.container.tags // []) | (index("latest") or index("buildcache")) | not) ] | sort_by(.updated_at) | reverse | .[$keep:] | [ .[] | select((.updated_at | fromdateiso8601) < $cutoff) ]