ci: split the Docker restore into its own cacheable layer - #12393
ci: split the Docker restore into its own cacheable layer#12393AnkushinDaniil wants to merge 4 commits into
Conversation
|
Claude finished @AnkushinDaniil's task in 2m 42s —— View job PR Review: split Docker restore into its own cacheable layer
VerdictLooks correct and the caching rationale is sound. The restore command (
No Critical / High / Medium findings. A couple of low-severity notes below. FindingsLow — restore-time import Low — BuildKit dependency. The Low — consistency with sibling Dockerfiles. Nice, minimal change — nothing blocking merge. |
|
Claude finished @AnkushinDaniil's task in 6m 24s —— View job PR Review: split the Docker restore into its own cacheable layer
VerdictThe restructure is correct, and the copy set is complete — I verified it mechanically rather than by inspection:
CI already validates this end-to-end at the current head ( One upside the PR description doesn't claim: in 1 Medium, 2 Low. No Critical/High. The Medium is a one-line fix and isn't a correctness problem. FindingsMedium — Low — the copy list is a hand-maintained allowlist tracking Runner's restore graph. Complete today (table above), but it will break on a cold build if a project in that closure ever imports a non- Low — the restore layer over-invalidates. The glob copies all 156 csproj, including ~70 test projects that are never evaluated during Runner's restore, so editing any test csproj busts the restore layer for no reason. Narrowing the glob would be more fragile than the cache win is worth, so the superset is the right call — just noting the cache is less sticky in practice than the "keyed only on project/lock files" framing suggests, since test-project churn is common here. Nothing else. The diff is minimal, the restore/publish commands are unchanged from |
rubo
left a comment
There was a problem hiding this comment.
While I appreciate the intention, the current disadvantages outweigh the benefits:
- Doesn't actually help CI -- every build site runs on ephemeral runners with no caching configured, so the restore layer never persists across runs. It's mostly helpful for local builds (and not even for everyone).
- Even with caching enabled, there are pitfalls: i) silent wrong-cache hits if a restore-affecting file is added/removed without updating the hand-maintained copy list; ii) with the GitHub Actions cache backend, a full .NET build can blow past the 10 GB limit and thrash, especially multi-arch.
- Requiring the labs frontend is not only error-prone for the (minority of) folks without BuildKit, but also adds an extra Docker Hub image pull at parse time on every build -- increasing the chance of hitting rate limits.
With this said, I'd hold this for now.
Changes
Restructure the build stage of
Dockerfilesodotnet restoreruns in its own layer, before the sources are copied:global.json,nuget.config, the rootDirectory.Build.props/Directory.Build.targets/Directory.Packages.props(CPM), and (viaCOPY --parents) every*.csproj, thesrc/Nethermindbuild props, andNethermind.Runner/packages.lock.json.RUN dotnet restore --locked-mode.COPY src/Nethermindanddotnet publish --no-restore.Adds the
# syntax=docker/dockerfile:1.7-labsdirective (required forCOPY --parents, which preserves the source paths of the copied project files). No descriptive code comments — rationale is here.Why
On
masterthe whole source tree is copied beforedotnet restore, so the restore layer's cache key busts on any source change — a one-line edit re-resolves and re-downloads every NuGet package. Splitting restore into a layer keyed only on the project/lock files lets a source-only change reuse it.Verified locally (Docker 29.4)
buildstage succeeds — the--parentscopy set is complete forrestore --locked-mode+publish(build stage ≈ 110s compute here).COPY src(Setup Constantinople branch and tests #12), so a source-only change busts only Setup Constantinople branch and tests #12/EIP-145 #13 (publish) and reuses the cached restore — skipping the NuGet graph resolution + package download (~30–60s) on every rebuild.Where it pays off
Immediately on runners with a persistent layer cache — e.g. the self-hosted
ubuntu-arm64-8-coreused bysync-pr-gate, whose image build already completes in ~1 min from a warm cache. Follow-up: to extend the benefit to ephemeral GitHub-hosted runners (e.g.trivy), wirecache-to/from: type=ghainto the buildx image builds — left out here to keep this change to the release-criticalDockerfilealone.Types of changes
Testing
Requires testing
Notes on testing
Verified with a local
docker build --target build(cold success + cached rebuild layer inspection, above). CI builds the full image on this PR viatrivyandsync-pr-gate, which validate the change end-to-end. The published image is byte-equivalent — only the layer ordering changed;restore --locked-modeandpublishare unchanged.Documentation
Requires explanation in Release Notes
Review follow-ups