Work out why a Docker Sandbox cannot reach a host, install a package, keep a file, or start at all. Use when a download or clone fails inside sbx, a credential is rejected, a sandbox is killed or stops on its own, or something written inside it disappeared.
72
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Start here, because it settles most of them in one command:
sbx policy log # what was blocked, and by which rule
sbx diagnose # everything needed to file a good bugFixing what you find usually means editing a kit — see creating-sbx-kits —
or the image, see creating-sbx-templates.
Every packet leaves through a host-side proxy that enforces policy and injects credentials. Configuring the network is configuring the proxy, and most network problems are proxy problems.
A failed install or download is almost always a blocked domain. Blocking surfaces as HTTP 403 with a structured reason in the body, not as a TCP error. Read the body before theorising.
| Symptom | Cause |
|---|---|
git clone fails, image looks broken | Only github.com allowed. Six hosts are needed — api., raw., objects., codeload., release-assets. |
| Install succeeds but the binary is missing | curl | bash swallowed a 403. Bash exits 0 on empty stdin |
| API rejects a valid key | The client bypassed the proxy and sent the raw sentinel |
| A host service is unreachable | Allowlist it as localhost, then address host.docker.internal — the proxy translates |
| Allowlisted an IP, still blocked | Akamai and friends rotate IPs. Allowlist the domain |
| TLS or binary downloads corrupt | A wildcard inject[].domain forced interception across CDN subdomains. Name the exact auth host |
| Rule looks right, nothing works | Under org governance, kit and local allow rules are inactive — only org rules grant. Deny rules still apply |
mount policy denied at creation | An org filesystem rule rejects the workspace path. Usually a * where ** was meant — see governing-sbx-fleets |
The sentinel bypass is worth recognising by name. The agent holds a
placeholder such as proxy-managed, and the proxy swaps in the real secret in
flight — but only for requests that actually traverse it. urllib3 and
aiohttp.ClientSession() ignore HTTPS_PROXY, connect direct, and ship the
placeholder to the API, which rejects it. Give them trust_env=True.
Check the PROXY column in sbx policy log when credentials are configured
and calls still fail. There are two paths: the forward proxy enforces policy
and injects credentials; the transparent fallback enforces policy and
injects nothing. A process inside a nested Docker container typically lands on
the transparent path, so it gets allowed through and then rejected for a bad key.
A corporate TLS-inspecting proxy breaks injection before it starts. Install the
internal root CA with update-ca-certificates. Point SSL_CERT_FILE at your CA
alone and you replace the system bundle, which breaks the egress path instead.
For one service, a stored secret beats an environment variable. And the sandbox
owns HTTP_PROXY, HTTPS_PROXY and NO_PROXY — setting them yourself severs
injection and policy.
What policy cannot do: DNS is unfiltered, but UDP and ICMP are blocked in the
kernel and cannot be unblocked. A mongodb+srv:// URI needs SRV records over
UDP, so it can never resolve inside a sandbox — use a seedlist URI. Non-HTTP
TCP works through the same domain rule, but adding :port breaks a TLS service
that sends SNI, because the proxy matches the SNI hostname when one is present.
Build-time egress allows 443 and blocks 80.
Guessing at rules wastes more time than checking them. Three commands settle it:
sbx policy check network api.example.com # would this be allowed, right now?
sbx policy ls --include-inactive # rules org governance has switched off
sbx policy ls --wide --source kit # rule-level detail with IDs, by originpolicy check takes a hostname, host:port, an IP, or a URL, and evaluates a
bare host against port 443. Add --sandbox <name> to evaluate in one box's
context. It answers the question before you burn a sandbox creation on it.
Inactive rules are hidden by default, which is what makes org governance so
confusing from inside: you add an allow, sbx policy ls does not show it, and
nothing changes. --include-inactive reveals it with inactive in the STATUS
column. A Governance: Managed by <org> line at the top of policy ls is the
tell. --source filters by local, org or kit; --decision by allow or
deny; sbx policy inspect expands a single rule.
sbx policy reset is not sbx reset. The first deletes only the local
policy store and re-prompts for a preset; the second destroys all sandbox state.
Both restart the daemon, so running sandboxes stop either way. Reach for the
narrow one.
Slow git status in a large direct-mode repo is a virtiofs caching
question. Caching is on by default everywhere and clone mode always enables it,
so this tuning only applies to direct mode. The kill switch exists for the
opposite symptom — Git index corruption or file content that reads back wrong:
DOCKER_SANDBOXES_ENABLE_VIRTIOFS_CACHE=0 sbx run <template>It applies at creation, so recreate the sandbox to change it.
--clone reporting "not in a Git repository" on a valid repo is Git's
dubious-ownership check, not sbx. It shows up on Windows against a
\\wsl.localhost\... path, where Git refuses a repository owned by another user
across the WSL boundary and the detection fails underneath. Confirm with
git -C <path> rev-parse --show-toplevel — a detected dubious ownership error
names the real cause. Add the repo to safe.directory on the host and retry:
> git config --global --add safe.directory '%(prefix)///wsl.localhost/Ubuntu/home/you/repo'A sandbox auto-stops after roughly 25 seconds idle, and there is no
sbx start to bring it back.
Memory is a hard wall, not a soft one. -m is rejected outright above 75% of
host RAM — a 48 GB host caps at 36 GiB, and 40g fails with a message saying
so. There is no swap and cannot be: the rootfs is overlay, so mkswap
succeeds and swapon returns EINVAL. Crossing the limit is an immediate
SIGKILL with no thrashing grace period, so size for peak, not for average.
Exit 137 from a kit's install hook is the other SIGKILL: install hooks are
killed for slow work. Move the work to startup, or stage it in the image.
~/.claude/settings.json is replaced with a symlink to a root-owned empty
directory after your files land, leaving it dead. Everything else copied
into the image survives. Only /home/agent/... is seeded over, so a workspace
path such as <workspace>/.claude/settings.local.json needs no workaround;
otherwise write it from setup.startup, which runs after the seeding.binfmt_misc registration is volatile. Register it in startup, never
install.sbx run claude ~/a ~/b:ro. sbx cp moves files into a running box — and
anything copied to a non-mounted path costs microVM root disk, which is 20 GB
by default and dies with the sandbox.sbx reset deletes stored secrets unless you pass --preserve-secrets.--kit, --clone, --publish and volumes are create-time only.
Re-attaching ignores them silently; use sbx kit add / sbx ports, or
recreate.--all-sandboxes registry credentials need a recreate.
Only sandbox-scoped entries reach a box that already exists.Disk is sized by environment variable at creation, and the three are
independent: DOCKER_SANDBOXES_ROOT_SIZE (20 GB), DOCKER_SANDBOXES_DOCKER_SIZE
(50 GB sparse, /var/lib/docker), DOCKER_SANDBOXES_CLONED_WORKSPACE_SIZE.
Reclaiming root disk after the fact. A workspace mount is host storage; the rest of the VM is a 20 GB overlay. Move bulk off the overlay and link it back:
mv /home/agent/big-thing <workspace>/big-thing
ln -s <workspace>/big-thing /home/agent/big-thingMeasured: 200 MB moved this way freed exactly 200 MB of overlay, stayed readable
through the link, and left a 71-byte symlink behind. It resolves because direct
mode mounts the workspace at the same absolute path inside the VM as on the
host — the same property that lets claude --resume work across sandboxes. The
link lives on the overlay and dies with the box; the data no longer does.
.gitignored files, so a .env
in the working directory is readable by the agent..git pointer file cannot resolve — files are editable,
but status, commit and branch all fail.--no-share-skills at
creation opts out.sbx exec skips ~/.bashrc, so an interactive-only export is invisible to it.
The base image supplies BASH_ENV to carry the persistent environment into
non-interactive shells; a Dockerfile that sets BASH_ENV itself disables that
mechanism silently.
/etc/sandbox-persistent.sh is sourced before every command, including
non-interactive ones. Export variables there. Keep shell completion scripts out
of it — they depend on COMP_WORDS and friends existing, and sourcing them
outside a completion context breaks every subsequent command, usually as silent
empty output.
printenv SANDBOX_VM_ID distinguishes host context from sandbox context.
The sandbox isolates the agent from the machine. It does not isolate the repository from the agent — in direct mode, edits land on the host tree immediately. A sandbox does not review a diff.
--branch shares one microVM, so that isolation is git-level, not VM-level.
A reproduced escape exists in public: sbx policy allow run from inside a
sandbox landed at global scope. Treat in-sandbox policy commands as untrusted.
An io.containerd.transfer.v1 plugin error can be a mkfs.erofs glibc-2.38
bug. The message blames the wrong component.
sbx runs an embedded containerd that the host's docker ps cannot see, at
$_MYSBX_SBXD_SOCK.
A containerd "database is at major version 6, but this binary only supports up
to major version 1" error means you downgraded sbx. A newer version migrated
the local state database to a schema the older binary cannot read. The error
blames the backend; the cause is the version swap. Recover with
sbx reset --preserve-secrets, which clears the state and keeps your secrets.
Windows drives map to /c/, not /mnt/c/. claude --resume works across
sandboxes only because dev boxes bind-mount at an identical absolute path, so it
breaks under --clone and on Windows.
Escalate in this order — each step destroys more than the last.
sbx reset --preserve-secrets — all sandbox state, secrets kept.
Remove the state directory by hand. Stop everything with sbx reset first.
macOS is one directory, ~/Library/Application Support/com.docker.sandboxes/.
Windows is %LOCALAPPDATA%\DockerSandboxes. Linux is three, following
XDG, and missing one leaves the problem in place:
rm -rf ~/.local/state/sandboxes/ ~/.cache/sandboxes/ ~/.config/sandboxes/Substitute $XDG_STATE_HOME, $XDG_CACHE_HOME and $XDG_CONFIG_HOME where
they are set.
File it. sbx diagnose --upload sends daemon logs, check results and system
information to Docker support and prints a diagnostics ID. Quote that ID in
the issue at github.com/docker/sbx-releases/issues so it can be correlated
with the bundle — an issue without one is much harder to action.
Verified against a research corpus and the sbx docs of 2026-08-07 (re-fetched unchanged on 2026-08-10). Several
entries here were found by reverse engineering rather than documentation, so
re-test before building on one. sbx --version first; the product reached a
second kit schema within four months of launch.
cd8f798
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.