Guide

Run your first AI agent in a Docker Sandbox

  • Beginner
  • 15 min
  • Updated
  • Verified

What you'll learn

  • Store an agent's credentials on your machine so the sandbox never holds them
  • Start Claude Code in a sandbox on one of your own projects
  • Find a running sandbox and run commands inside it from your host
  • Show that your files and your real keys aren't inside the sandbox
  • See a network request blocked by policy, allow it, and retry
  • Stop and remove a sandbox

The terminals below are simulated. Nothing runs on your machine, and each one starts from the state the guide has reached at that point.

A Docker Sandbox runs an AI coding agent inside an isolated microVM. The agent works on your project, but it can’t see the rest of your machine, it reaches the network only through a proxy that enforces your network policy, and it never holds your real credentials.

In this guide, you’ll run Claude Code in a sandbox against one of your own projects and test each of those boundaries. You’ll watch the network proxy block a request, allow it, and then clean everything up.

Before you start

You can follow this whole guide in your browser. The terminals on this page are simulations: click through them to see what each command does.

To run it for real on your own machine, you need three things:

  • The sbx CLI, installed and signed in. Follow Install Docker Sandboxes, then run sbx login.
  • A way for Claude Code to reach its model. Either a Claude subscription or an Anthropic API key.
  • A project to work on. Any directory works. This guide calls it ~/my-app. A Git repository is best, because you can review what the agent changed with git diff.

Your output will differ from the simulations in the details: your paths, your project, and what the agent says.

Step 1: Give the agent its credentials

An agent can’t think without its AI model, and the model wants a key. The risky way to provide it is to paste the key into the agent’s environment, where anything the agent reads could talk it into handing the key over.

A sandbox never puts your real key inside. You store it on your machine, in your operating system’s keychain, and the sandbox’s network proxy attaches it to the agent’s requests on their way out.

How you do this depends on how you pay for Claude:

  • With a Claude subscription, there’s nothing to store yet. When Claude Code starts in Step 2, type /login and sign in. The sign-in token stays on your machine, not in the sandbox.
  • With an API key, store it now. sbx prompts for the value, so it never lands in your shell history:

Host machine

sbx secret set anthropic
Saved secret anthropic (global)

The secret is global: it’s stored once, on your machine, for any sandbox that asks for it. Run sbx secret ls any time to see what’s stored. It lists names, never values.

Storing a key doesn’t hand it to anything yet. In the next step, the sandbox asks for it, and you decide.

Step 2: Start Claude Code on your project

Change into your project and start Claude Code:

$ cd ~/my-app
$ sbx run docker/sbx-kit-claude --name claude-my-app

docker/sbx-kit-claude is a kit: a published bundle with Claude Code, the tools it works with, and the rules for where it may connect and which keys it may use. sbx pulls it from Docker Hub. --name gives the sandbox a name you’ll use for the rest of this guide.

The very first time you run a sandbox, sbx asks which network policy to start from:

Initialize the global network policy for your sandboxes:

  Applies to all sandboxes, current and future — change it later with
  "sbx policy allow/deny/rm". Kits, including built-in agent kits, may
  also add per-sandbox rules.

     1. Open         — All network traffic allowed, no restrictions.
  ❯  2. Balanced     — Default deny, with common dev sites allowed.
     3. Locked Down  — All network traffic blocked unless you allow it.

  Use ↑/↓ or 1–3 to navigate, Enter to confirm, Esc to cancel.

Choose Balanced. It lets the agent reach AI providers, package managers, code hosts, and container registries, and blocks everything else. Step 5 depends on it.

Next, sbx asks you to approve the kit’s use of a credential. The kit says it needs the anthropic service, and names the Anthropic domains the network proxy may attach your key to. This is sbx checking with you, not the agent asking: storing a key in Step 1 didn’t let any kit use it, and approving here lets this kit’s sandboxes use it only on those domains. Read the domains, then choose Approve all. sbx records your answer, so you aren’t asked again unless a kit wants the key on more domains.

If you decline, the sandbox still starts, but the proxy won’t attach a key, so Claude Code can’t reach its model.

The first run also takes a minute while sbx pulls the kit. After that, sandboxes start in seconds. Then you’re talking to Claude Code, running inside the sandbox. Give it something to do:

