Thomas Piacentini
← Workshop

Cheat sheet

Docker Sandboxes for Agents

Original text · English

Run an agent in a microVM, control its network and keys, get its commits back. sbx 0.47.0, docker-agent 1.149.0.

What runs where

socketstartsrunsHTTPS_PROXYswapallowedmountsbx CLIhostsandboxdsandboxd.sock, daemon.logSecret storemacOS KeychainSandbox microVMown kernel, privateDocker EngineAgentuser agent, uid 1000Forward proxygateway.docker.internal:3128Workspacevirtiofs, same absolutepathAllowed hostspolicy rules
Nodes and connections
NodeRoleKind
sbx CLIhost
sandboxdsandboxd.sock, daemon.log
Secret storemacOS Keychain
Sandbox microVMown kernel, private Docker Engine
Agentuser agent, uid 1000
Forward proxygateway.docker.internal:3128
Workspacevirtiofs, same absolute path
Allowed hostspolicy rules
FromToConnection
sbx CLIsandboxdsocket
sandboxdSandbox microVMstarts
Sandbox microVMAgentruns
AgentForward proxyHTTPS_PROXY
Secret storeForward proxyswap
Forward proxyAllowed hostsallowed
Sandbox microVMWorkspacemount
Sources

One request leaves the sandbox

  1. sbx create sandboxd pulls the template and boots the microVM.
  2. Agent sends the request to the forward proxy on the host.
  3. Authorizer a matching deny wins. No matching allow is an implicit deny.
  4. Blocked the proxy answers 403 inside TLS with "Approval required".
  5. Allowed the proxy tunnels the host as forward-bypass.
  6. Bound secret the proxy swaps the sentinel for the stored value.
  7. policy log one row per host with proxy type and rule.
Sources

Sandbox lifecycle

TaskCode
Create on a repositorysbx create shell <path> --name <name>
Attach againsbx run --name <name>
Run a command as rootsbx exec -u root <name> sh -c '<cmd>'
Copy a file insbx cp <host-file> <name>:/tmp/in.txt
Publish a portsbx ports <name> --publish 18081:8080
Stop and keep statesbx stop <name>
Remove stopped sandboxessbx prune --force
Sources

Secrets, MCP and health

TaskCode
Bind a secret to hostssbx secret set-custom --host <host> --env <VAR> --value <value>
Find keys in host envsbx secret import --dry-run
Register an MCP serversbx mcp add <name> --url <url>
Attach it to a sandboxsbx mcp load <name> --sandbox <sandbox>
Run the 13 checkssbx diagnose
Save a templatesbx template save <name> <tag> --output <file>.tar
Run an agent file in a sandboxdocker-agent run --sandbox --exec <agent-file> '<prompt>'
Sources

Clone mode round trip

sbx create --clone shell <repo> \private clone, host repo read-only
--name <name>source at /run/sandbox/source
sbx exec <name> sh -c '<edit and git commit>'agent commits in the clone
git -C <repo> remote -vsbx added sandbox-<name>
git -C <repo> fetch sandbox-<name>bring the commits back
sbx rm --force <name>VM, state and scoped secrets go
Sources

Debug a blocked host

sbx policy init balancedonce, before the first sandbox
sbx create shell --name m101-policyno workspace mount
sbx exec m101-policy sh -c \
'curl -sS --max-time 10 https://example.com'Approval required
sbx policy log m101-policyforward, default deny
sbx policy allow network \
--sandbox m101-policy example.comrule for one sandbox
sbx policy check network example.com \
--sandbox m101-policy --verbose --jsonread allowed, rule
Sources

What the agent sees inside

CommandWhat it does
HTTPS_PROXY=http://gateway.docker.internal:3128Forward proxy for HTTP and HTTPS
ANTHROPIC_API_KEY=proxy-managedSentinel. The real key stays on the host
M101_RECV_KEY=sbx-cs-<rand>Custom secret placeholder
SSH_AUTH_SOCK=/run/ssh-agent.sockHost SSH agent, forwarded by default
forward-bypassLog proxy type. Tunnel, no inspection
transparentLog proxy type. Other TCP, no injection
"deny_kind": "explicit"A rule denied it. implicit means no allow
Sources

Defaults

SettingDefaultNote
--name<agent>-<workdir>Set it, so two agents stay apart
-m, --memory50% of host memoryClamped from 512 MiB to 32 GiB
--pullalwaysAlso missing or never
--skillsreadonlyAlso off or readwrite
secret set --refresh55mRefresh for a dynamic source
ssh.agentForwardingEnabledtrueTurn off for agents you do not trust
sandbox.disk.dockerVolume10gSize of /var/lib/docker
kit.allowedSources["docker.io/"]Allowed remote kit sources
Sources

Key concepts

Sandbox
a microVM with its own kernel and a private Docker Engine.
Clone mode
--clone. The agent works on a private clone. Commits return through a remote.
Preset
allow-all, balanced or deny-all, set once with sbx policy init.
Policy scope
global for all sandboxes, or one sandbox with --sandbox.
Deny wins
a deny rule takes precedence over an allow for the same host.
Sentinel
the placeholder the agent sees in place of a secret.
Sources

Troubleshooting

SymptomCauseFix
Approval required for example.com:443.No allow rule matchessbx policy allow network --sandbox <name> <host>
curl to ::1 fails with exit 7Port published as tcp4Publish 8080/tcp for both families
copying between sandboxes is not supportedcp needs a host sideCopy through the host
Deny conflicts with existing allow ruleAllow and deny in one scopesbx policy rm network --id <uuid> first
No API key from ~/.zshrcHost environment not read since v0.35.0sbx secret set or sbx secret import
ensure daemon: daemon exited unexpectedlysandboxd problemsbx daemon status --json, read daemon.log
Sources

Q&A

Do I need Docker Desktop?
No. You need sbx login. Containers of the agent never show in host docker ps.
stop or rm?
sbx stop keeps state. sbx rm deletes the sandbox, its state and scoped secrets.
Does a host allow block a gist upload?
No. A host allow passes any method, path and body.
Does the agent see my key?
No. It sees proxy-managed. The proxy adds the key after the VM.
Sources

Tips

  • Set memory, ports, skills and static MCP servers at creation. A re-attach ignores them.
  • Run sbx prune --dry-run before a prune.
  • Add narrow rules with --sandbox. Keep global rules for hosts every agent needs.
  • Store keys with sbx secret set, never with -e KEY.
  • Bind a custom secret to exact hosts. The proxy replaces the placeholder anywhere.
Sources
Original source
---
id: docker-sandboxes
title: Docker Sandboxes for Agents
code: DS
subtitle: Run an agent in a microVM, control its network and keys, get its commits back. sbx 0.47.0, docker-agent 1.149.0.
hue: 210
sources: manuals/docker-sandboxes-101
source_hash: 27c0ba82cd494898
---

## What runs where
type: architecture
from: manuals/docker-sandboxes-101/sections/1-1-sbx-and-docker-agent.md, manuals/docker-sandboxes-101/sections/2-2-sbx-exec-cp-and-ports.md, manuals/docker-sandboxes-101/sections/3-4-sbx-policy-log-and-the-proxy.md, manuals/docker-sandboxes-101/sections/3-5-sbx-secret-set-import-and-set-custom.md, manuals/docker-sandboxes-101/sections/2-4-sandboxd-diagnose-and-reset.md

```arch
node | cli | 1,1 | sbx CLI | host
node | daemon | 2,1 | sandboxd | sandboxd.sock, daemon.log
node | keys | 3,1 | Secret store | macOS Keychain
node | vm | 1,2 | Sandbox microVM | own kernel, private Docker Engine
node | agent | 2,2 | Agent | user agent, uid 1000
node | proxy | 3,2 | Forward proxy | gateway.docker.internal:3128
node | ws | 1,3 | Workspace | virtiofs, same absolute path
node | net | 3,3 | Allowed hosts | policy rules
edge | cli | daemon | socket
edge | daemon | vm | starts
edge | vm | agent | runs
edge | agent | proxy | HTTPS_PROXY
edge | keys | proxy | swap
edge | proxy | net | allowed
edge | vm | ws | mount
```

