Docker Sandbox Kits: A Hands-On Tutorial
A step-by-step walkthrough of Docker Sandboxes and Sandbox Kits: run a plain sandbox, compose a published Kit, then build your own GitHub CLI mixin.
Docker Sandboxes run AI agents in an isolated microVM. Sandbox Kits package what an agent is allowed to reach—network destinations, credentials, files—into a single OCI image alongside the agent itself.
This is a hands-on walkthrough, not an explainer. By the end, you'll have run a sandbox, composed a predefined Kit, and built your own. All commands are copy-paste ready and tested with sbx v0.47.0. The companion repository with every file is at github.com/ContainerSecurity-dev/sbx-examples.
Key Takeaways
- Install sbx and run a plain sandbox with no Kit at all.
- Confirm the default network policy blocks everything not explicitly allowed.
- Compose Docker's published docker/sbx-kit-shell workload with the docker/sbx-kit-gh mixin.
- Build the same mixin from source and run it locally, with no registry push required.
Technical Requirements
- macOS or Linux, with virtualization support (Apple Silicon or Intel VT-x/AMD-V)
- A free Docker Hub account
1. Install and Sign In
Install sbx following Docker's installation guide. On macOS:
brew trust docker/tap
brew install docker/tap/sbx
Sign in:
sbx login
This opens a device-code flow in your browser. Once confirmed, you'll see Signed in as <your-username>.
Start the daemon in its own terminal tab, and leave it running:
sbx daemon start
In a different tab, confirm everything is healthy:
sbx diagnose
Look for Daemon — healthy in the output.
2. Run a Plain Sandbox
No Kit yet—just the built-in shell agent:
sbx run shell --detached --name step1
This mounts your current directory, boots a microVM, and prints the sandbox ID. Confirm it's running:
sbx ls
Confirm the Default Network Policy
Every sandbox starts with a default-deny network policy. Try reaching a domain that isn't on the allowlist:
sbx exec step1 -- curl -sS -o /dev/null -w "%{http_code}\n" https://example.com
Expected output: 403.
For the full response body, drop the -o/-w flags:
sbx exec step1 -- curl -sS https://example.com
Approval required for example.com:443.
Review and respond with:
sbx policy approval ls
List the pending approval, and allow it if you want to:
sbx policy approval ls
sbx policy approval respond <APPROVAL-ID> --option allow
Clean up before moving on:
sbx rm -f step1
3. Use a Predefined Kit
Docker publishes ready-made Kits on Docker Hub. This step composes the official docker/sbx-kit-shell workload with the official docker/sbx-kit-gh mixin, which adds the GitHub CLI:
sbx run docker.io/docker/sbx-kit-shell:1.0.0 \
--kit docker.io/docker/sbx-kit-gh:2.100.0 \
--detached --name step2
Confirm gh is available inside the sandbox:
sbx exec step2 -- gh --version
gh version 2.100.0 (nixpkgs)
https://github.com/cli/cli/releases/tag/v2.100.0
Confirm the Kit's network policy: github.com is allowed, everything else is still denied by default:
sbx exec step2 -- curl -sS -o /dev/null -w "%{http_code}\n" https://github.com
sbx exec step2 -- curl -sS -o /dev/null -w "%{http_code}\n" https://example.com
Expected output: 200 then 403.
The mixin also declares a GitHub credential, but no token is bound yet, so authenticated calls fail cleanly instead of silently:
sbx exec step2 -- gh api user
gh: Requires authentication (HTTP 401)
Inspect the Kit's declared capabilities without running anything:
sbx kit inspect docker.io/docker/sbx-kit-gh:2.100.0
Name: sbx-kit-gh
Kind: mixin
Schema: v3
Display: GitHub CLI
Description: gh from nixpkgs, pinned by commit, as a self-contained overlay
Binary: gh
Template: docker.io/docker/sbx-kit-gh@sha256:86299d85fa8a260c79317ebefe13f3b1e3cf3275078102d9a93295ae6447fae0
Run Options: --help
Policies:
Network: 1 allow, 0 deny
Credentials: 1 sources
Clean up:
sbx rm -f step2
4. Build Your Own Kit
gh-mixin/ in the companion repository is the same GitHub CLI mixin as step 3, built from source instead of pulled from Docker Hub. It has three files:
gh.yaml— the Kit descriptor: what it provides, its network policy, and its credential binding.gh.dockerfile— the build recipe: compilesghvia Nix and copies only the result into ascratchimage.gh-context.md— guidance text injected into the agent's context.
Here's the descriptor in full:
# syntax=docker/sandbox-kit:3
schemaVersion: "3"
displayName: GitHub CLI
description: gh from nixpkgs, pinned by commit, as a self-contained overlay
sourceUrl: https://github.com/cli/cli
licenses: [MIT]
kind: mixin
provides: ["gh@2.101.0"]
capabilities:
- type: com.docker.sandbox/network-policy@2
config:
runtime:
allow:
- github.com
- hosts: [api.github.com]
methods: [GET, HEAD, POST, PATCH, PUT, DELETE]
- hosts: [uploads.github.com]
methods: [POST, PUT]
deny:
- hosts: [api.github.com]
methods: [DELETE]
paths: [/repos/**]
- type: com.docker.sandbox/credential@1
description: GitHub API access for gh
optional: true
config:
service: github
phase: runtime
apiKey:
name: GH_TOKEN
proxyManaged: true
inject:
- {domain: api.github.com, header: Authorization, format: "Bearer %s"}
- {domain: uploads.github.com, header: Authorization, format: "Bearer %s"}
- {domain: github.com, header: Authorization, format: "Bearer %s"}
- type: com.docker.sandbox/agent-context@1
config:
contentFile: ./gh-context.md
dockerfile: gh.dockerfile
Clone the companion repository, then compose your local mixin with the same published shell workload:
git clone https://github.com/ContainerSecurity-dev/sbx-examples.git
cd sbx-examples
sbx run docker.io/docker/sbx-kit-shell:1.0.0 \
--kit ./gh-mixin \
--detached --name step3
sbx builds gh-mixin/ locally the first time, then reuses the cached result on later runs. Verify it behaves exactly like step 3:
sbx exec step3 -- gh --version
sbx exec step3 -- curl -sS -o /dev/null -w "%{http_code}\n" https://github.com
sbx exec step3 -- curl -sS -o /dev/null -w "%{http_code}\n" https://example.com
Expected output: a gh version string, then 200, then 403—identical to the published Kit, because it's the same descriptor.
Clean up:
sbx rm -f step3
What You've Done
You ran a sandbox with no Kit, confirmed its default-deny network policy, composed a workload with a published mixin from Docker Hub, and built that same mixin from source without pushing it anywhere. The Kit descriptor you built is the same file format Docker and the community publish to Docker Hub—nothing about it was a toy example.
Next Steps
- Browse more published Kits on Docker Hub.
- Read the full Docker Sandbox Kit Specification for the complete capability-type reference and more example Kits.
- Bind a real GitHub token with
sbx secret set githuband re-rungh api userfrom step 3 to see an authenticated call succeed.
For the security reasoning behind Kits—why packaging authority alongside software matters, and what changed with zombie GitHub Actions the same month—see Docker Security Dispatch — Issue 7.
