Skip to content

Share one pixi package cache across containers: --mount + PIXI_CACHE_DIR from workspace_up #232

Closed

Description

@blooop

Graduated from map blooop/dotfiles#13's pixi-cache fog item after a measured investigation (2026-08-15). Owner: devlaunch alone — dotfiles need no edit, no repo's devcontainer.json is involved.

Measured case

The container profile's real workload is 23 pixi-global envs. pixi global sync with a scratch PIXI_CACHE_DIR/PIXI_HOME:

  • COLD (empty cache): 62–113 s, 1.2 GB downloaded
  • WARM (fresh PIXI_HOME, pre-warmed cache): 18–28 s
  • NO-OP (envs present): 0.6 s

So a shared cache saves ~40–90 s and 1.2 GB of network per cold container, compounding with wayfinder's N-containers-per-map pattern. wayfinder#39 already names dotfiles/tool provisioning the dominant cold-launch cost; this is that cost's biggest slice.

Facts the design leans on (verified)

  • The shareable part is exactly ~/.cache/rattler/cache (content-addressed pkgs/, per-package .lock files). Envs and trampolines are prefix-baked and must stay per-container.
  • PIXI_CACHE_DIR is honored and wins over RATTLER_CACHE_DIR (pixi 0.76.1, verified via pixi info) — so the dotfiles install.sh's conditional RATTLER_CACHE_DIR fallback is harmlessly overridden; an inherited env var is consumed as-is with no dotfiles change.
  • Concurrency: safe. Two simultaneous pixi global sync runs sharing one fresh cache (separate PIXI_HOMEs) → both exit 0, all binaries work. rattler locks per package. The known bug (Error: failed to collect prefix records during concurrent execution on HPC (SLURM + Lustre) prefix-dev/pixi#5476) is a shared env prefix, not a shared cache — it argues for exactly this split. Never share PIXI_HOME/envs across containers.
  • devpod up takes --mount type=bind,source=…,target=… and --workspace-env/--dotfiles-script-env first-class; workspace_up() (dl.py ~3495) already appends --init-env/--dotfiles/--workspace-env-file, so this is two more entries at the one argv seam.
  • uid: containers write as vscode (uid 1000 = host user here), but that is not guaranteed for other remoteUsers — hence a dedicated host dir (~/.cache/devlaunch/pixi), never the host's real ~/.cache/rattler/cache; this also keeps a host pixi clean cache from yanking packages under a live container, and makes GC trivially safe (rm -rf of a pure cache).
  • Cross-filesystem hardlink fallback is a copy: measured 20.7 s for 1.2 GB — still far cheaper than downloading.

Done when

  1. workspace_up() appends --mount type=bind,source=$HOME/.cache/devlaunch/pixi,target=/home/vscode/.cache/devlaunch-pixi, --workspace-env PIXI_CACHE_DIR=/home/vscode/.cache/devlaunch-pixi, and the matching --dotfiles-script-env (the dotfiles install script is the consumer).
  2. Spawn-count/argv pins updated at the same seam the existing up-args tests use.
  3. Live acceptance: two back-to-back dl launches of different repos on a clean scratch cache — the second one's pixi global sync finishes <30 s with no network fetch; two dl up in parallel leave both containers with working rg/go/jq.
  4. README notes the shared cache dir and that deleting it is always safe.

Recorded alternatives / falsification

  • Not chosen: sharing a whole PIXI_HOME volume (~0.6 s installs) — puts concurrent syncs on one prefix tree, which is exactly pixi#5476. Do not.
  • Falsification condition: if the base image ever bakes the pixi manifest in, the delta collapses toward the 0.6 s no-op number and this mount stops earning its keep — reopen then.
  • GC story for the dedicated dir is deliberately out of scope (it is a pure cache; rm -rf is the GC).

This ticket was generated by AI during investigation follow-through; measurements above were taken live on 2026-08-15.

Activity

  1. self-assigned this
    on Aug 15, 2026
  2. blooop commented on Aug 15, 2026

    @blooop
    OwnerAuthor

    breadcrumb: building this ticket. Restating the slice: the one devpod up argv seam grows three entries — a bind mount of a dedicated host cache dir onto a container-side path, --workspace-env PIXI_CACHE_DIR=<that path>, and the same assignment repeated as --dotfiles-script-env because the dotfiles install script is a separate env scope from the workspace. The host source directory is created by dl before devpod is spawned rather than left to whichever runtime is underneath, so ownership lands on the invoking user instead of root.

    Seams I will test at, all pre-agreed by the ticket:

    1. The up-argv seam — the existing helper that picks the up invocation out of everything a launch spawns, patched at run_devpod. New assertions in the same class idiom as the identity/ide/devcontainer-path pins that already live there.
    2. The cold-launch spawn/argv pin — the one test that asserts the whole up argv by equality rather than by membership. It is the pin that would otherwise silently absorb three new flags, so it is where red shows first.
    3. The host directory on disk — observed as a real directory under a scoped cache home, not as a mock call, since the property is "it exists and the invoking user owns it" and a mock cannot hold that.

    Host path is derived from the same cache-home resolver the rest of dl's storage already goes through, so it follows XDG_CACHE_HOME and no /home/<user> is written down anywhere.

  3. blooop commented on Aug 15, 2026

    @blooop
    OwnerAuthor

    breadcrumb: PR #236 opens this. The up argv now carries the bind mount and the same PIXI_CACHE_DIR assignment twice — once for the workspace, once for the dotfiles install script, which is the scope the sync actually runs in.

    One thing checked rather than assumed, and it changed the shape of the change: the container runtime refuses a bind mount whose source directory does not exist, rather than creating it. So creating the host directory before spawning is not a nicety — without it the first launch on any fresh machine fails outright. It is created through the same cache resolver the rest of dl's storage uses, so it follows a scoped cache home and the purge already knows about it. A directory that cannot be created drops the three flags and lets the launch proceed, which lands the container back on exactly the behaviour it had before this existed.

    Live acceptance ran in full on real containers. Second container's sync finished in 16 s having pulled 13 KB, against 667 MB for the first — comfortably inside the ticket's bound. Two parallel launches both succeeded and, syncing simultaneously against one emptied cache, downloaded 666 MB between them rather than each; every tool worked in all four containers afterwards. Deleting the whole cache directory under two live containers left both entirely unharmed, which is the safety claim tested rather than asserted.

    Two things a reviewer should weigh, both written up on the PR. The dotfiles install script recursively chowns $HOME/.cache when it finds it root-owned, and the mount target sits inside that — benign at the uid seen here and confined to a pure cache, but a genuine cross-container side effect of the settled target path. And the container profile's applied package manifest currently contains only chezmoi, so a stock launch would have measured nothing; the numbers above come from a seeded manifest at roughly half the scale the ticket measured. The mechanism is proven; the win is latent until that manifest carries the real workload again.

  4. blooop commented on Aug 15, 2026

    @blooop
    OwnerAuthor

    breadcrumb: PR #236 is open and fully green — every CI matrix leg, e2e, prek and coverage. Build stage ends here; the next move is review in fresh context. The slice: every container dl creates is now put on one shared, dedicated host directory of downloaded pixi packages, with the cache path delivered to both the workspace and the dotfiles install script, and the host directory created before the spawn because the runtime will not create it.

  5. blooop commented on Aug 15, 2026

    @blooop
    OwnerAuthor

    This was generated by AI during review.

    Review complete on PR #236: Approve on both axes (Standards, Spec). All three argv entries and the load-bearing mkdir mutation-verified; live acceptance audited (the parallel-fetch arithmetic — 666MB between two containers vs 667MB for one — is the convincing evidence). Four residues fixed during review (f73ff26). Known caveat, recorded on map blooop/dotfiles#13: the stock container manifest is chezmoi-only today, so the win is latent until the manifest carries real packages. #236

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions