Configuring Docker sbx some more
I gave this as an internal talk last week, so here's the short written version. It's a follow-up to the last post on wiring up sbx with Azure AI Foundry — less "why did I get a 401", more "where does configuration actually live". Maybe this post should have come first...
tl;dr
Docker sbx uses templates (Dockerfiles - where you install SDKs and other "heavy" stuff) with kits (a dir with a spec.yaml file - where you set env vars and such) to build your sandbox. By combining these two (you can even have multiple kits stacked together) you can build just about any agent setup that you need.
sbx create <agent> --name my-sandbox `
--template localhost:5000/my-custom-template `
--kit "path/to/base-configuration/kit" `
--kit "path/to/my-special-kit" `
path/to/my-project-dir
Why you can't trust Klaus
The way I explain it to people who haven't touched a sandbox yet: imagine you hire Klaus. Brilliant, fast, works through the night, wants nothing more than to build you that table. So you hand him the house key, your card, and your unlocked phone, and you go to bed.
That's yolo mode on your laptop. Klaus has good intentions the whole time, but he's also a bit scatterbrained and very trusting. He tidies up files that were fine. He buys what he thinks he needs. And when someone at the door says the boss sent them, he hands over what they ask for — that one's prompt injection, and it doesn't care how nicely your CLAUDE.md asked him not to. A prompt is a request, not a boundary.
The fix isn't less Klaus. It's a different room. Give him a workshop with the material and a copy of the plans, and he can go full throttle. It's the actual argument for sandboxing: without such isolation you just cannot leave an agent alone.
The workshop: what sbx gives you
sbx runs the agent in a microVM, not a container — its own kernel, its own filesystem, optionally its own Docker engine. The agent runs as a non-root user, can install and build and break whatever it wants in there, and sees exactly one thing from your host: the folder you mounted.
Two properties matter beyond the isolation:
- One door. All outbound traffic goes through a forward proxy on your host. That's where network policy is enforced and where credentials get attached.
- The workshop gets cleaned.
sbx rmthrows the VM away. Only what changed in the mount survives — which is also why review doesn't go away. The sandbox protects your machine; it does nothing for your codebase. Diff, PR, tests, same as always.
sbx is still experimental and moves fast, so pin what you can and read the changelog.
Layer 1: templates
A template is just a Docker image. sbx ships them for common agents (claude, codex, copilot, …), and you build on top when you need a toolchain or config that isn't included. Heavy things belong here — eg SDKs, anything you don't want reinstalled on every start.
My Claude layer sits on a base image and installs MCP server by default. Build, push to a registry (a local one is fine), and pass it with sbx create claude --template <registry>/my-template.
ARG BASE_IMAGE
FROM ${BASE_IMAGE}
USER root
# Azure DevOps MCP server, launched over stdio by managed-mcp.json below
RUN npm install -g --prefix /usr/local @azure-devops/mcp undici
# The presence of this file flips Claude Code into "Enterprise MCP Configuration"
# mode: project-scoped .mcp.json is ignored and `claude mcp add` is refused.
COPY managed-mcp.json /etc/claude-code/managed-mcp.json
RUN chmod 0644 /etc/claude-code/managed-mcp.json
USER agent
Layer 2: kits
A kit is just a folder with a spec.yaml. Two kinds exist:
- a mixin extends an existing agent,
- a sandbox kit defines an gent from scratch (that's what you'd write for an agent sbx doesn't support yet).
Mine is a mixin, and it covers roughly everything you'd otherwise do by hand:
environment— env vars for the agentnetwork— allowed and denied domainscredentials— what the proxy should inject wherecommands.install/commands.startup— once at creation, and on every start- static files and
initFiles— config, scripts, docs agentContext— text appended to the agent's memory file
Trimmed to the parts that matter, pointing Claude Code at Azure AI Foundry:
schemaVersion: "2"
kind: mixin
name: claude-foundry
environment:
variables:
IS_SANDBOX: "1"
CLAUDE_CODE_USE_FOUNDRY: "1"
ANTHROPIC_FOUNDRY_RESOURCE: "my-foundry-resource"
ANTHROPIC_DEFAULT_OPUS_MODEL: "claude-opus-5"
ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-sonnet-5"
ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-haiku-4-5"
ANTHROPIC_FOUNDRY_API_KEY: "proxy-managed" # sentinel, not a secret
caps:
network:
allow:
- "my-foundry-resource.services.ai.azure.com"
- "dev.azure.com" # for the Azure DevOps MCP Server
credentials:
- service: anthropic # built-in secret provider - dont need to provide the format
apiKey:
inject:
- domain: "my-foundry-resource.services.ai.azure.com"
- service: azure-devops # custom secret provider - need to provide the format
apiKey:
inject:
- domain: "dev.azure.com"
header: Authorization
format: "Basic %s"
And the whole thing comes together on one line:
sbx create claude `
--template localhost:5000/my-template `
--kit .\my-claude-kit\ `
--name my-sandbox `
.\my-project\
Template and kit are independent on purpose: build the image once, layer thin kits on top per project.
Room service: how injection actually works
Since my last post, which was based on Docker sbx v0.31.0, the syntax/necessary config got simplified. Now (v0.37.0) supports more secret providers and custom secrets much better.
ANTHROPIC_FOUNDRY_API_KEY: "proxy-managed" above is the whole idea in one line. The agent boots with a sentinel value, sends it in the auth header, and the proxy swaps in the real credential before the request goes out. Klaus couldn't leak the key if someone talked him into it. He's never seen it.
Two things follow from that. It only works for HTTP/S. And it only works while the request actually goes through the proxy — a domain in NO_PROXY connects directly, gets no header, and fails with a 401 that points at nothing. I wrote that one up last time, and I still think it's the single most useful thing to internalise about sbx.
One heads-up on the schema
Kit spec v2 got released recently, and my kit above is written against it. If you're copying from older examples — including my last post — the field names moved: network.serviceDomains + serviceAuth became the credentials: list, memory: became agentContext:, kind: agent became kind: sandbox.
We are currently rolling out a very similar setup, but with GitHub Copilot CLI, at my work. Docker sbx still has some nasty bugs, like that credential injection breaks for existing sandboxes when the Windows host is rebooted. But as long as you keep your templates, kits and creation commands stored e.g. in git, you won't have any trouble.