Docker Sandboxes for AI Agents: Isolation, Credentials, and the MCP Gateway
If you have run Claude Code, Codex, or Cursor on your own machine, you know the tension: the agent needs real power (install packages, run Docker, edit files, execute code), and you would rather it never have root on your host. Docker Sandboxes is Docker's answer: each agent runs in its own microVM with its own kernel, its own Docker daemon, and its own network, while your credentials stay on the host. I have run agents on my own boxes long enough to be suspicious of the word "sandbox", so this post is the sysadmin's-eye view: what is actually isolated, how secrets really work, and where the MCP gateway fits.
The short version
- Each sandbox is a microVM with its own Linux kernel and its own Docker daemon. The agent has sudo inside the VM, can build images and run containers against that private daemon, and cannot see the host daemon, the host network, or the host filesystem outside the workspace.
- All outbound HTTP and HTTPS goes through a host-side proxy. The proxy enforces the network policy (deny by default) and injects credentials. Raw TCP, UDP, and ICMP are blocked at the network layer.
- API keys never enter the sandbox. The agent sees a sentinel like
proxy-managed; the host proxy looks up the real credential and swaps it into the auth header before forwarding. - Secrets live in your OS keychain via `sbx secret set` (macOS Keychain, Windows Credential Manager, or the Linux Secret Service). On headless Linux without a keyring they fall back to an encrypted file, which is weaker than a keychain.
- The MCP gateway is a host-managed broker. Register MCP servers once on the host; sandboxed agents reach them through one gateway endpoint, and OAuth tokens stay on the host.
- The default workspace mount is read-write direct passthrough. The agent edits your working tree in place.
--clonegives you a read-only host mount and a private clone, but the repository stays readable, including.env. - `sbx` is free to use, including commercially. Organization governance is the separate paid add-on.
- sbx CLI
current (docs checked 2026-08-10) - Supported agents
10 (Claude Code, Codex, Copilot, Cursor, Droid, Gemini, Kiro, OpenCode, Docker Agent, Shell)
Checked 2026-08-10 against the official Docker Sandboxes documentation at docs.docker.com/ai/sandboxes. The sbx CLI is under active development; commands and flags may change between releases.
What Docker Sandboxes actually is
A sandbox is a lightweight virtual machine. Each one boots its own Linux kernel and runs its own Docker Engine, so the agent inside has full Docker capabilities, sudo, package installation, and read-write access to its VM filesystem without any path to the host daemon. The architecture docs frame it as a trade: sandboxes cost more resources than containers (a VM plus its own daemon), and in exchange you can hand something autonomous the keys to a Docker engine without trusting it with your host.
Key mechanics:
- The workspace is mounted through a filesystem passthrough at the same absolute path as on your host. Changes in either direction are instant, with no sync process.
- Every sandbox maintains its own daemon state, image cache, and package installations; sandboxes do not share images or layers.
- Everything inside the VM persists until you remove it: images, containers, installed packages, agent state. Stop and restart does not recreate the VM;
sbx rmdeletes it and everything in it.
Install and first run
$ brew trust docker/tap && brew install docker/tap/sbx$ winget install -h Docker.sbx$ sudo apt-get install docker-sbx
$ sudo usermod -aG kvm $USER && newgrp kvm$ sbx login
$ cd ~/my-project
$ sbx run claudeThe trade-off vs the alternatives:
Approach | Isolation | Docker access | Use case |
|---|---|---|---|
Sandboxes (microVMs) | Full (hypervisor) | Isolated daemon | Autonomous agents |
Container with socket mount | Partial (namespaces) | Shared host daemon | Trusted tools |
Docker-in-Docker | Partial (privileged) | Nested daemon | CI/CD pipelines |
Host execution | None | Host daemon | Manual development |
The trust boundary
The primary trust boundary is the microVM. Everything the agent does inside the VM is real, but bounded. What crosses into the VM: the workspace directory, credentials (as injected headers, never as values), HTTP/HTTPS to allowed domains, the shared agent skills store, and MCP gateway traffic. What crosses back: workspace file changes, HTTP/HTTPS requests, and shared skill changes.
Blocked by design: the host filesystem outside the workspace, the host Docker daemon, the host network and localhost, domains outside the allowlist, and direct sandbox-to-sandbox networking. Raw TCP, UDP, and ICMP never leave the sandbox.
The five isolation layers
- Hypervisor isolation: separate kernel per sandbox. No shared memory or processes with the host.
- Network isolation: all HTTP/HTTPS proxied through the host with a deny-by-default policy; non-HTTP protocols are blocked entirely.
- Docker Engine isolation: each sandbox runs its own engine, with no path to the host daemon.
- Workspace isolation: opt-in via
--clone. The default direct mode has no workspace boundary at all. - Credential isolation: API keys are injected into HTTP headers by the host-side proxy. Credential values never enter the VM as environment variables or files.
What is not isolated by default
The agent's actions can still affect you through the channels you do share:
- The default network allowlist includes broad wildcards.
- The shared agent skills store is read-write across sandboxes. One sandbox can modify skills another sandbox loads later. Opt out per sandbox with
--no-share-skills. - Kit installs run with root inside the sandbox. To limit supply-chain risk,
sbxrestricts kit install sources to an allowlist that defaults to Docker Hub only. - Local stdio MCP servers run on the host. More on that in the MCP section; treat them as trusted host integrations.
Credentials: how secrets actually work
This is the part I cared most about. Agents need API keys for model providers, and the naive approach (inject them as environment variables into the sandbox) would hand every credential to anything that escapes the VM. Docker Sandboxes does the opposite: the keys never go in.
How credential injection works
The host-side proxy intercepts outbound requests and decides three things: whether the request matches a service the kit or built-in agent declares, which header to write, and which value to inject. For proxy-managed credentials, the real value never enters the sandbox. The agent sees only a sentinel such as proxy-managed. When more than one source has a value for a service, the stored secret wins. Multi-provider agents (OpenCode, Docker Agent) pick credentials by the API endpoint being called.
Stored secrets: the default path
sbx secret set <service> stores the value in the host's native credential store:
- macOS: the system Keychain
- Windows: the Windows Credential Manager
- Linux: the Secret Service (GNOME Keyring, KDE Wallet)
Secrets are global by default and available to every sandbox. Scope one to a single sandbox with --sandbox. A sandbox-scoped secret takes effect immediately, even while the sandbox is running; a global secret only applies at sandbox creation, so recreate the sandbox after changing a global secret.
If your keys are already in your shell, sbx secret import scans the session for the built-in service variables and stores them with confirmation. --all imports everything without prompting, --force overwrites, --dry-run previews.
Built-in services (the env vars import reads):
Service | Env vars |
|---|---|
anthropic | ANTHROPIC_API_KEY |
cursor | CURSOR_API_KEY |
droid | FACTORY_API_KEY |
github | GH_TOKEN, GITHUB_TOKEN |
GEMINI_API_KEY, GOOGLE_API_KEY | |
groq | GROQ_API_KEY |
mistral | MISTRAL_API_KEY |
nebius | NEBIUS_API_KEY |
openai | OPENAI_API_KEY |
openrouter | OPENROUTER_API_KEY |
xai | XAI_API_KEY |
Kits can declare their own service identifiers in spec.yaml; you still provide the value with sbx secret set <service>, with no separate registration step.
Useful flows:
$ sbx secret set anthropic # interactive prompt, OS keychain
$ echo "$(gh auth token)" | sbx secret set github
$ sbx secret import # scan shell env for known vars
$ sbx secret ls
$ sbx secret set openai --sandbox my-sandbox # scope one sandbox only
$ sbx secret rm openaiGitHub access: echo "$(gh auth token)" | sbx secret set github gives the agent the gh CLI. SSH agent forwarding is handled separately: if the host has SSH_AUTH_SOCK set, Docker Sandboxes forwards the agent into the sandbox. Private keys stay on the host; processes inside can request signatures but cannot read or copy the key.
Custom secrets: when the service model does not fit
sbx secret set-custom keys a secret to a domain and an environment variable instead of a service identifier. Use it when the agent validates an env var format at boot, or when the credential rides in a request body rather than a header. Inside the sandbox, the env var is set to a generated placeholder like sbx-cs-...; when a request to a configured host contains that placeholder, the proxy substitutes the real value. The agent never sees it. Wildcards follow network-rule syntax: *.example.com covers one label, **.example.com covers any number. Custom secrets are experimental, so pin your sbx version before scripting around them.
Credential bindings: the CI gotcha
Third-party kits that declare schemaVersion: "2" require an approved credential binding in ~/.config/sbx/credentials.yaml. The first interactive run walks you through approval; in CI or --detached mode there is no one to answer the prompt, and without a binding the sandbox starts with the credential withheld. If the kit marks the credential required: true, you also get a warning. Pre-create bindings by running the kit interactively once, or write the YAML by hand before running unattended. Built-in agents are authorized by provenance and do not need bindings.
Registry credentials
sbx secret set --registry HOST authenticates to private OCI registries for template and kit pulls, and optionally lets agents pull and push images through the proxy. Three scopes: host-only (CLI pulls, never available inside the sandbox), --all-sandboxes (proxy also authenticates registry logins from sandboxes, the value still never written to the sandbox filesystem), and --sandbox NAME (same proxy behavior for one sandbox). Docker Hub reuses your sbx login; no registry secret needed.
OAuth: token stays on the host
Where the agent supports it (Claude Code, Codex, Cursor, Droid), OAuth runs on the host and the token never enters the sandbox. Details vary by agent: Codex prompts on the host from sbx run codex; Claude Code prompts inside the sandbox (or use /login); Cursor and Droid have no ahead-of-time option, so their sign-in prompt appears at agent start.
Best practices in one place: use stored secrets, do not set API keys manually inside the sandbox, prefer sandbox scope over --all-sandboxes for registry credentials, use OAuth where supported, and remember sbx reset is a credential-rotation event.
The MCP gateway
MCP servers are how agents get tools beyond their built-ins. Docker Sandboxes includes a gateway that changes who owns the configuration: instead of configuring an MCP server inside each agent, you register servers once on the host, and sandboxed agents reach them through one gateway endpoint. sbx manages registrations, OAuth credentials, and sandbox lifecycle on the host. The gateway is separate from the Docker Desktop MCP Toolkit; you do not need the Toolkit, and its settings are not shared.
Registration types:
- Remote endpoint:
sbx mcp add notion --url https://mcp.notion.com/mcp. The server runs remotely; the gateway connects to it. - Local stdio from metadata:
--local --urlresolving to an OCI-packaged stdio server.sbxstarts it on the host with Docker. - Explicit command:
--command npx --args ...or a Docker command, run as a stdio server on the host.
OAuth-backed remote servers: sbx mcp add runs the authorization flow by default; tokens stay in the host credential store. --skip_auth registers without authorizing. For servers without Dynamic Client Registration, pass --client-id (plus --oauth-authorization-server when no OAuth metadata is published). There is deliberately no --client-secret flag: store the secret first with sbx secret set mcp:slack.client_secret, and registration never writes it. Scopes are recorded with repeatable --scope flags.
MCP mode is chosen when the sandbox starts and persists across restarts:
| Feature | Static (--static-mcp) | Dynamic (default) |
|---|---|---|
| Server loading | Pre-loads the named registered servers | Pre-loads none; agent discovers and attaches |
| Discovery tools | mcp-find, mcp-add, mcp-config-set hidden | mcp-find, mcp-add, mcp-config-set exposed |
| Catalog changes | Attach more via sbx mcp load | New registrations refresh the searchable catalog |
| Best for1 | Locked-down, auditable tool sets | Exploratory sessions |
Server loading
- Static (--static-mcp)
- Pre-loads the named registered servers
- Dynamic (default)
- Pre-loads none; agent discovers and attaches
Discovery tools
- Static (--static-mcp)
- mcp-find, mcp-add, mcp-config-set hidden
- Dynamic (default)
- mcp-find, mcp-add, mcp-config-set exposed
Catalog changes
- Static (--static-mcp)
- Attach more via sbx mcp load
- Dynamic (default)
- New registrations refresh the searchable catalog
Best for1
- Static (--static-mcp)
- Locked-down, auditable tool sets
- Dynamic (default)
- Exploratory sessions
- Choice persists across restarts
Attach a registered server to a running sandbox with sbx mcp load linear --sandbox my-session; connected agents get a live tool-list update. The gateway also exposes built-in tools that admins can govern separately from server tools:
Tool | Description |
|---|---|
mcp-exec | Executes a tool by name through the gateway |
code-mode | Creates an ephemeral JavaScript tool that can call selected gateway tools |
mcp-find | Searches the registered server catalog (dynamic mode only) |
mcp-add | Attaches a registered server to the sandbox (dynamic mode only) |
mcp-config-set | Sets per-session config overrides for an attached server (dynamic mode only) |
<server>-authorize | Starts or restarts OAuth authorization for an exposed OAuth-backed server |
In MCP access policies, gateway tools are MCP::Primordial resources (invokePrimordial), while registered server tools are MCP::Tool resources (invokeTool). Organizations with AI Governance write these policies in Cedar.
Architecture details that matter in operations
- Workspace mounting: the passthrough means every file read and write crosses the host boundary. Avoid network-attached storage (SMB/NFS/cloud-synced folders) as workspaces; it adds latency and slows agents.
- Virtiofs caching is on by default and reduces round-trips for read-heavy work like
git status. Opt out withDOCKER_SANDBOXES_ENABLE_VIRTIOFS_CACHE=0. - Upstream proxy: the host proxy chains to your environment. It reads
HTTP_PROXY,HTTPS_PROXY,NO_PROXY(and lowercase equivalents).DOCKER_SANDBOXES_PROXYroutes only sandbox traffic through a different proxy and acceptshttp://,https://,socks5://, andsocks5h://URLs (credentials supported in the URL).DOCKER_SANDBOXES_NO_PROXYexcludes destinations. Set these before the daemon starts; if it is already running,sbx daemon restartapplies changes. PAC files are not supported. - Lifecycle:
sbx runcreates the VM, stop/start preserves it,sbx rmdeletes it.--clonealso removes thesandbox-<name>Git remote from your host repo on delete.
The honest trade-offs
Docker Sandboxes are not a free lunch, and the docs say so. Each sandbox is a VM plus its own Docker daemon: higher resource overhead than a container. And isolation is not the same as safety from your own workspace: in direct mode, the agent edits the same files you do, including files that execute implicitly (Git hooks, CI config, Makefiles, package.json scripts, IDE task configs, .claude/.codex/.gemini settings). Review agent sessions the way you review an untrusted PR, and check .git/hooks separately, since those do not appear in git diff.
Use sandboxes when you need to give an autonomous agent full Docker capabilities without trusting it with your host. Use plain containers when you need lightweight packaging without Docker access, and keep host execution for manual work. If you are weighing rootful vs rootless for your own stacks, the Docker vs Podman comparison covers that ground; if your budget concern is agent tokens rather than isolation, the token-usage field guide is the companion piece.
Official sources
- Docker Sandboxes overview: https://docs.docker.com/ai/sandboxes/
- Supported agents: https://docs.docker.com/ai/sandboxes/agents/
- MCP gateway: https://docs.docker.com/ai/sandboxes/mcp-gateway/
- Architecture: https://docs.docker.com/ai/sandboxes/architecture/
- Security model: https://docs.docker.com/ai/sandboxes/security/
- Credentials: https://docs.docker.com/ai/sandboxes/security/credentials/
- Isolation layers: https://docs.docker.com/ai/sandboxes/security/isolation/
- Default security posture: https://docs.docker.com/ai/sandboxes/security/defaults/
Running agents in sandboxes yet, or still letting them loose on the host? Drop your setup in the comments.
Until next time, keep your systems thoughtful.

No comments yet