fix(devcontainer): mount the cache volumes at the roots the tools use - #231
Closed
Syndic wants to merge 2 commits into
Closed
fix(devcontainer): mount the cache volumes at the roots the tools use#231Syndic wants to merge 2 commits into
Syndic wants to merge 2 commits into
Conversation
The two named volumes each missed most of what they were meant to persist. `ud-go-cache` targeted `~/go`, but `features/go` exports `GOPATH=/go`, so the volume held an empty directory and GOMODCACHE (`/go/pkg/mod`, 530M) plus the six `go install`ed tools in `/go/bin` were discarded on every rebuild. `ud-bazel-cache` targeted `~/.cache/bazel`, one directory inside the cache root, leaving its siblings ephemeral: `go-build` (967M), `bazelisk` (61M, a fresh bazel download per rebuild), `pre-commit` (13M) and `uv`. Mount the roots instead — `ud-cache` at `~/.cache`, `ud-go-cache` at `/go` — so a tool that starts caching under XDG is covered without an edit here. Nothing under either root needs to stay ephemeral: every entry is content-addressed or key-validated by its own tool, and CI builds cold. post-create.sh's chown follows the new targets and is now guarded on current ownership, since recursing tens of GB to re-assert correct ownership on every rebuild is minutes for nothing. The guard also preserves the go feature's `vscode:golang` group on `/go`, which an unconditional chown would flatten. test_devcontainer_config.py asserts both couplings the change creates: the volume targets are exactly the two cache roots, and post-create.sh chowns exactly that set (a target with no entry mounts root-owned and unwritable). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #231 +/- ##
==========================================
+ Coverage 84.07% 84.45% +0.37%
==========================================
Files 39 40 +1
Lines 2129 2193 +64
Branches 91 99 +8
==========================================
+ Hits 1790 1852 +62
- Misses 324 325 +1
- Partials 15 16 +1
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
Review pushback on the previous commit's `/go` mount prompted a closer look at what actually lives under GOPATH, and the mount was wrong in two ways. `/go/bin` is image content: `features/go` builds ten tools there at image-build time (`/usr/local/etc/vscode-dev-containers/go.log`), of which post-create.sh reinstalls six at pinned versions. Docker seeds a named volume from the image only while the volume is empty, so a volume over `/go` freezes `bin/` at whatever the image held on first mount — a later feature bump would install tools nobody ever sees. Persisting it buys nothing either, since post-create rewrites its six on every create regardless. `/go/pkg/mod` alone is too narrow the other way: the checksum-db cache is a sibling at `/go/pkg/sumdb`, and Docker creates the `/go/pkg` mountpoint parent root-owned, so the first `go install` fails on open /go/pkg/sumdb/sum.golang.org/latest: no such file or directory `$GOPATH/pkg` is the boundary that holds: `pkg/` is the derived half of GOPATH (mod + sumdb, absent from the image — the feature purges it), `bin/` is the artifact half. Verified after recreate: `/go/bin` keeps its Aug 12 image dates for the four tools post-create doesn't install, while `mod` and `sumdb` both persist across `--remove-existing-container`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
|
Superseded by #234 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
Both named volumes missed most of what they were supposed to persist. Measured inside a running container on this branch's base:
~/.cache/bazelGOCACHE~/.cache/go-build~/.cache/bazelisk~/.cache/pre-commit~/.cache/uv$GOPATH/pkg(module + checksum-db caches)/go/pkg~/goTwo separate bugs:
ud-bazel-cachetargeted~/.cache/bazel, one directory inside the cache root, so its four siblings were container-local.ud-go-cachetargeted~/go, butfeatures/gobakesGOPATH=/gointo the image ENV (docker inspect … | grep GOPATH→GOPATH=/go).~/godoes not exist in the image at all and nothing in the container writes it.Neither fails loudly. The container just rebuilds those caches every time, which reads as "devcontainers are slow".
The fix
Two volumes, each at the root of what it persists:
ud-cache→~/.cacheud-go-pkg-cache→/go/pkgWhy the whole of
~/.cache, not per-tool volumes. Per-tool only persists what someone remembered to enumerate, which is precisely the failure being fixed —bazeliskwas silently missing and nobody noticed. Mounting the root means a tool that starts caching under XDG is covered with no edit. Nothing under it wants to stay ephemeral either: every entry is content-addressed or key-validated by its own tool, and CI builds cold, so a stale local cache can't reachmain.Why
$GOPATH/pkgand not$GOPATH./go/binis image content —features/gobuilds ten tools there at image-build time — and Docker only seeds a volume from the image while the volume is empty. A volume over all of/gowould freezebin/at whatever the image held on first mount, so a later feature bump would install tools nobody ever sees. Persisting it buys nothing anyway:post-create.shreinstalls its six pinned tools over the image's copies on every create.pkg/is the derived half of GOPATH,bin/is the artifact half.Why not
$GOMODCACHEalone. Tried it; it fails outright. The checksum-db cache is a sibling of the module cache at/go/pkg/sumdb, and Docker creates the/go/pkgmountpoint parent root-owned, so the firstgo installdies onopen /go/pkg/sumdb/sum.golang.org/latest: no such file or directory.Nesting a
~/.cachevolume under the existing~/.cache/bazelone would have worked mechanically, but leaves two volumes with overlapping meaning and no axis separating them —bazel clean --expungealready gives per-tool discard from inside the container. Soud-bazel-cacheis retired rather than kept.post-create.sh's chown follows the new targets and is now guarded on current ownership. Unconditionalchown -Rover a warm~/.cacheis minutes spent re-asserting ownership that is already correct, and it would flatten the go feature'svscode:golanggroup.Correction to an earlier claim in this PR
An earlier revision described
~/goas having "persisted an empty directory since it was added". A reviewer observed 731M and ten binaries at~/goin a different worktree's container on the same host, which looked like a direct contradiction. Reconciled — both observations are correct and neither undermines the change:ud-go-cacheat~/go. This branch's containers mounted that same volume at/go, wherepost-create.shfilled it with 540M of module cache. A volume's content is shared across containers; its mount point is per-container. So what reads as "~/gohas been doing real work" is this branch's own output arriving through the shared volume.golint,goplay,reviveandstaticcheckare exactly the four of the ten thatpost-create.shdoes not install, which is why they keep the image's date while the other six carry the recreate's. They are re-provided by the image on every rebuild — nothing is lost by not persisting them.~/gois genuinely vestigial, and this PR drops the mount rather than orphaning anything.The reviewer's
GOPATH=/go/GOMODCACHE=/go/pkg/modfinding is confirmed, and the pushback is what surfaced both the/go/binshadowing and thesumdbbreakage above.Validation
Ran in the devcontainer. Recreated with
devcontainer up --remove-existing-container; sentinel files written in the previous container survived in both volumes, and/go/binis demonstrably not shadowed:Warm-vs-cold, same commands:
go build ./...pre-commit install-hookspostCreateCommandbazel test //...— 21 tests pass.pre-commit run --all-files— all hooks pass except a pre-existingSC1091inmeta/devcontainer-base/scripts/devcontainer-plumbing.sh, untouched here.New tests are mutation-checked: widening the mount to
/go, and dropping a target from post-create's chown loop, each make//.devcontainer:test_devcontainer_configfail.Notes for the reviewer
docker volume rm ud-bazel-cache ud-go-cachereclaims the two now-unreferenced volumes. Other worktrees' running containers still hold the old mounts until they are recreated..claude/CLAUDE.mdgains a "Devcontainer cache volumes" section — expect a possible trivial conflict with ci(bazel): namespace the repository cache per workflow #229, which also appends to that file.🤖 Generated with Claude Code