Terminal 1 · Claude Code in the sandbox

sbx run docker/sbx-kit-claude --name claude-my-app
This kit wants to use these credentials:
  anthropic:  API key sent to api.anthropic.com, mcp-proxy.anthropic.com (stored)
              OAuth refreshed at platform.claude.com
Approve these credentials?
  1. Approve all
  2. Review each
  3. No   start without them
Choice [1-3, default 3]: 1
  ✓ Binding saved to /home/moby/.config/sbx/credentials.yaml

SANDBOX
  Name:       claude-my-app
  Agent:      sbx-kit-claude
  Kit:        docker/sbx-kit-claude
  Workspace:  ~/my-app (rw)
  Skills:     0 entries · read-only
    Source:   ~/.local/state/sandboxes/sandboxes/agent-skills
    Target:   /home/agent/.claude/skills
  Image:      docker.io/docker/sbx-kit-claude@sha256:c0132e89a89d94ddca3984513afdb36be57bd087fcc75823a4d14064b6a39ea5
  CPU:        4
  Memory:     4g

IMAGE
  ✓ image — ready

  ✓ Created sandbox claude-my-app

  ○ sbx-kit-claude — starting in sandbox claude-my-app…
      workspace /home/moby/my-app
What's in this project? Answer in one sentence.
A small Node.js web app: package.json, a src/ folder with an Express server, and a README.

Your answer depends on your project, but notice that Claude didn’t ask permission before reading your files. Inside a sandbox, Claude Code starts in its bypass-permissions mode (--dangerously-skip-permissions), so it doesn’t stop to ask before every command. That’s safe to do here because the sandbox, not a stream of approval prompts, is what keeps it contained.

Leave this session open. Use a second terminal for the rest of the guide.

Step 3: See the running sandbox

In a second terminal on your host, list your sandboxes:

Terminal 2 · Host machine

sbx ls
SANDBOX         AGENT            STATUS      PORTS   WORKSPACE           CREATED
claude-my-app   sbx-kit-claude   ● running           /home/moby/my-app   3m

The WORKSPACE column is the one directory on your machine that the sandbox can see. It’s shared, not copied: the agent’s edits land in your project as it makes them.

Run sbx on its own to open a live dashboard of every sandbox, with their CPU and memory use and a panel for network rules.

Step 4: Test the isolation

sbx exec runs a command inside the sandbox from your host, so you can look around from the agent’s side.

Your other files aren’t there

Start with something that’s definitely on your machine but outside the project, like your Documents folder. Your shell expands ~ before the command reaches the sandbox, so this asks the sandbox for your real home directory’s path:

Terminal 2 · Host machine

sbx exec claude-my-app ls ~/Documents
ls: cannot access '/home/moby/Documents': No such file or directory
sbx exec claude-my-app ls ..
my-app

Your Documents folder isn’t blocked. It doesn’t exist in there. And the directory around your project holds exactly one thing: the project. Sibling projects, SSH keys, cloud credentials, photos, none of it was shared, so none of it is in the sandbox.

The same goes the other way. Packages the agent installs, Docker images it builds, and files it writes outside the project all stay inside the sandbox, and disappear when you remove it.

The agent only holds a placeholder

Now look for the Anthropic key. Claude Code needs to find something that looks like a key before it will start, so the kit gives it one. If you stored an API key in Step 1, the kit tells Claude Code to get its key from a command, and that command just prints a placeholder:

Terminal 2 · Host machine

sbx exec claude-my-app grep apiKeyHelper /home/agent/.claude/settings.json
  "apiKeyHelper": "echo proxy-managed",

That’s the key Claude Code holds: the words proxy-managed. When Claude Code sends a request to Anthropic, the network proxy replaces it with your real key on the way out. If you signed in with a subscription instead, the sign-in file inside the sandbox holds placeholder tokens in the same way. Either way, if the agent were tricked into leaking its key, nobody could use it.

Step 5: See the network policy at work

The Balanced policy allows the places agents commonly need and nothing else. Ask the sandbox to fetch a page from a site that no rule allows, example.com:

Terminal 2 · Host machine

sbx exec claude-my-app curl -sS https://example.com
Approval required for example.com:443.