## One request leaves the sandbox
type: flow
from: manuals/docker-sandboxes-101/sections/2-1-sbx-run-create-stop-and-rm.md, manuals/docker-sandboxes-101/sections/3-3-sbx-policy-init-allow-deny-and-check-network.md, manuals/docker-sandboxes-101/sections/3-4-sbx-policy-log-and-the-proxy.md, manuals/docker-sandboxes-101/sections/3-5-sbx-secret-set-import-and-set-custom.md

1. **sbx create**: sandboxd pulls the template and boots the microVM.
2. **Agent**: sends the request to the forward proxy on the host.
3. **Authorizer**: a matching deny wins. No matching allow is an implicit deny.
4. **Blocked**: the proxy answers 403 inside TLS with "Approval required".
5. **Allowed**: the proxy tunnels the host as `forward-bypass`.
6. **Bound secret**: the proxy swaps the sentinel for the stored value.
7. **policy log**: one row per host with proxy type and rule.

## Sandbox lifecycle
type: task-table
from: manuals/docker-sandboxes-101/capture/out/04-create.txt, manuals/docker-sandboxes-101/capture/out/07-template-save.txt, manuals/docker-sandboxes-101/capture/out/06-cp.txt, manuals/docker-sandboxes-101/capture/out/05-ports.txt, manuals/docker-sandboxes-101/capture/out/04-stop.txt, manuals/docker-sandboxes-101/capture/out/04-prune.txt, manuals/docker-sandboxes-101/capture/out/04-ls.txt, manuals/docker-sandboxes-101/sections/2-1-sbx-run-create-stop-and-rm.md

| Task | Code |
|---|---|
| Create on a repository | `sbx create shell <path> --name <name>` |
| Attach again | `sbx run --name <name>` |
| Run a command as root | `sbx exec -u root <name> sh -c '<cmd>'` |
| Copy a file in | `sbx cp <host-file> <name>:/tmp/in.txt` |
| Publish a port | `sbx ports <name> --publish 18081:8080` |
| Stop and keep state | `sbx stop <name>` |
| Remove stopped sandboxes | `sbx prune --force` |

## Secrets, MCP and health
type: task-table
from: manuals/docker-sandboxes-101/capture/out/10-set-custom.txt, manuals/docker-sandboxes-101/capture/out/10-import-dry-run.txt, manuals/docker-sandboxes-101/capture/out/11-mcp-add.txt, manuals/docker-sandboxes-101/capture/out/11-load.txt, manuals/docker-sandboxes-101/capture/out/02-daemon-status.txt, manuals/docker-sandboxes-101/capture/out/02-diagnose.txt, manuals/docker-sandboxes-101/capture/out/07-template-save.txt, manuals/docker-sandboxes-101/capture/out/27-sandbox-run.txt

| Task | Code |
|---|---|
| Bind a secret to hosts | `sbx secret set-custom --host <host> --env <VAR> --value <value>` |
| Find keys in host env | `sbx secret import --dry-run` |
| Register an MCP server | `sbx mcp add <name> --url <url>` |
| Attach it to a sandbox | `sbx mcp load <name> --sandbox <sandbox>` |
| Run the 13 checks | `sbx diagnose` |
| Save a template | `sbx template save <name> <tag> --output <file>.tar` |
| Run an agent file in a sandbox | `docker-agent run --sandbox --exec <agent-file> '<prompt>'` |

## Clone mode round trip
type: annotated-code
lang: bash
from: manuals/docker-sandboxes-101/capture/out/08-clone-create.txt, manuals/docker-sandboxes-101/capture/out/08-clone-host.txt, manuals/docker-sandboxes-101/capture/out/08-clone-rm.txt, manuals/docker-sandboxes-101/sections/3-2-clone-and-run-sandbox-source.md, manuals/docker-sandboxes-101/sections/2-1-sbx-run-create-stop-and-rm.md

