improvement(ci): measure where the layer-cache disk actually goes - #7521
improvement(ci): measure where the layer-cache disk actually goes#7521waleedlatif1 wants to merge 1 commit into
Conversation
The layer cache cap is enforced against `buildctl du`, and on the busiest disk
that number no longer describes the disk. In the same job, the app image's amd64
sticky disk reports 224.9 GB while du reads 65.87 GB — so the prune correctly
does nothing, being already under its 100 GB cap by its own accounting, and the
disk keeps growing. The same Dockerfile's arm64 disk, which builds far less
often, reads 88.04 GB against an 86.4 GB disk: no gap at all. The divergence
tracks build frequency, not image content.
The mount log ("Filesystem free space after mount") shows the filesystem really
is holding those bytes, so this is not a thin-provisioned volume failing to
release freed blocks — there are real files that du's total does not cover. The
`RUN --mount=type=cache` dirs (apt, bun, npm, next, turbo) are the candidates,
being a different record class from the layer cache `--keep-storage` trims.
Those two causes need opposite fixes, and neither is proven from the logs
available today, so this adds the three readings that separate them rather than
a speculative fix: df for the filesystem's own view, a du summary by record type
for whether cache mounts are accounted at all, and a bounded directory walk to
locate the bytes if they are not.
All best-effort and guarded. Composite steps run under `bash -e -o pipefail`,
where a failing command in an assignment or pipeline aborts the step and fails
the build — the bug that broke an app image build in #7252. Verified by running
this block under those exact flags with every command failing: it reaches the end
and exits 0. Shellcheck clean.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds bounded, best-effort BuildKit disk-accounting diagnostics after layer-cache pruning.
Confidence Score: 4/5The PR appears safe to merge operationally, but its diagnostic output and explanatory comment should be corrected so the investigation produces reliable conclusions. The commands remain bounded and cannot fail the deployment step, but the record-type aggregation reports near-zero totals for human-readable BuildKit sizes, and the surrounding explanation overstates what existing evidence proves. Files Needing Attention: .github/actions/docker-build/action.yml
|
| Filename | Overview |
|---|---|
| .github/actions/docker-build/action.yml | Adds non-failing disk diagnostics, but the record-size aggregation mishandles BuildKit’s unit-suffixed output and an explanatory comment prematurely rules out one investigated cause. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Layer-cache prune completes] --> B[df /var/lib/buildkit]
B --> C[buildctl du --verbose]
C --> D[Aggregate sizes by record type]
D --> E[Bounded du directory walk]
E --> F[Continue Docker build setup]
Reviews (1): Last reviewed commit: "improvement(ci): measure where the layer..." | Re-trigger Greptile
| df -h /var/lib/buildkit 2>/dev/null || true | ||
| echo "du by record type:" | ||
| sudo buildctl --addr "$addr" du --verbose 2>/dev/null \ | ||
| | awk '/^Type:/ { t=$2 } /^Size:/ { s[t] += $2 } END { for (k in s) printf " %-24s %10.2f GB\n", k, s[k]/1e9 }' \ |
There was a problem hiding this comment.
buildctl du --verbose emits human-readable Size: values such as 65.87GB. Awk converts $2 to only the leading number before dividing it by 1e9, so non-empty record types appear as approximately 0.00 GB. This makes the new diagnostic unable to show which record types consume the cache disk.
| # The mount log ("Filesystem free space after mount") says the filesystem | ||
| # really is holding those bytes, so this is not a thin-provisioned volume | ||
| # failing to release freed blocks — there are real files here that du's | ||
| # total does not cover. The candidates are the `RUN --mount=type=cache` | ||
| # dirs (apt, bun, npm, next, turbo), which are a different record class | ||
| # from the layer cache `--keep-storage` trims. |
There was a problem hiding this comment.
This explanation says thin provisioning has been ruled out, although the free-space reading is only indirect evidence. That could steer the follow-up investigation toward deleting cache mounts instead of also evaluating discard behavior. Please preserve both possible causes until the new diagnostics distinguish them.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
The problem
The layer-cache cap is enforced against
buildctl du, and on the busiest disk that number no longer describes the disk.From a single real job (app image, amd64):
The prune is behaving correctly — 65.87 GB is already under the 100 GB cap, so there is nothing to trim. Meanwhile that sticky disk reports 224.9 GB.
The control is the same Dockerfile on the other architecture:
buildctl duapp.Dockerfile/linux-amd64app.Dockerfile/linux-arm64Same image, same 100 GB cap, both disks created 2026-08-29. The only difference is build frequency — amd64 builds on every push to
mainandstaging, arm64 only onmain. So the divergence tracks write churn, not image content.Why this is a diagnostic and not a fix
Two causes fit the symptom, and they need opposite fixes:
discardwould grow monotonically. Fix would befstrim.du's total does not cover. TheRUN --mount=type=cachedirs (apt ×2, bun, npm, next, turbo) are a different record class from the layer cache--keep-storagetrims. Fix would be bounding or dropping specific mounts.One log line argues against (1) —
Filesystem free space after mount: 527497363456 bytes (491.27 GiB)implies the filesystem really is holding those bytes — but that is a single indirect reading, not proof, and shippingfstrimor deleting a cache mount on it would be guessing.So this adds the three readings that separate the cases:
df— the filesystem's own view, versus buildkit's.buildctl du --verbosesummarised by record type — whether cache mounts are accounted for at all.du -x -d1walk — where the bytes actually live, if they are not.One or two builds of output settles it, and the follow-up fix is then evidenced rather than assumed.
Safety
Composite steps run under
bash -e -o pipefail, where a failing command inside an assignment or pipeline aborts the step and fails the build — the bug that broke a real app image build in #7252. Every command here is guarded, and the directory walk is bounded bytimeout 120.Verified rather than assumed: the block was run under
bash --noprofile --norc -e -o pipefailwith every command failing (no buildkitd, no/var/lib/buildkitlocally). It reaches the end and exits 0. Shellcheck clean.This step already could not fail a deploy by design — that property is preserved.
Type of Change
Testing
Executed the block under the exact composite-step shell flags with all commands failing (exit 0, reached end marker). Shellcheck clean. No behaviour change to the prune itself — this is additive output after the existing before/after readings.
Checklist