Advanced usage
Observe mode: building an allowlist#
msb doesn't record what it denies, only what it allows. So the way to learn what a sandbox actually needs is a round trip:
$ msbctl observe . on --rebuild # stop blocking, start recording
$ # … use the sandbox normally for a while …
$ msbctl observe . # every host it reached, and which no rule covers
$ msbctl allow . <the ones that belong>
$ msbctl observe . off --rebuild # back to deny-by-defaultWhile observe mode is on, the sandbox is created with egress allowed by default, no --net-rule at all, debug logging and DNS rebind protection off: nothing is blocked and every host is written to the sandbox's runtime log. Your rules stay in the config and keep being curated; they're just not emitted. The report prints a ready-made msbctl allow line for whatever no rule covers.
The mode lives in the registry entry only, never in .msb/sandbox.toml. That file is committed, and an unfiltered sandbox must never ship to everyone who clones the project.
The report also shows secret-policy blocks: requests msb killed because they carried a placeholder somewhere substitution isn't allowed. Those are logged even in normal (enforce) mode.
Package caches#
Each package manager can get its own disk for its download cache: npm, bun, python (pip), rust (cargo registry) and go (module cache).
[caches]
npm = "5G"Each is a named disk volume, <sandbox>-cache-<key>. It's as fast as the root disk, capped at its size, kept across rebuilds and deleted by purge.
$ msbctl cache . # usage per cache
$ msbctl cache . clear npm # empty one (all of them without a key)A volume's size is fixed when it's first created. Change it in the config later and msbctl keeps the existing size and warns you; msbctl cache . clear then recreates it at the new size.
Containers inside a sandbox (podman)#
The podman feature installs podman and opens the containers egress group. podman then needs somewhere to keep images. The root filesystem is an overlay that podman's overlay driver can't sit on, so pick one of:
- a container disk (
containers_disk = "20G"): a named volume at/var/lib/containers. Fast, and pulled images survive rebuilds. This is the recommended option. - fuse-overlayfs (
containers_storage = "fuse-overlayfs"under[bootstrap], plus the package): no extra disk, slower.
With neither, podman falls back to vfs: slow, and about three times the disk. The wizard asks.
Resizing without a rebuild#
$ msbctl resize . --cpus 8 --memory 16G # needs a restart; asks first
$ msbctl resize . --root-disk 32G # grows live; shrinking is refusedresize uses msb modify, so the sandbox keeps its state. The new values are written to the registry entry, so a later rebuild reproduces them.
Versions and updates#
Component versions are pinned in defaults.toml under [versions] (node's major, gh, gitleaks, bun), and the image is pinned by digest.
$ msbctl ls --refresh # check upstream (cached for 6 hours)
$ msbctl update . -c claude gh apt # in placeupdate records what it installed in the registry entry's [versions] table, so the next rebuild reproduces it rather than silently going back. Delete an entry there to return to the default. The image can't be updated in place: change its digest in .msb/dev.yaml, then rebuild.
The upstream checks use the anonymous GitHub API (60 requests an hour). A GH_TOKEN in secrets/global.env is used for them if present.
Renaming a sandbox, or moving its project#
$ msbctl rename old-name new-name
$ msbctl move . ~/src/the-new-placemsb can rename neither a sandbox nor a volume, so a rename removes the VM and creates it again under the new name, at the cost of a rebuild. Everything msbctl keeps under the name moves with it: the registry entry, its secrets, the Claude state and the version cache. Package caches and container images are kept too: the entry's volumes key keeps pointing at the existing disks, and that old name stays reserved until you rename back.
A move points the sandbox at a different folder, for example after you moved the checkout on disk. The old folder may already be gone. If the new folder has no .msb/, msbctl offers to copy it over, re-detects the GitHub repo, and offers the rebuild the new mount needs.
Both are also in msbctl edit → "Name & folder".
Extra folders#
$ msbctl mount . ~/reference # read-only at /mnt/reference
$ msbctl mount . ~/datasets:/data --rw --rebuild # read-write, at /data, nowExtra host folders are read-only unless you ask, and read-write ones show your own ownership, like /work. Host paths are machine-local, so they're stored in the registry entry, never in the project. Like every mount, they're fixed at create time.
Project setup and environment#
[bootstrap]
packages = ["postgresql-client"] # extra apt packages
script = "scripts/sandbox-setup.sh" # run last, inside the guest, from /work
[env]
SOME_PROJECT_FLAG = "1" # merged over the machine-wide [env]The setup script runs once, at create time, after everything else, for things like npm ci. A failure is reported, not fatal. Environment variables are create-time, like mounts.
Your network#
For hosts on your LAN (a Git server, a package mirror), define a group once in config.toml, or use msbctl setup → "your network". If those names resolve to private addresses, list the group under private_dns_groups, so projects that use it get allow_private_dns = true automatically. See DNS resolution is part of the rule.
Your CLAUDE.md additions#
~/.config/msb/CLAUDE.local.md is appended to msb-manager's shipped instructions, and the result is copied into every sandbox's ~/.claude on each start. Edit it from the main menu ("edit your CLAUDE.md additions") or directly. Editing the copy inside a sandbox does nothing: it's overwritten on the next start.
Autostart#
Mark a sandbox to start whenever the picker opens: msbctl edit . → "Identity & autostart". An idle sandbox costs about 300 MB, and a running one is the only kind whose installed versions can be checked.