Skip to main content
•6 min read

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.

DockerSandboxesSandbox KitssbxAI AgentsTutorial2026

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: compiles gh via Nix and copies only the result into a scratch image.
  • 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 github and re-run gh api user from 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.