Review and respond with:
  sbx policy approval ls
sbx policy approval ls
APPROVAL                                              SANDBOX         TITLE                                    DETAIL                                                                      OPTIONS
network:IDBxSfVGPCuAPGkrgIwGCUQIfzf54kIz6lofLskPg70   claude-my-app   deb.debian.org:80                        Protocol: TCP Resource type: domain no matching allow rule (default deny)   allow (Allow), dismiss (Dismiss)
network:G5rVts0tU9heuVP2toTrcCAOLXROcYF_WavKzr2hzv8   claude-my-app   http-intake.logs.us5.datadoghq.com:443   Protocol: TCP Resource type: domain no matching allow rule (default deny)   allow (Allow), dismiss (Dismiss)
network:xZCRq6wR9aj-dJwGTv2gxY1AafnNPMa_QLxYsiFrEHs   claude-my-app   example.com:443                          Protocol: TCP Resource type: domain no matching allow rule (default deny)   allow (Allow), dismiss (Dismiss)
sbx policy approval respond network:xZCRq6wR9aj-dJwGTv2gxY1AafnNPMa_QLxYsiFrEHs --option allow
Recorded answer Allow
sbx exec claude-my-app curl -sS https://example.com
<!doctype html><html lang=en><head><meta charset=utf-8><link rel=icon href=data:,><meta name=viewport content="width=device-width,initial-scale=1"><title>Example Domain</title><style>html{color-scheme:light dark;background:light-dark(#eee,#222)}body{font:16px/1.6 system-ui,sans-serif;max-width:26em;margin:auto;padding:25vh 2em 2em;text-align:center}</style></head><body><p>This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.</p><script src=/s.js></script></body></html>

Here’s what happened:

  1. The proxy blocked the request. No rule matched example.com, so the proxy blocked it and told the sandbox why. The site never heard from it.
  2. The proxy asked you. A blocked destination becomes a pending approval on your machine. The agent can’t answer it. Only you can.
  3. You approved it, and the same command worked. The change applied to the running sandbox straight away, with no restart.

Approving the request allows one destination (example.com on port 443), for this sandbox only. A different sandbox reaching for the same site would ask again. Use your own approval ID; sbx policy approval ls prints it.

Your list may hold other rows too. Here, the sandbox had already reached for deb.debian.org and Claude Code’s telemetry host since it started, and the proxy blocked those the same way. Respond only to requests you recognize. Your agent goes through the same proxy: when Claude reaches for a blocked site, the approval waits for you here, and Claude picks up the access when it tries again.

If you already know a sandbox needs a site, add the rule up front instead of waiting to be asked:

$ sbx policy allow network --sandbox claude-my-app example.com

Leave off --sandbox to allow it for every sandbox. To see everything your sandboxes have reached, and what the proxy blocked, run sbx policy log.

Step 6: Stop and remove the sandbox

Go back to Terminal 1 and type /exit to leave Claude Code. Leaving the session doesn’t delete the sandbox. It keeps everything the agent installed, so sbx run --name claude-my-app picks up where you left off.

When you’re finished with it, stop it and then remove it:

Terminal 2 · Host machine

sbx stop claude-my-app
Stopped sandbox claude-my-app
sbx ls
SANDBOX         AGENT            STATUS      PORTS   WORKSPACE           CREATED
claude-my-app   sbx-kit-claude   ○ stopped           /home/moby/my-app   5m
sbx rm claude-my-app
Removed sandbox claude-my-app

sbx rm asks first, because removing a sandbox deletes the microVM and everything in it, and can’t be undone. Your project is safe: it lives on your machine, and only the sandbox’s view of it goes away. The secrets you stored stay in your keychain for the next sandbox.

For a throwaway session, add --rm to sbx run. It removes the sandbox for you when the agent exits.

What’s next

You’ve run a real agent on a real project and tested each boundary: your other files weren’t in the sandbox, the agent only held a placeholder key, and the network proxy blocked a request until you allowed it.

So far the sandbox has run the stock Claude Code kit. Next, Customize your sandbox with kits shows how to add your own tools or swap in a different agent, and after that you’ll capture the whole setup in a file you can share.

Learning paths

Part of a learning path

This works on its own and is also included in the paths below. Open a path to see the complete route.