```bash
sbx create --clone shell <repo> \  # private clone, host repo read-only
  --name <name>  # source at /run/sandbox/source
sbx exec <name> sh -c '<edit and git commit>'  # agent commits in the clone
git -C <repo> remote -v  # sbx added sandbox-<name>
git -C <repo> fetch sandbox-<name>  # bring the commits back
sbx rm --force <name>  # VM, state and scoped secrets go
```

## Debug a blocked host
type: annotated-code
lang: bash
from: manuals/docker-sandboxes-101/capture/out/03-policy-init.txt, manuals/docker-sandboxes-101/capture/out/09-create.txt, manuals/docker-sandboxes-101/capture/out/09-blocked-by-rule.txt, manuals/docker-sandboxes-101/capture/out/09-policy-log.txt, manuals/docker-sandboxes-101/capture/out/09-allow.txt, manuals/docker-sandboxes-101/capture/out/09-rm-allow.txt, manuals/docker-sandboxes-101/capture/run.py

```bash
sbx policy init balanced  # once, before the first sandbox
sbx create shell --name m101-policy  # no workspace mount
sbx exec m101-policy sh -c \
  'curl -sS --max-time 10 https://example.com'  # Approval required
sbx policy log m101-policy  # forward, default deny
sbx policy allow network \
  --sandbox m101-policy example.com  # rule for one sandbox
sbx policy check network example.com \
  --sandbox m101-policy --verbose --json  # read allowed, rule
```

## What the agent sees inside
type: command-table
from: manuals/docker-sandboxes-101/capture/out/04-env.txt, manuals/docker-sandboxes-101/capture/out/10-env-sentinel.txt, manuals/docker-sandboxes-101/capture/out/09-policy-log.txt, manuals/docker-sandboxes-101/sections/3-4-sbx-policy-log-and-the-proxy.md, manuals/docker-sandboxes-101/sections/3-3-sbx-policy-init-allow-deny-and-check-network.md

| Command | What it does |
|---|---|
| `HTTPS_PROXY=http://gateway.docker.internal:3128` | Forward proxy for HTTP and HTTPS |
| `ANTHROPIC_API_KEY=proxy-managed` | Sentinel. The real key stays on the host |
| `M101_RECV_KEY=sbx-cs-<rand>` | Custom secret placeholder |
| `SSH_AUTH_SOCK=/run/ssh-agent.sock` | Host SSH agent, forwarded by default |
| `forward-bypass` | Log proxy type. Tunnel, no inspection |
| `transparent` | Log proxy type. Other TCP, no injection |
| `"deny_kind": "explicit"` | A rule denied it. `implicit` means no allow |

## Defaults
type: defaults
from: manuals/docker-sandboxes-101/sections/r-1-sbx-commands.md, manuals/docker-sandboxes-101/sections/r-3-settings-keys-environment-variables-and-paths.md

| Setting | Default | Note |
|---|---|---|
| `--name` | `<agent>-<workdir>` | Set it, so two agents stay apart |
| `-m, --memory` | 50% of host memory | Clamped from 512 MiB to 32 GiB |
| `--pull` | `always` | Also `missing` or `never` |
| `--skills` | `readonly` | Also `off` or `readwrite` |
| `secret set --refresh` | `55m` | Refresh for a dynamic source |
| `ssh.agentForwardingEnabled` | `true` | Turn off for agents you do not trust |
| `sandbox.disk.dockerVolume` | `10g` | Size of `/var/lib/docker` |
| `kit.allowedSources` | `["docker.io/"]` | Allowed remote kit sources |

## Key concepts
type: concepts
from: manuals/docker-sandboxes-101/sections/r-8-glossary-and-index-of-figures.md, manuals/docker-sandboxes-101/sections/3-3-sbx-policy-init-allow-deny-and-check-network.md

