Caching with named volumes
Share package caches, model weights, and build artifacts across sandboxes without rebuilding snapshots.
Snapshots bake everything in; once built, they’re immutable. But state that should change between launches — package caches, model weights, CI build artifacts — doesn’t belong in the snapshot. That’s what named volumes are for.
When to reach for a volume
| Goal | Approach |
|---|---|
Speed up parallel npm install runs | Mount a shared npm-cache volume at /root/.npm |
| Avoid re-downloading model weights on every launch | Mount a hf-cache volume at /root/.cache/huggingface |
| Hand build artifacts from one sandbox to another | Both sandboxes mount the same volume |
| Persist long-running scratch data | Mount once, write, mount from the next sandbox |
Not for:
- Shipping local source code in. Use a bind-mount (string value in
mounts) — volumes are managed by the runtime and can’t be edited from the host easily. - Sharing read-only deps that never change. Bake them into the snapshot.
Pattern 1: Shared package cache
import { runInSandbox } from "@beamhop/lightbox";
async function runTest(name: string) {
return runInSandbox(
{
snapshot: "node-ci",
name: `test-${name}`,
mounts: {
"/work": "./my-project",
"/root/.npm": { volume: "npm-cache" }, // shared between all tests
},
workdir: "/work",
},
(sb) => sb.shell("npm ci && npm test"),
);
}
await Promise.all(["unit", "integration", "e2e"].map(runTest));
The first job warms the npm-cache volume; the next two hit the cache cold. The volume is auto-created on first use.
Pattern 2: Producer / consumer
A snapshot bakes the build toolchain. A “builder” sandbox writes artifacts to a volume. A “runtime” sandbox reads them.
import { runInSandbox } from "@beamhop/lightbox";
// Step 1: build into the volume
await runInSandbox(
{
snapshot: "rust-toolchain",
name: "build",
mounts: {
"/src": "./my-rust-project",
"/artifacts": { volume: "rust-artifacts" },
},
workdir: "/src",
},
(sb) => sb.shell("cargo build --release && cp target/release/myapp /artifacts/"),
);
// Step 2: run from the artifacts (different snapshot, same volume)
await runInSandbox(
{
snapshot: "minimal-runtime",
name: "run",
mounts: { "/app": { volume: "rust-artifacts" } },
},
(sb) => sb.exec("/app/myapp", ["--version"]),
);
Pattern 3: Pre-warm before scaling
If you’ll launch many sandboxes that all want the same cache, prime it once before going parallel — otherwise you race on the first cold population.
import { runInSandbox, launchSandbox } from "@beamhop/lightbox";
// Prime the cache (single sandbox).
await runInSandbox(
{
snapshot: "py-ml",
name: "prime",
mounts: { "/root/.cache/huggingface": { volume: "hf-cache" } },
},
(sb) => sb.shell("python -c \"from transformers import AutoModel; AutoModel.from_pretrained('bert-base-uncased')\""),
);
// Now fan out — every sandbox reuses the warm cache.
await Promise.all(
Array.from({ length: 10 }, (_, i) => i).map(async (i) => {
const sb = await launchSandbox({
snapshot: "py-ml",
name: `worker-${i}`,
mounts: { "/root/.cache/huggingface": { volume: "hf-cache" } },
});
await sb.detach();
}),
);
Configuring quotas and labels
For ad-hoc use, the auto-create-on-mount default is fine. For production caches you usually want a quota (so a runaway job doesn’t fill the disk) and labels (so you can sweep them later).
import { ensureVolume } from "@beamhop/lightbox";
await ensureVolume("npm-cache", {
quotaMib: 4096,
labels: { team: "platform", purpose: "ci-cache", ttl: "30d" },
});
ensureVolume is idempotent — calling it on an existing volume is a no-op. Quota and labels are only applied on initial creation; to change them you must remove + recreate (see Troubleshooting).
Cleanup
Volumes persist until removed. List them with listVolumes(), remove with removeVolume(name). Removing a volume deletes its contents.
import { listVolumes, removeVolume } from "@beamhop/lightbox";
const stale = (await listVolumes())
.filter((v) => v.labels.ttl === "30d" && isOlderThan30Days(v.createdAt));
for (const v of stale) {
await removeVolume(v.name);
}
See also
- Mounts example — bind + named in one launch
launchSandboxmounts — the option reference- Volume lifecycle —
ensureVolume,listVolumes,removeVolume