- **Sandbox**: a microVM with its own kernel and a private Docker Engine.
- **Clone mode**: `--clone`. The agent works on a private clone. Commits return through a remote.
- **Preset**: `allow-all`, `balanced` or `deny-all`, set once with `sbx policy init`.
- **Policy scope**: `global` for all sandboxes, or one sandbox with `--sandbox`.
- **Deny wins**: a deny rule takes precedence over an allow for the same host.
- **Sentinel**: the placeholder the agent sees in place of a secret.

## Troubleshooting
type: troubleshooting
from: manuals/docker-sandboxes-101/capture/out/09-blocked.txt, manuals/docker-sandboxes-101/capture/out/05-ports-curl.txt, manuals/docker-sandboxes-101/capture/out/06-cp.txt, manuals/docker-sandboxes-101/capture/out/09-deny-conflict.txt, manuals/docker-sandboxes-101/capture/out/09-rm-allow.txt, manuals/docker-sandboxes-101/capture/out/07-template-save.txt, manuals/docker-sandboxes-101/sections/2-2-sbx-exec-cp-and-ports.md, manuals/docker-sandboxes-101/sections/3-5-sbx-secret-set-import-and-set-custom.md, manuals/docker-sandboxes-101/sections/3-2-clone-and-run-sandbox-source.md, manuals/docker-sandboxes-101/sections/2-4-sandboxd-diagnose-and-reset.md

| Symptom | Cause | Fix |
|---|---|---|
| `Approval required for example.com:443.` | No allow rule matches | `sbx policy allow network --sandbox <name> <host>` |
| curl to `::1` fails with exit 7 | Port published as `tcp4` | Publish `8080/tcp` for both families |
| `copying between sandboxes is not supported` | `cp` needs a host side | Copy through the host |
| Deny `conflicts with existing allow rule` | Allow and deny in one scope | `sbx policy rm network --id <uuid>` first |
| No API key from `~/.zshrc` | Host environment not read since v0.35.0 | `sbx secret set` or `sbx secret import` |
| `ensure daemon: daemon exited unexpectedly` | sandboxd problem | `sbx daemon status --json`, read `daemon.log` |

## Q&A
type: qa
from: manuals/docker-sandboxes-101/sections/1-1-sbx-and-docker-agent.md, manuals/docker-sandboxes-101/sections/2-1-sbx-run-create-stop-and-rm.md, manuals/docker-sandboxes-101/sections/3-4-sbx-policy-log-and-the-proxy.md, manuals/docker-sandboxes-101/sections/3-5-sbx-secret-set-import-and-set-custom.md, manuals/docker-sandboxes-101/sections/3-2-clone-and-run-sandbox-source.md

- **Do I need Docker Desktop?** No. You need `sbx login`. Containers of the agent never show in host `docker ps`.
- **stop or rm?** `sbx stop` keeps state. `sbx rm` deletes the sandbox, its state and scoped secrets.
- **Does a host allow block a gist upload?** No. A host allow passes any method, path and body.
- **Does the agent see my key?** No. It sees `proxy-managed`. The proxy adds the key after the VM.

## Tips
type: tips
from: manuals/docker-sandboxes-101/sections/2-1-sbx-run-create-stop-and-rm.md, manuals/docker-sandboxes-101/sections/3-3-sbx-policy-init-allow-deny-and-check-network.md, manuals/docker-sandboxes-101/sections/3-5-sbx-secret-set-import-and-set-custom.md, manuals/docker-sandboxes-101/sections/2-4-sandboxd-diagnose-and-reset.md, manuals/docker-sandboxes-101/sections/2-2-sbx-exec-cp-and-ports.md

- Set memory, ports, skills and static MCP servers at creation. A re-attach ignores them.
- Run `sbx prune --dry-run` before a prune.
- Add narrow rules with `--sandbox`. Keep global rules for hosts every agent needs.
- Store keys with `sbx secret set`, never with `-e KEY`.
- Bind a custom secret to exact hosts. The proxy replaces the placeholder anywhere.
Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