diff --git a/content/guides/mistral-vibe-sandbox.md b/content/guides/mistral-vibe-sandbox.md index 833abaa2492..767caea87cc 100644 --- a/content/guides/mistral-vibe-sandbox.md +++ b/content/guides/mistral-vibe-sandbox.md @@ -168,7 +168,7 @@ Each field does the following: | Field | Purpose | | --------------------------- | -------------------------------------------------------------------------------------------------------------- | | `kind: sandbox` | Declares a sandbox agent: a complete image plus its launch configuration. | -| `name` | The kit's identifier, reused in the `sbx run` command. | +| `name` | The kit's identifier. Pass the kit directory to `sbx run`. | | `sandbox.image` | The pinned image you published in Step 3. Its `CMD` launches Vibe, so the kit doesn't set an entrypoint. | | `agentInstructions.filename`| The instructions file Vibe reads in the project. | | `agentInstructions.content` | Markdown appended to `AGENTS.md` at creation to prime the agent about its environment. | @@ -177,8 +177,8 @@ Each field does the following: | `credentials[].apiKey.name` | The environment variable the proxy manages. Vibe sees a sentinel value; the proxy swaps in the real key. | | `credentials[].apiKey.inject`| Where and how the proxy attaches the key. `scheme: bearer` sets `Authorization: Bearer ` for the domain. | -For the full kit format, see -[Kits](../manuals/ai/sandboxes/customize/kits.md). +This guide uses the deprecated v2 format. For its reference and migration +guidance, see [Kits v2](../manuals/ai/sandboxes/customize/kits-v2/_index.md). > [!WARNING] > `--agent auto-approve` runs Vibe in a mode that approves every tool @@ -197,13 +197,13 @@ $ sbx kit validate ./mistral-vibe Then, from your project directory, launch the agent with the kit: ```console -$ sbx run --kit ./mistral-vibe --name mistral-vibe mistral-vibe . +$ sbx run ./mistral-vibe --name mistral-vibe . ``` -- `--kit ./mistral-vibe` points to the folder that contains `spec.yaml`. +- `./mistral-vibe` is the sandbox kit reference, pointing to the folder that + contains `spec.yaml`. - `--name mistral-vibe` names the sandbox. Without it, `sbx` derives a name from the agent and the working directory, and the commands below won't match. -- `mistral-vibe` is the agent name from `spec.yaml`. - `.` is the project directory to mount in the sandbox. Vibe starts in an isolated microVM, talks to the Mistral API through the @@ -223,7 +223,7 @@ it. Use it to spot a host missing from `permissions.network.allow`. After you change `spec.yaml`, recreate the sandbox for a clean start: ```console -$ sbx rm mistral-vibe && sbx run --kit ./mistral-vibe --name mistral-vibe mistral-vibe . +$ sbx rm mistral-vibe && sbx run ./mistral-vibe --name mistral-vibe . ``` ## Clean up diff --git a/content/manuals/ai/sandboxes/agents/claude-code.md b/content/manuals/ai/sandboxes/agents/claude-code.md index e2df6832f6d..e8c3bcd564c 100644 --- a/content/manuals/ai/sandboxes/agents/claude-code.md +++ b/content/manuals/ai/sandboxes/agents/claude-code.md @@ -122,7 +122,7 @@ See [Git workflows](../workflows/git.md) for clone-mode details. ## Base image The sandbox uses `docker/sandbox-templates:claude-code`. See -[Templates](../customize/templates.md) to build your own image on top of +[Base images](../customize/base-images.md) to build your own image on top of this base. ## Use a local model diff --git a/content/manuals/ai/sandboxes/agents/docker-agent.md b/content/manuals/ai/sandboxes/agents/docker-agent.md index 656d1488e38..1f23ece4c58 100644 --- a/content/manuals/ai/sandboxes/agents/docker-agent.md +++ b/content/manuals/ai/sandboxes/agents/docker-agent.md @@ -69,5 +69,5 @@ $ sbx run --name -- run --yolo agent.yml ## Base image The sandbox uses `docker/sandbox-templates:docker-agent`. See -[Templates](../customize/templates.md) to build your own image on top of +[Base images](../customize/base-images.md) to build your own image on top of this base. diff --git a/content/manuals/ai/sandboxes/agents/shell.md b/content/manuals/ai/sandboxes/agents/shell.md index 5e26f6c8f94..29fc486a2b5 100644 --- a/content/manuals/ai/sandboxes/agents/shell.md +++ b/content/manuals/ai/sandboxes/agents/shell.md @@ -52,7 +52,7 @@ $ sbx secret set openai Once inside the shell, you can install agents using their standard methods, for example `npm install -g @continuedev/cli`. For complex setups, build a -[custom template](../customize/templates.md) instead of installing +[workload kit](../customize/kits.md) instead of installing interactively each time. ## Base image diff --git a/content/manuals/ai/sandboxes/configuration/credentials.md b/content/manuals/ai/sandboxes/configuration/credentials.md index 503016b7976..38418e730b4 100644 --- a/content/manuals/ai/sandboxes/configuration/credentials.md +++ b/content/manuals/ai/sandboxes/configuration/credentials.md @@ -37,7 +37,7 @@ value for the same service, the stored secret takes precedence. | [Stored secrets](#stored-secrets) (`sbx secret set`) | A value or dynamic source in your OS keychain, keyed by service | The default for any built-in or kit-declared service | | [Custom secrets](#custom-secrets) (`sbx secret set-custom`) | A value keyed to a domain and environment variable | The service model doesn't fit — the agent validates the variable's format, or the secret rides in a request body | | OAuth | A host-side sign-in flow; the token never enters the sandbox | The agent supports it, such as Claude Code, Codex, Cursor, or Droid | -| [Credential bindings](#credential-bindings) (`credentials.yaml`) | Per-service mechanism and domain approval | Required for third-party `schemaVersion: "2"` kits | +| [Credential bindings](#credential-bindings) (`credentials.yaml`) | Per-service mechanism and domain approval | Required for third-party kits | | [Registry credentials](#registry-credentials) (`sbx secret set --registry`) | Authentication for pulling images and kits | Pulling templates or kits from a private registry | For multi-provider agents (OpenCode, Docker Agent), the proxy selects @@ -211,8 +211,34 @@ it into requests to the listed API domains. ### Services declared by kits -Custom kits can declare their own service identifiers in `spec.yaml`. In -`schemaVersion: "2"`, credentials are declared under the `credentials:` list: +Custom kits declare their service identifiers in the kit descriptor. V3 uses +a credential capability. The deprecated v2 format uses a top-level +`credentials` list: + +{{< tabs >}} +{{< tab name="v3" >}} + +```yaml +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: [api.my-service.com] + - type: com.docker.runtime/credential@1 + config: + service: my-service + phase: runtime + apiKey: + name: MY_SERVICE_TOKEN + proxyManaged: true + inject: + - domain: api.my-service.com + header: Authorization + format: "Bearer %s" +``` + +{{< /tab >}} +{{< tab name="v2" >}} ```yaml credentials: @@ -223,8 +249,15 @@ credentials: inject: - domain: api.my-service.com scheme: bearer + +permissions: + network: + allow: [api.my-service.com] ``` +{{< /tab >}} +{{< /tabs >}} + Each service declares `apiKey`, `oauth`, or both. When both resolve at runtime, the API key takes precedence and OAuth acts as the fallback. To provide the credential value, run `sbx secret set` with the same identifier the kit @@ -237,7 +270,8 @@ $ sbx secret set my-service There's no separate registration step; the keychain entry is keyed on the identifier the kit already uses. See [Authenticate to external services](../customize/kits.md#authenticate-to-external-services) -for the full kit-side wiring. +for the v3 configuration. For existing v2 kits, see +[V2 credentials](../customize/kits-v2/_index.md#credentials). ### List and remove secrets @@ -434,8 +468,8 @@ you've approved for each service. It lives at `~/.config/sbx/credentials.yaml`, or `%APPDATA%\sbx\credentials.yaml` on Windows. -Third-party kits that declare `schemaVersion: "2"` require an approved binding -for each credential they use. `sbx` creates one interactively the first time you +Third-party kits require an approved binding for each credential they use, +regardless of schema version. `sbx` creates one interactively the first time you run such a kit (see [First-run approval](#first-run-approval)); you can also write entries by hand. Credentials declared only by embedded, built-in kits are authorized by provenance and don't need a binding. @@ -478,30 +512,28 @@ both cases, you approve the domains declared by the kit. `sbx` writes the entry to `credentials.yaml`. In non-interactive contexts (CI or `--detached`), there's no one to answer the -prompt. Without a binding, the sandbox starts with the credential withheld. If -the kit marks the credential as `required: true`, `sbx` also prints a warning. +prompt. Without a binding, the sandbox starts with the credential withheld. For a +required credential, `sbx` also prints a warning. Pre-create the binding by running the kit interactively once or by writing `credentials.yaml` directly before running unattended. -The bindings file gates whether a third-party v2 kit can use a service +The bindings file gates whether a third-party kit can use a service credential. The kit's credential injection rules and network permissions still constrain which requests can carry the credential. ### Kits that require a binding -Only third-party kits that declare `schemaVersion: "2"` require a binding. -Built-in agents also use `schemaVersion: "2"`, but credentials declared only by -embedded kits are authorized by provenance and inject automatically. A -third-party kit that extends a built-in agent inherits its credentials, but not -its built-in provenance. The inherited credentials therefore require approval. +Third-party kits require a binding regardless of schema version. Credentials +declared only by embedded kits are authorized by provenance and inject +automatically. A third-party v2 kit that extends a built-in agent inherits its +credentials, but not its built-in provenance. The inherited credentials therefore require approval. If a third-party kit declares the same service itself, that service also -requires approval. Kits on `schemaVersion: "1"` inject their declared -credentials without a binding. +requires approval. ## Registry credentials Registry credentials authenticate to private OCI registries when pulling -[templates](../customize/templates.md) or [kits](../customize/kits.md), and can +[templates](../usage.md#load-a-template) or [kits](../customize/kits.md), and can also let the agent pull and push images from inside the sandbox through the host-side proxy. Use `sbx secret set --registry ` to store them. For Docker Hub, `sbx` reuses your `sbx login` session — no registry secret needed. @@ -561,10 +593,12 @@ To scope the credential to a single sandbox, store it under that sandbox's name: $ gh auth token | sbx secret set --sandbox my-app --registry ghcr.io --password-stdin ``` -For Docker Hub, `sbx kit pull` and `sbx kit push` use the session from +For v2 kits on Docker Hub, `sbx kit pull` and `sbx kit push` use the session from `sbx login`. For other registries, both commands use these credentials. Both commands fall back to the Docker credential store, so credentials from -`docker login` also work. +`docker login` also work. V3 kits are +[published with Docker Buildx](../customize/kits.md#publish-an-image), +which uses the credentials from `docker login`. ### Remove registry credentials diff --git a/content/manuals/ai/sandboxes/configuration/registry-mirror.md b/content/manuals/ai/sandboxes/configuration/registry-mirror.md index dc4cb9dea03..3b398df7b15 100644 --- a/content/manuals/ai/sandboxes/configuration/registry-mirror.md +++ b/content/manuals/ai/sandboxes/configuration/registry-mirror.md @@ -54,7 +54,7 @@ mirror URL paths differently from image repository prefixes. Docker Engine connects to the mirror over HTTPS, so the sandbox must trust the certificate that the mirror presents. For a mirror that uses an internal certificate authority, add the CA to the sandbox's system trust store. See -[Install an internal CA certificate](../customize/kit-examples.md#install-an-internal-ca-certificate). +[Install an internal CA certificate](../customize/kits-v2/_index.md#install-an-internal-ca-certificate). Template and kit pulls use the changed setting immediately. Existing sandboxes retain the Docker Engine mirror configuration with which they were created. diff --git a/content/manuals/ai/sandboxes/customize/_index.md b/content/manuals/ai/sandboxes/customize/_index.md index 94e3434250a..f0d017bb80f 100644 --- a/content/manuals/ai/sandboxes/customize/_index.md +++ b/content/manuals/ai/sandboxes/customize/_index.md @@ -1,8 +1,8 @@ --- title: Customizing sandboxes linkTitle: Customize -description: Build reusable sandbox images and extend or define agents using templates and kits. -keywords: sandboxes, sbx, customize, templates, kits, mixins, custom agents +description: Build and share sandbox environments with v3 workload and mixin kits, using Docker-provided base images or your own Linux image. +keywords: sandboxes, sbx, customize, templates, kits, mixins, workloads, custom agents weight: 90 aliases: - /ai/sandboxes/agents/custom-environments/ @@ -15,56 +15,37 @@ params: {{< summary-bar feature_name="Docker Sandboxes sbx" >}} -Docker Sandboxes offers two ways to customize a sandbox beyond the built-in -defaults: +Use kits to package the tools, configuration, and runtime behavior your +sandboxes need. A workload kit defines the environment and command to run. +Mixin kits extend it with tools, configuration, or instructions. -- [Templates](templates.md) — reusable sandbox images with tools, packages, - and configuration baked in. Extend a base image with a Dockerfile, or - save a running sandbox as a template. -- [Kits](kits.md) — declarative YAML artifacts that extend an agent with - tools, credentials, network rules, and files at runtime, or define a new - agent from scratch. +A workload's build recipe can start from a Docker-provided agent image or +another Linux base image. Its descriptor declares network access, credentials, +and lifecycle hooks alongside that build. -Kits are experimental. The kit file format, CLI commands, and experience for -creating, loading, and managing kits are subject to change as the feature -evolves. Share feedback and bug reports in the +Kits are experimental. The format and CLI commands are subject to change. +Share feedback in the [docker/sbx-releases](https://github.com/docker/sbx-releases) repository. -## Templates and kits, side by side +## Choose a customization -A template is a Docker image that the sandbox runs. It's built ahead -of time with a Dockerfile (or saved from a running sandbox), pushed to a -registry, and pulled when a sandbox is created. Use templates for things -that belong in an image: system packages, language toolchains, large -dependencies — anything you'd rather not reinstall on every sandbox start. +| Goal | Option | +| --- | --- | +| Run a v3 workload and add mixins | [Use kits](kits.md#use-kits) | +| Define an agent or another sandbox workload | [Build an agent](build-an-agent.md) | +| Add tools, configuration, or instructions to a v3 workload | [Mixin examples](kit-examples.md) | +| Choose an existing agent image or your own Linux base | [Base images](base-images.md) | -A kit is a YAML artifact applied at sandbox creation. The kit can run -install commands, drop files into the sandbox, declare network and -credential rules, and (for sandbox kits) define which template image the -agent runs in. Use kits for things that vary per agent or per team: -shared linter config, project-specific install steps, credential -injection for a service the agent talks to. +## Start with v3 kits -Templates and kits work together. A sandbox kit's `sandbox.image` field -points at a template: the template provides the base environment, the -kit layers config, secrets, and runtime behavior on top. A team can ship -one heavy template and several thin kits without rebuilding the image -each time something changes. +V3 is the recommended format for kit development. To use it, select an +explicit v3 workload reference and combine it with v3 mixins. Built-in agent +shortcuts such as `claude` and `codex` select v2 kits, so they can't be used +with v3 mixins. See [Run a kit](kits.md#run-a-kit) for the opt-in workflow. -## When to use which +V2 is deprecated but remains supported. For existing customizations, see +[Kits v2](kits-v2/_index.md), including guidance for moving a complete +environment to v3. -| Goal | Option | -| --------------------------------------------------------- | ------------------------------------------------------------- | -| Pre-install tools and packages into a reusable base image | [Template](templates.md) | -| Capture a configured running sandbox for reuse | [Saved template](templates.md#saving-a-sandbox-as-a-template) | -| Add a tool, credential, or config to agent runs via YAML | [Kit (mixin)](kits.md) | -| Define a new agent from scratch | [Kit (sandbox)](kits.md#define-an-agent) | - -Templates and kits can be used together. A template bakes heavy tools into -the image for fast sandbox startup; a kit layered on top adds per-run -credentials, config, or extra capabilities. - -## Tutorials - -- [Build your own agent kit](build-an-agent.md) — step-by-step walkthrough - for packaging [Amp](https://ampcode.com/) as a sandbox kit. +To capture an interactively configured sandbox's container filesystem for reuse, +see [Save a sandbox as a template](/manuals/ai/sandboxes/usage.md#saving-a-sandbox-as-a-template). diff --git a/content/manuals/ai/sandboxes/customize/base-images.md b/content/manuals/ai/sandboxes/customize/base-images.md new file mode 100644 index 00000000000..0157aa38a81 --- /dev/null +++ b/content/manuals/ai/sandboxes/customize/base-images.md @@ -0,0 +1,162 @@ +--- +title: Base images for sandbox workloads +linkTitle: Base images +weight: 25 +description: Choose a Docker-provided agent image or your own Linux base image for a sandbox workload kit, and use image overrides with built-in agents. +keywords: sandboxes, sbx, kits, base images, templates, dockerfile, custom agents +aliases: + - /ai/sandboxes/customize/templates/ +--- + +{{< summary-bar feature_name="Docker Sandboxes sbx" >}} + +A workload kit's Dockerfile defines the sandbox's filesystem. Start from a +Docker-provided agent image to reuse its tools and setup, or choose another +Linux image to control the operating system and prepare the environment yourself. +The kit descriptor declares the workload's runtime requirements alongside that +build recipe. + +To save and reuse an environment you've configured interactively, see +[Save a sandbox as a template](/manuals/ai/sandboxes/usage.md#saving-a-sandbox-as-a-template). + +## Base images + +All sandbox templates are published as +`docker/sandbox-templates:`. They are based on Ubuntu and run as a +non-root `agent` user with sudo access. Most variants include Git, Docker +CLI, and common development tools like Node.js, Python, Go, and Java. + +| Variant | Agent | +| --------------------- | -------------------------------------------------------------------- | +| `claude-code` | [Claude Code](https://claude.ai/download) | +| `claude-code-minimal` | Claude Code with a minimal toolset (no Node.js, Python, Go, or Java) | +| `codex` | [OpenAI Codex](https://github.com/openai/codex) | +| `copilot` | [GitHub Copilot](https://github.com/github/copilot-cli) | +| `cursor-agent` | [Cursor](https://cursor.com/cli) | +| `devin` | [Devin CLI](https://docs.devin.ai/work-with-devin/devin-cli) | +| `docker-agent` | [Docker Agent](https://github.com/docker/docker-agent) | +| `droid` | [Droid](https://www.factory.ai) | +| `gemini` | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | +| `kiro` | [Kiro](https://kiro.dev) | +| `opencode` | [OpenCode](https://opencode.ai) | +| `shell` | No agent pre-installed. Use for manual agent setup. | + +## Use an image in a v3 workload + +Reference the image in your workload's Dockerfile with `FROM`, then add the +tools and configuration you need. System package installations run as `root`; +switch back to `agent` before installing tools into the agent's home directory. +Otherwise, user-level installers put files under `/root/`, where the agent +can't use them. + +The [workload example](kits.md#build-a-workload) extends the OpenCode image +with Python tooling and pairs it with a kit descriptor. `sbx` builds the +image as part of the kit, so you don't need to build and distribute a separate +template first. + +The base image supplies filesystem content and image settings. It doesn't +replace the kit's declarations for network access, credentials, storage, or +lifecycle hooks. Using a Docker-provided template image as a build base also +doesn't select a built-in v2 kit: the workload descriptor defines the kit +version. See [Version compatibility](kits.md#version-compatibility). + +## Use your own Linux image + +You don't have to derive your workload from a Docker-provided image. Choose a +Linux base image that suits your operating system and package requirements, +and prepare it for the sandbox runtime. + +See [Base image requirements](kit-reference.md#base-image-requirements) for the +required tools, user account, and launch configuration. The +[Build an agent](build-an-agent.md) tutorial walks through preparing a base +image, installing an agent, and declaring its runtime requirements. + +## Image overrides for built-in agents + +Built-in agent shortcuts such as `claude` and `codex` use v2 kits. Their +`--template` option selects a replacement image for that agent. Installing a +different agent binary in the image doesn't change which agent the built-in +kit launches. For v3 customization, define the image build and launch command +in a workload kit instead. + +Each variant also has a `-docker` version (for example, `claude-code-docker`) +that includes a full Docker Engine running inside the sandbox — no local Docker +daemon required. When you pick a built-in agent without specifying a custom +template, `sbx run` and `sbx create` use the `-docker` template variants by +default. + +The agent containers created from the `-docker` templates run in privileged +mode inside the microVM (not on your host), with a dedicated block volume at +`/var/lib/docker`, and `dockerd` starts automatically inside the sandbox. The +block volume defaults to 10 GB and uses a sparse file, so it only consumes +disk space as Docker writes to it. + +To change the volume size for a sandbox, set +`DOCKER_SANDBOXES_DOCKER_SIZE` when you create it: + +```console +$ DOCKER_SANDBOXES_DOCKER_SIZE=20g sbx run claude +``` + +The volume size must be at least 512 MiB. The environment variable doesn't +resize existing volumes. + +Use the non-Docker variant if you don't need to build or run containers +inside the sandbox and want a lighter, non-privileged environment. Specify +it explicitly with `--template`: + +```console +$ sbx run claude --template docker.io/docker/sandbox-templates:claude-code +``` + +### Build a custom template + +Building a custom template requires +[Docker Desktop](/manuals/desktop/_index.md). + +Write a Dockerfile that extends one of the base images. Pick the variant +that matches the agent you plan to run. For example, extend `claude-code` +to customize a Claude Code environment, or `codex` to customize an OpenAI +Codex environment. + +The following example creates a Claude Code template with Rust and +protocol buffer tools pre-installed: + +```dockerfile +FROM docker/sandbox-templates:claude-code +USER root +RUN apt-get update && apt-get install -y protobuf-compiler +USER agent +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +``` + +Build the image and push it to an OCI registry, such as Docker Hub: + +```console +$ docker build -t my-org/my-template:v1 --push . +``` + +For registry credentials and loading a locally built image, see +[Load a template](/manuals/ai/sandboxes/usage.md#load-a-template). + +Unless you use the permissive `allow-all` network policy, you may also need +to allow-list any domains that your custom tools depend on: + +```console +$ sbx policy allow network "*.example.com:443,example.com:443" +``` + +Then run a sandbox with your template. The agent you specify must match +the base image variant your template extends: + +```console +$ sbx run --template docker.io/my-org/my-template:v1 claude +``` + +Because this template extends the `claude-code` base image, you run it +with `claude`. If you extend `codex`, use `codex`; if you extend `shell`, +use `shell` (which drops you into a Bash shell with no agent). + +> [!NOTE] +> Unlike Docker commands, `sbx` does not automatically resolve the Docker +> Hub domain (`docker.io`) in image references. diff --git a/content/manuals/ai/sandboxes/customize/build-an-agent.md b/content/manuals/ai/sandboxes/customize/build-an-agent.md index 3d7f111dc30..063091fa800 100644 --- a/content/manuals/ai/sandboxes/customize/build-an-agent.md +++ b/content/manuals/ai/sandboxes/customize/build-an-agent.md @@ -1,8 +1,8 @@ --- title: Build your own agent kit linkTitle: Build an agent -description: Walk through building an agent kit for Amp, from base image choice to invocation. -keywords: sandboxes, sbx, kits, agent, tutorial, amp, ampcode +description: Build a schema v3 agent workload from your own Linux base image, prepare its sandbox environment, and configure credentials and agent instructions. +keywords: sandboxes, sbx, kits, agent, tutorial, claude, workload, build weight: 30 --- @@ -10,317 +10,385 @@ weight: 30 > [!NOTE] > Kits are experimental. The kit file format, CLI commands, and experience -> for creating, loading, and managing kits are subject to change as the -> feature evolves. Share feedback and bug reports in the -> [docker/sbx-releases](https://github.com/docker/sbx-releases) repository. - -This tutorial walks through building an agent kit for the -[Amp](https://ampcode.com/) coding agent. Each step explains the decision -behind a part of the spec, so you can apply the same reasoning to other agents. - -For reference on every field, see the [Kit spec reference](kit-reference.md). -This tutorial focuses on the journey. - -The finished kit is also published as a runnable sample at -[docker/sbx-kits-contrib](https://github.com/docker/sbx-kits-contrib/tree/main/amp) — -useful as a reference while you follow along. - -## Choose a base image - -An agent kit needs a container image that satisfies the -[base image requirements](kit-reference.md#base-image-requirements): non-root -`agent` user at UID 1000, passwordless sudo, `/home/agent/` home, and HTTP -proxy environment variable forwarding. - -Rather than build an image from scratch, extend one of the published -sandbox templates. Three common starting points: - -- `docker/sandbox-templates:shell`. Generic base with no pre-installed - agent. -- `docker/sandbox-templates:shell-docker`. Same, with Docker Engine inside - the sandbox. -- Agent-specific variants (`claude-code`, `codex`, etc.). Only useful if - you're extending that specific agent. - -For Amp, pick `shell-docker`: - -- Amp isn't pre-installed in any variant, so you need a generic base - (`shell`). -- Docker support is handy since coding agents often need to run containers. -- If you don't need Docker inside the sandbox, use the `shell` tag for a - lighter, non-privileged environment. - -## Plan authentication - -Amp authenticates with an API key in `AMP_API_KEY`. To keep the real key -out of the VM, you split the work in two: - -- The kit's network section maps the API host to a service identifier - and tells the proxy which header to inject. -- You provide your key once on the host, via sbx's secret store. The - real value stays on the host; only a placeholder reaches the sandbox. - -Inside the sandbox `AMP_API_KEY` is set to that placeholder. The proxy -substitutes the real key on outbound requests to the API host, so the -secret never enters the sandbox. A later section walks through the -specific command for storing the key. - -## Write the sandbox block - -The `sandbox:` block tells the sandbox how to launch Amp when the user -attaches. - -```yaml {title="amp/spec.yaml"} -schemaVersion: "1" -kind: sandbox -name: amp -displayName: Amp -description: The frontier coding agent. - -sandbox: - image: "docker/sandbox-templates:shell-docker" - aiFilename: AGENTS.md - entrypoint: - run: [amp, --dangerously-allow-all] +> for creating, loading, and managing kits are subject to change. Share +> feedback in the [docker/sbx-releases](https://github.com/docker/sbx-releases) +> repository. + +Build a sandbox environment from a Linux base image you choose, then package +it as a schema v3 workload kit. You'll prepare the operating system, install +Claude Code, and configure its model, API access, and agent instructions. +The same approach applies to other agents and organization-maintained images. + +For a shorter example that extends an existing agent environment, see +[Build a workload](kits.md#build-a-workload). This tutorial builds up the +whole environment and its descriptor step by step. If you're starting with +kits, read [Kits v3](kits.md) for the file layout and the roles of a workload +and a mixin. For field definitions, see the [Kit spec reference](kit-reference.md). + +This creates an independent v3 workload. It doesn't inherit the built-in +`claude` kit, which uses v2. Any mixins you add must also use v3. For existing +customizations in the deprecated format, see [Kits v2](kits-v2/_index.md). + +## Prepare the kit directory + +You need `sbx` with schema v3 support, Docker with Buildx, and an Anthropic +API key. Create a directory beside the project you want the agent to work on: + +```console +$ mkdir claude-team ``` -- `aiFilename: AGENTS.md` tells the sandbox to create `AGENTS.md` at launch - and append the [`agentContext`](#prime-amp-with-memory) block to it. Amp reads - this file for instructions. -- `entrypoint.run` runs `amp` in "YOLO-mode" when the sandbox starts. Adjust if - you want to pass different args on startup. +The completed directory contains three files: -## Install Amp +```text +claude-team/ +├── claude-team.yaml +├── claude-team.dockerfile +└── context.md +``` + +The YAML descriptor declares the kit's requirements. Its companion Dockerfile +builds the agent and defines the launch command. The Markdown file contains +instructions the agent can read. Matching the YAML and Dockerfile stems lets +the kit frontend find the recipe. + +## Use your own base image + +A Docker sandbox template is optional. Your Dockerfile can prepare a Linux +base image with the tools, account, and certificate store that the sandbox +needs. This tutorial uses Red Hat Universal Base Image (UBI) 9 as a concrete +example. For another base, adapt the package installation and account creation +to its existing contents while preserving the +[base image requirements](kit-reference.md#base-image-requirements). -Amp installs via a curl-to-bash script: +### Install the system packages -```yaml -commands: - install: - - command: "curl -fsSL https://ampcode.com/install.sh | bash" - user: "1000" - description: Install Amp +Create `claude-team/claude-team.dockerfile` with the base image and packages: + +```dockerfile {title="claude-team/claude-team.dockerfile"} +FROM registry.access.redhat.com/ubi9/ubi:9 + +USER root +RUN dnf install -y bash ca-certificates curl-minimal git shadow-utils tar gzip \ + && dnf clean all ``` -Note `user: "1000"`. That's the agent user. Install commands run as root -(UID 0) by default, and Amp's installer puts the binary in the user's home -directory. Running as root would land the binary in `/root/` where the -agent can't reach it. - -## Allow network access - -The network block does two things: it lists the hosts the sandbox can -reach (`allowedDomains`), and it wires the kit-side half of the auth flow -from [Plan authentication](#plan-authentication) with `serviceDomains` and -`serviceAuth`. - -```yaml -network: - serviceDomains: - ampcode.com: amp - serviceAuth: - amp: - headerName: Authorization - valueFormat: "Bearer %s" - allowedDomains: - - "ampcode.com:443" - - "*.ampcode.com:443" +Bash, Git, curl, and CA certificates provide the shell, source control, and +HTTPS tools used in the sandbox. The `shadow-utils` package provides account +creation commands. The archive tools support agent installation. Add any +compilers, libraries, or other tools your projects need here. + +### Create the agent account + +The sandbox needs a non-root `agent` user with UID 1000 and home directory +`/home/agent`. Append the following to the Dockerfile to create that account +and writable directories for its workspace, configuration, and state: + +```dockerfile {title="Append to claude-team/claude-team.dockerfile"} +RUN groupadd --gid 1000 agent \ + && useradd --uid 1000 --gid 1000 --create-home --shell /bin/bash agent \ + && mkdir -p /home/agent/workspace /home/agent/.local/bin \ + /home/agent/.local/share /home/agent/.local/state \ + /home/agent/.config/claude-team /home/agent/.docker/sandbox/locks \ + && chown -R agent:agent /home/agent ``` -`allowedDomains` here covers the apex (`ampcode.com`) and the -install/CDN subdomains (`*.ampcode.com`). Treat it as a starting point; -Amp may reach other domains (model providers, analytics, updates) that -you'll discover by watching `sbx policy log` while testing. - -Kits can also declare `deniedDomains` for hosts the sandbox should not -reach, such as telemetry endpoints. Deny rules take precedence over -allow rules and apply only to sandboxes that use the kit. - -For the auth wiring, when the agent makes an outbound request to -`ampcode.com`, the proxy looks up the host in `serviceDomains` to find -the service id `amp`, then uses `serviceAuth.amp` to inject an -`Authorization: Bearer ` header. The `` value comes from the -secret you'll register in -[Register your API key](#register-your-api-key), matched by host. The -service id (`amp`) is just a label that ties the two blocks together — -pick any name. - -> [!IMPORTANT] -> Keep `serviceDomains` narrow. Mapping `*.ampcode.com` would push the -> proxy into TLS-intercepting mode for every subdomain — including the -> binary CDN the install script downloads from — which corrupts those -> downloads. List only the host that actually needs auth. - -## Prime Amp with memory - -The `agentContext` field appends markdown to `AGENTS.md` at sandbox creation. -Use it to tell Amp about the sandbox environment so it knows the -conventions when it starts. - -```yaml -agentContext: | - ## Sandbox environment - - You are running inside a Docker sandbox. The workspace is mounted at - its absolute host path. `sudo` is passwordless; use it for package - installs. Docker is available inside the sandbox; containers you start - are isolated in the microVM. +Creating these directories as part of the build gives them the intended +ownership before runtime mounts are applied. This example runs the agent +without `sudo`. Install system packages in the Dockerfile. Reserve runtime +install hooks for configuration that depends on an individual sandbox. + +### Prepare certificate trust + +Docker Sandboxes adds its proxy CA at runtime so clients can make HTTPS +requests through the sandbox proxy. UBI stores its public CA roots at a +different path from the bundle used by `sbx`. Append the following to copy +those roots to that path and direct clients to the combined bundle: + +```dockerfile {title="Append to claude-team/claude-team.dockerfile"} +RUN update-ca-trust \ + && mkdir -p /usr/local/share/ca-certificates /etc/ssl/certs \ + && cp /etc/pki/tls/certs/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt + +ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \ + CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \ + REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \ + NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt ``` -Keep this short and sandbox-specific. For project instructions, put a -regular `AGENTS.md` in the workspace. - -## The full spec - -Putting it all together: - -```yaml {title="amp/spec.yaml"} -schemaVersion: "1" -kind: sandbox -name: amp -displayName: Amp -description: The frontier coding agent. - -sandbox: - image: "docker/sandbox-templates:shell-docker" - aiFilename: AGENTS.md - entrypoint: - run: [amp, --dangerously-allow-all] - -network: - serviceDomains: - ampcode.com: amp - serviceAuth: - amp: - headerName: Authorization - valueFormat: "Bearer %s" - allowedDomains: - - "ampcode.com:443" - - "*.ampcode.com:443" - -commands: - install: - - command: "curl -fsSL https://ampcode.com/install.sh | bash" - user: "1000" - description: Install Amp - -agentContext: | - ## Sandbox environment - - You are running inside a Docker sandbox. The workspace is mounted at - its absolute host path. `sudo` is passwordless; use it for package - installs. +If you add corporate CA certificates during the build, add them to +`/etc/pki/ca-trust/source/anchors/` before running `update-ca-trust` and +copying the bundle. For another distribution, use its certificate management +command and source bundle path. + +### Prepare the shell environment + +Give the agent a writable file for environment exports that need to persist +across shell commands. Append the following to create the file, source it +from login and interactive shells, and set `BASH_ENV` for non-interactive Bash: + +```dockerfile {title="Append to claude-team/claude-team.dockerfile"} +RUN touch /etc/sandbox-persistent.sh \ + && chown agent:agent /etc/sandbox-persistent.sh \ + && chmod 0644 /etc/sandbox-persistent.sh \ + && printf '%s\n' '. /etc/sandbox-persistent.sh' \ + > /etc/profile.d/sandbox-persistent.sh \ + && printf '%s\n' '. /etc/sandbox-persistent.sh' >> /home/agent/.bashrc + +ENV HOME=/home/agent \ + PATH="/home/agent/.local/bin:${PATH}" \ + BASH_ENV=/etc/sandbox-persistent.sh ``` -## Register your API key +The agent instructions later in this tutorial explain how to use this file. +This completes the operating system preparation. The next step installs the +agent into that environment. -Register your Amp API key on the host with `sbx secret set-custom`. The -value goes into the host secret store, and a placeholder is exposed -inside every sandbox you launch from this kit. +## Build the agent into the image -Amp validates `AMP_API_KEY`'s format at startup, so the placeholder needs -to look like a real Amp key. Pick a placeholder shape that matches Amp's -expected format: +Append the following to the Dockerfile to install Claude Code as `agent` and +set its launch command: -```console -$ sbx secret set-custom \ - --host ampcode.com \ - --env AMP_API_KEY \ - --placeholder "sgamp-{rand}" \ - --value "$AMP_API_KEY" +```dockerfile {title="Append to claude-team/claude-team.dockerfile"} +USER agent +ENV CLAUDE_ENV_FILE=/etc/sandbox-persistent.sh \ + IS_SANDBOX=1 +ARG CLAUDE_VERSION +RUN curl -fsSL https://claude.ai/install.sh -o /tmp/install-claude.sh \ + && bash /tmp/install-claude.sh "${CLAUDE_VERSION}" \ + && rm /tmp/install-claude.sh + +WORKDIR /home/agent/workspace +ENTRYPOINT ["claude", "--settings", "/home/agent/.config/claude-team/settings.json"] +CMD [] ``` -`{rand}` expands to a random suffix at registration time. Inside the -sandbox `AMP_API_KEY` is set to that placeholder; Amp accepts it as a -syntactically valid key, and the proxy substitutes the real secret on -outbound requests to `ampcode.com`. +Installing as `agent` puts Claude Code under `/home/agent/`, where the launch +user can access it. The `CLAUDE_VERSION` build argument receives its value +from the descriptor in the next step. `CLAUDE_ENV_FILE` points Claude Code +to the persistent environment file you prepared above. + +The Dockerfile owns the image's `ENTRYPOINT`, `CMD`, environment, user, and +working directory. `CMD []` clears inherited arguments. This tutorial uses +Claude Code's `--settings` option to load a model setting chosen when creating +the sandbox. You'll declare that settings file in +[Write the model settings](#write-the-model-settings). + +Installing Claude Code belongs in the build recipe because the binary is the +same in every sandbox using this kit. BuildKit can cache that work. The image +provides the software and launch command; the descriptor that follows declares +its runtime needs. Any mixins added to this workload must use v3 and provide +tools compatible with its operating system and architecture. + +## Describe the workload and its inputs + +Create `claude-team/claude-team.yaml`. Start by identifying the workload and +declaring its two inputs: the agent version to build and the model to use +when the sandbox runs. + +```yaml {title="claude-team/claude-team.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: workload +displayName: Team Claude Code +description: Claude Code with team defaults and API-key authentication + +args: + version: + default: "2.1.259" + pattern: '^[0-9]+\.[0-9]+\.[0-9]+$' + buildArg: CLAUDE_VERSION + model: + default: sonnet + enum: [sonnet, opus, haiku] + +provides: ["claude@${{ kit.args.version }}"] +``` -> [!TIP] -> `sbx secret set-custom` is only required because Amp validates the -> key's format. If your agent reads the env var without a local format -> check, you can declare `environment.proxyManaged: [AMP_API_KEY]` in -> the kit instead and skip this user-side step — the proxy uses a -> default sentinel value (`proxy-managed`) that the agent never sees -> rejected. +The `version` input maps to `CLAUDE_VERSION` in the Dockerfile. The build +validates the value and substitutes it into `provides`, which tells other +kits which agent version this workload supplies. -> [!NOTE] -> `sbx secret set-custom` is experimental and may change in future -> releases. This tutorial surfaces it because there's no other path to -> register a custom-format placeholder. +The `model` input will be used in the settings file. It is chosen when creating +a sandbox and doesn't change the installed agent binary. For local sources, +changing this argument can still invoke Buildx, which can reuse cached image +layers. -## Run it +## Allow access to the API -Validate the spec: +Claude Code needs to reach the Anthropic API. Add a `capabilities` list at +the top level of `claude-team.yaml`, after `provides`. Its first entry declares +that network access: -```console -$ sbx kit validate ./amp/ +```yaml {title="Add to claude-team/claude-team.yaml"} +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: + - api.anthropic.com:443 +``` + +This rule applies when the sandbox runs. Downloading Claude Code in the +Dockerfile happens during the build and uses the builder's network. + +## Declare the credential + +The network rule permits a connection. A credential capability tells Docker +Sandboxes how to authenticate requests on that connection using a key stored +on the host. + +Append this entry to the same `capabilities` list: + +```yaml {title="Append under capabilities"} + - type: com.docker.runtime/credential@1 + description: Anthropic API access + config: + service: anthropic + phase: runtime + apiKey: + name: ANTHROPIC_API_KEY + proxyManaged: true + inject: + - domain: api.anthropic.com + header: x-api-key + format: "%s" +``` + +The sandbox receives a placeholder in `ANTHROPIC_API_KEY`. When Claude Code +makes a request to `api.anthropic.com`, the host proxy inserts the real API +key into the `x-api-key` header. The key stays on the host. You will supply +its value and approve its use when launching the kit. + +## Write the model settings + +The Dockerfile's launch command reads +`/home/agent/.config/claude-team/settings.json`. Use the lifecycle capability +to create that file with the model chosen for the sandbox. + +Append this entry to `capabilities`: + +```yaml {title="Append under capabilities"} + - type: com.docker.runtime/lifecycle@1 + config: + files: + - path: /home/agent/.config/claude-team/settings.json + content: | + {"model": "${{ kit.args.model }}"} + mode: "0644" +``` + +Docker Sandboxes substitutes the `model` argument and writes the file before +Claude Code starts. This work belongs to sandbox creation because the value +can differ between sandboxes using the same image. + +## Add agent instructions + +The kit can also give Claude Code instructions about the environment. Save the +following Markdown alongside the descriptor and Dockerfile: + +```markdown {title="claude-team/context.md"} +## Team workflow + +Read the project's README before changing code. Run the project's checks +before reporting a task complete, and report any checks you couldn't run. + +Claude Code is installed in this sandbox. Its additional settings are at +`/home/agent/.config/claude-team/settings.json`. + +Use `/etc/sandbox-persistent.sh` for environment exports needed by later +Bash commands. Keep shell completion scripts out of that file because +non-interactive commands also source it. ``` -Launch the sandbox by passing the kit directory in place of a built-in agent -name: +Append an agent-context entry to `capabilities` to include these instructions: + +```yaml {title="Append under capabilities"} + - type: com.docker.runtime/agent-context@1 + config: + filename: CLAUDE.md + contentFile: ./context.md +``` + +The build includes `context.md` in the image. At runtime, `sbx` generates a +`CLAUDE.md` profile in the parent directory of the mounted workspace inside +the sandbox. The profile points to the packaged `context.md` file for the +agent to read. It doesn't replace a `CLAUDE.md` in your project. See +[Agent instructions](kits.md#give-the-agent-instructions) for how workload +and mixin instructions contribute to the profile. + +## Store the key and run + +Store your Anthropic API key on the host: ```console -$ sbx run ./amp/ +$ sbx secret set anthropic ``` -The published copy of this kit also runs directly from the contrib -repository: +From the directory containing `claude-team`, launch the kit against your +project: ```console -$ sbx run "git+https://github.com/docker/sbx-kits-contrib.git#dir=amp" +$ sbx run --name claude-team ./claude-team ``` -## Iterate +`sbx` builds the local directory, loads the kit, and launches Claude Code. +Approve the kit's credential request to connect the stored key to this kit, +then follow Claude Code's first-run prompts. Without an approved credential +binding, storing a key alone doesn't authenticate the agent. See +[Credential bindings](../configuration/credentials.md#credential-bindings). -As you use the kit, you'll likely hit missing domains or install quirks. -Two loops help: +Choose a different model when creating a sandbox: -- Watch the network policy log (`sbx policy log`) to catch blocked - requests, then add their domains to `allowedDomains`. -- Add domains to `deniedDomains` when the agent should stay blocked from - a host even if another policy permits it. -- Edit the spec and re-run `sbx run ./amp/` to pick up changes. - Remove the sandbox first (`sbx rm `) for a clean start. +```console +$ sbx run --name claude-team-opus ./claude-team \ + --kit-arg claude-team.model=opus +``` -Flesh out the `agentContext` block as you refine how Amp should behave in the -sandbox. +The argument prefix is the local kit directory's name. The model value is +validated against the descriptor's `enum` and written to the settings file +before Claude Code starts. -## Publish +## Iterate and publish -Once the kit works, share it by packaging as a ZIP, pushing to an OCI -registry, or committing to a Git repository. See -[Packaging and distribution](kits.md#packaging-and-distribution) for the -`sbx kit` subcommands. +Edit the descriptor, Dockerfile, or context file and create another sandbox +with a different name to test the changes: -## Adapt this to another agent +```console +$ sbx run --name claude-team-test-2 ./claude-team +``` -Most of the specifics here are Amp's. To port the pattern, work through -the same decisions for your agent: +Running an existing sandbox keeps its recorded configuration. During sandbox +creation, `sbx` reuses a local build when both the source content and supplied +kit arguments are unchanged. Changing either can invoke Buildx, including +changes to create-time arguments such as `model`. BuildKit can still reuse +unchanged image layers. To change the installed agent version for local runs, +update `args.version.default` in the descriptor. -- **Base image**: `shell-docker` if you need Docker inside the sandbox, - `shell` otherwise. Or extend either with your own image if the install - is heavy. -- **Install**: a `commands.install` block at runtime, or bake the agent - into a custom image. Pick install if it's a one-line script; bake if - the install is slow or you need a pinned version. -- **Network mapping**: list only the API host in `serviceDomains`, not - a wildcard. Keep install/CDN paths out of TLS-intercepting mode. Use - `deniedDomains` for hosts the agent should not reach. -- **Credential injection**: if the agent validates the API key's format - locally, register with `sbx secret set-custom` and pick a matching - placeholder. If it accepts the env var as-is, declare - `environment.proxyManaged` in the kit and skip the user-side step. +When the kit is ready to share, sign in to Docker Hub, then build and push it +with Docker Buildx. Replace `` with a Docker Hub namespace you can +push to: -The rest — agent-context block, network-policy iteration, packaging — is the -same regardless of agent. +```console +$ docker login +$ docker buildx build ./claude-team \ + --file ./claude-team/claude-team.yaml \ + --tag docker.io//claude-team:1.0.0 \ + --push +``` -## Remove the stored secret +Buildx reads the descriptor as the build file. Its syntax directive selects +the kit frontend, which builds the companion Dockerfile and publishes the +declarations with the image. To override the binary version for a published +build, add `--build-arg version=`. Use the kit argument name +`version` in this flag, rather than the Dockerfile's `CLAUDE_VERSION` name. -To remove the entry created earlier with `sbx secret set-custom`, pass -the host to `sbx secret rm`: +Run the published kit by its image reference: ```console -$ sbx secret rm --host ampcode.com +$ sbx run --name claude-team-shared docker.io//claude-team:1.0.0 ``` -The `--host` flag is part of the experimental `set-custom` surface and doesn't appear in `sbx secret rm --help`. +For build layouts, multi-platform images, and distribution details, see +[Kits v3](kits.md). To add tools or shared configuration to this workload, +see [Kit examples](kit-examples.md). diff --git a/content/manuals/ai/sandboxes/customize/kit-examples.md b/content/manuals/ai/sandboxes/customize/kit-examples.md index 615b9108704..bb3510ff3a9 100644 --- a/content/manuals/ai/sandboxes/customize/kit-examples.md +++ b/content/manuals/ai/sandboxes/customize/kit-examples.md @@ -1,8 +1,8 @@ --- title: Kit examples linkTitle: Examples -description: Copy-and-adapt spec.yaml snippets for common mixin and sandbox kit patterns — static files, install commands, shell customization, background services, setup files, Claude Code skills, and agent forks. -keywords: sandboxes, sbx, kits, mixins, examples, patterns, skills +description: Build schema v3 kits that add tools, shared files, runtime configuration, lifecycle hooks, and agent instructions to a sandbox workload. +keywords: sandboxes, sbx, kits, mixins, examples, capabilities, build, lifecycle weight: 40 --- @@ -10,458 +10,419 @@ weight: 40 > [!NOTE] > Kits are experimental. The kit file format, CLI commands, and experience -> for creating, loading, and managing kits are subject to change as the -> feature evolves. Share feedback and bug reports in the -> [docker/sbx-releases](https://github.com/docker/sbx-releases) repository. +> for creating, loading, and managing kits are subject to change. Share +> feedback in the [docker/sbx-releases](https://github.com/docker/sbx-releases) +> repository. + +These schema v3 examples show how to add tools, configuration, and instructions +to a workload. Each section is a separate customization with its own kit files. +The run commands use the [sample shell workload](#create-a-workload-for-the-examples) +unless the example names another agent. Save mixin directories beside the +workload directory and run the commands from their parent directory. + +These mixins require a v3 workload. They can't be used with the v2 built-in +agent shortcuts, such as `claude` or `codex`. Select a v3 workload before +adding these mixins. For existing customizations in the deprecated format, +see [Kits v2](kits-v2/_index.md). + +For concepts and source layout, see [Kits v3](kits.md). +For field definitions, see the [Kit spec reference](kit-reference.md). + +## Create a workload for the examples + +This shell workload gives you an environment for inspecting files and running +tools contributed by mixins. Create a `shell-v3` directory with these two +files. The descriptor names `AGENTS.md` as the profile used by the +[agent-instructions example](#contribute-agent-instructions): + +```yaml {title="shell-v3/shell-v3.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: workload +displayName: Example shell + +capabilities: + - type: com.docker.runtime/agent-context@1 + config: + filename: AGENTS.md + content: This shell is an environment for testing composed kits. +``` -Each section below shows one `spec.yaml` snippet that demonstrates a -single kit pattern. These aren't complete, distributable kits — they're -small, focused examples you can lift into your own kit. For the full -spec reference, see [Kit spec reference](kit-reference.md). +The Dockerfile supplies the shell environment: -## Drop a shared config file +```dockerfile {title="shell-v3/shell-v3.dockerfile"} +FROM docker/sandbox-templates:shell +USER agent +ENTRYPOINT ["bash"] +CMD ["-l"] +``` -Use static files under `files/workspace/` when the content is the same -across every sandbox and doesn't need any runtime values substituted -in. Typical use cases: linter rules, editor settings, a shared -`.editorconfig`, team dotfiles. +Run it from the parent directory, using your current directory as the workspace: -```text -ruff-lint/ -├── spec.yaml -└── files/ - └── workspace/ - └── ruff.toml +```console +$ sbx run --name kit-shell ./shell-v3 . ``` -```yaml {title="ruff-lint/spec.yaml"} -schemaVersion: "2" +Each example uses a different sandbox name because selecting a different kit +set requires creating a sandbox. + +## Contribute agent instructions + +Use the agent-context capability to tell an agent how to use tools and +configuration supplied by a kit. A mixin contributes instructions to the +workload's context profile without choosing its filename: + +```yaml {title="team-review/team-review.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" kind: mixin -name: ruff-lint -displayName: Ruff -description: Python linting with shared team config - -setup: - install: - - command: "uv tool install ruff@latest" - user: "1000" +displayName: Team review instructions + +capabilities: + - type: com.docker.runtime/agent-context@1 + config: + content: | + When reviewing a Dockerfile, check the base image version, layer + ordering, cache use, and whether secrets appear in ARG or ENV. + Explain the effect of each suggested change and run available + project checks before reporting completion. ``` -```toml {title="ruff-lint/files/workspace/ruff.toml"} -line-length = 80 +The [sample shell workload](#create-a-workload-for-the-examples) chooses +`AGENTS.md`. When you compose this mixin, `sbx` adds a kit entry to that +profile and puts the instructions in a separate +file for the agent to read on demand. An agent workload can choose another +profile, such as `CLAUDE.md`, and the same mixin contributes to that profile. -[lint] -select = ["E", "F", "I"] +```console +$ sbx run --name kit-team-review ./shell-v3 --kit ./team-review . ``` -## Install a tool at sandbox creation - -`setup.install` runs once per sandbox, at creation time. It's where -anything that needs to land in the image goes — package managers -(`apt-get`, `pip`, `npm`), binary downloads, or vendor install scripts. - -> [!TIP] -> Each new sandbox runs all `setup.install` commands. The results aren't -> cached between sandboxes. Creating a kit avoids building and distributing an -> image, so kits work well for smaller, composable changes. For substantial -> build or installation steps, consider a -> [custom template](templates.md#build-a-custom-template). Sandboxes reuse -> template images from the local cache. - -```yaml -setup: - install: - - command: "apt-get update && apt-get install -y jq" - - command: "curl -fsSL https://example.com/install.sh | sh" -``` +Keep this mixin's instructions inline: `contentFile` requires a kit with a +build recipe. Only workload kits can set `filename`. + +## Write runtime configuration -Install commands run as root by default. Set `user: "1000"` when the -step should run as the agent user — for example, `npm install -g` -against a user-scoped prefix, or anything that writes to -`/home/agent/`. - -Install steps run under `sh`, not bash, so bash-only builtins such as -`source` fail with `sh: source: not found`. Pipe explicitly to `bash` -(`curl … | bash`) or wrap the step in `bash -c '…'` when you need them. - -Downloads are subject to the sandbox's -[network access rules](../governance/access-controls/network.md). A domain that -resolves from your host can still be blocked inside the sandbox — for -example, `get.sdkman.io` returns a 403 until you allow it with -`sbx policy allow network get.sdkman.io`. A tool may also need base -packages that aren't in the image: [SDKMAN!](https://sdkman.io/), for -instance, needs `zip` and `unzip`, so add an -`apt-get install -y zip unzip` step (as root) before installing it. - -> [!WARNING] -> `curl … | bash` masks download failures. The pipe's exit status is -> bash's, and bash exits `0` on empty input, so a blocked or failed -> download still reports success — the sandbox is created with no error -> even though nothing was installed. Download first, then run, so a -> failed fetch fails the step: -> -> ```yaml -> setup: -> install: -> - command: "curl -fsSL https://example.com/install.sh -o /tmp/install.sh && bash /tmp/install.sh" -> user: "1000" -> ``` - -## Customize the shell environment - -Some tools install into a versioned directory and expect you to source -an init script from your shell profile so their commands land on `PATH`. -Version managers like [nvm](https://github.com/nvm-sh/nvm) and -[SDKMAN!](https://sdkman.io/) follow this pattern. To make the tool -available in every shell, append the source line to -`/etc/sandbox-persistent.sh` in an install command. - -`/etc/sandbox-persistent.sh` is the sandbox's persistent environment -file. It's sourced before every bash invocation — interactive shells and -non-interactive ones, including agents started with `sbx run` and -commands run with `sbx exec`. Appending here makes the tool available to -the agent regardless of how its shell is launched. Use -[`environment.variables`](kit-reference.md#environment) for ordinary variables -declared by a kit. To pass variables when creating a sandbox, use -[`-e` or `--env-file`](../usage.md#set-environment-variables). - -```yaml {title="nvm/spec.yaml"} -schemaVersion: "2" +Use lifecycle `files` when a file contains values chosen at sandbox creation. +This example writes a team settings file using a validated kit argument. +It needs no Dockerfile because it contributes only declarations: + +```yaml {title="workspace-config/workspace-config.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" kind: mixin -name: nvm -displayName: nvm -description: Node version manager available in every shell - -setup: - install: - - command: "curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash" - user: "1000" - description: Install nvm - - command: | - cat >> /etc/sandbox-persistent.sh <<'EOF' - export NVM_DIR="$HOME/.nvm" - unset NPM_CONFIG_PREFIX - [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" - EOF - user: "1000" - description: Source nvm for every shell +displayName: Workspace settings + +args: + project: + default: demo + pattern: '^[a-z][a-z0-9-]*$' + +capabilities: + - type: com.docker.runtime/lifecycle@1 + config: + files: + - path: /home/agent/.config/team/project.json + content: | + {"project": "${{ kit.args.project }}"} + mode: "0644" ``` -Both install steps run as `user: "1000"`. This installs the tool under -`/home/agent/` and can update `/etc/sandbox-persistent.sh`, which the agent user -owns. The `$HOME` in the appended lines resolves per user at source time, so the -agent user finds its own install. Append to the file rather than overwriting it -— the sandbox relies on its existing contents. - -The base image ships its own Node and sets `NPM_CONFIG_PREFIX`, which -nvm won't activate alongside. `unset NPM_CONFIG_PREFIX` before sourcing -`nvm.sh` clears that conflict. Sourcing makes the `nvm` command -available; it doesn't put a Node version on `PATH`. Run -`nvm install --lts` to add one — wrap it in `bash -c '…'` if you script -it as an install step, since install steps run under `sh`. +Create the `workspace-config` directory and save the descriptor. Run it with +the [sample shell workload](#create-a-workload-for-the-examples), supplying +the project name as a kit argument: -Append only the init script, not the tool's tab-completion script. -Because `/etc/sandbox-persistent.sh` is sourced before every command, -completion scripts — which rely on variables that exist only during -completion — can break non-interactive shells that agents rely on. +```console +$ sbx run --name kit-project ./shell-v3 --kit ./workspace-config \ + --kit-arg workspace-config.project=payments . +``` -## Install an internal CA certificate +The runtime expands `${{ kit.args.project }}` and writes the file before the +workload starts. The argument prefix is the local kit directory's name. +The file is written during sandbox creation. Add `overwrite: false` if it +should only seed a default and preserve an existing file. -If your organization uses a proxy that inspects HTTPS traffic, install -the proxy's internal root CA in the sandbox trust store. This helps -agents and SDKs trust certificates signed by the proxy. +Use absolute paths under `/home/agent/` for files the agent owns. A variable +such as `$HOME` in `path` isn't expanded. To write to a runtime workspace +path, use a hook with `env: [WORKSPACE_DIR]`. See +[Copy shared configuration](#copy-shared-configuration). -```text -internal-ca/ -├── spec.yaml -└── files/ - └── home/ - └── internal-ca.crt -``` +## Run a hook on every start -Use a PEM-encoded certificate with a `.crt` extension. Files under -`files/home/` land in `/home/agent/` in the sandbox, so -`files/home/internal-ca.crt` becomes `/home/agent/internal-ca.crt` — -which is the path the install command reads from. If traffic can be -signed by more than one internal proxy, include each proxy's root CA in -the kit and install each certificate before running -`update-ca-certificates`. +Use lifecycle `startup` for work that must repeat after the sandbox stops +and starts. This example records the time and workspace at each start: -```yaml {title="internal-ca/spec.yaml"} -schemaVersion: "2" +```yaml {title="start-log/start-log.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" kind: mixin -name: internal-ca +displayName: Sandbox start log + +capabilities: + - type: com.docker.runtime/lifecycle@1 + config: + startup: + - command: | + printf '%s %s\n' "$(date -u +%FT%TZ)" "$WORKSPACE_DIR" >> /home/agent/sandbox-starts.log + user: agent + env: [WORKSPACE_DIR] + description: Record each sandbox start +``` + +Run the mixin with the [sample shell workload](#create-a-workload-for-the-examples): -setup: - install: - - command: "install -m 0644 /home/agent/internal-ca.crt /usr/local/share/ca-certificates/internal-ca.crt && update-ca-certificates" - user: "0" - description: Install internal CA certificate +```console +$ sbx run --name kit-start-log ./shell-v3 --kit ./start-log . ``` -`update-ca-certificates` adds the certificate to the system trust -store, so tools and SDKs that read the system bundle trust the proxy's -certificates without further configuration. +Startup hooks run separately from the workload launch. Use install hooks +or lifecycle files for configuration the agent must read when it starts. +Design repeated setup so running it again leaves the environment usable. + +For a long-running service, set `background: true` on its startup hook and +redirect output to a log file. The service binary must already be present in +the workload or a composed tool overlay. -## Run a background service +## Copy shared configuration -`setup.startup` runs on every sandbox start. To keep a long-running -service such as a dev server or daemon alive, set `background: true`. The -sandbox runs the command in the background and replays startup commands on -each start, so the service comes back after a stop/start cycle: +Ship static files in the image, then use a lifecycle install hook to copy +them to a destination that only exists when the sandbox is created. This is +useful for workspace defaults, because the workspace is mounted at runtime +and its path varies between projects. -```yaml -setup: - startup: - - command: ["my-service", "--port", "8080"] - user: "1000" - background: true +Create this directory: + +```text +team-config/ +├── team-config.yaml +├── team-config.dockerfile +└── editorconfig ``` -A background service doesn't write to your terminal. To capture its output -for debugging, wrap the command in a shell and redirect to a log file. Let -`background: true` run the command in the background rather than adding a -trailing `&` yourself: - -```yaml -setup: - startup: - - command: - - sh - - -c - - my-service --port 8080 > /tmp/my-service.log 2>&1 - user: "1000" - background: true +```ini {title="team-config/editorconfig"} +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true ``` -An empty log file tells you the wrapper ran; a populated one tells you why -the service failed. +The Dockerfile places the file outside the workspace, where a runtime mount +won't hide it: -## Write runtime values to a file +```dockerfile {title="team-config/team-config.dockerfile"} +FROM scratch +COPY editorconfig /usr/share/team-config/editorconfig +``` -When a config file needs a value that isn't known until sandbox start -— most often the absolute workspace path — use `setup.files`. -The `${WORKDIR}` placeholder expands to the primary workspace path -when the file is written. +The descriptor copies the default into the workspace only when the project +has no `.editorconfig`. It also keeps a copy in the agent's home directory: -```yaml -setup: - files: - - path: /home/agent/.local/bin/start-code-server.sh - content: | - exec code-server --bind-addr 0.0.0.0:8080 --auth none "${WORKDIR}" - mode: "0755" - startup: - - command: - - sh - - -c - - nohup /home/agent/.local/bin/start-code-server.sh > /tmp/code-server.log 2>&1 & - user: "1000" +```yaml {title="team-config/team-config.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +displayName: Team configuration + +capabilities: + - type: com.docker.runtime/lifecycle@1 + config: + install: + - command: | + set -eu + if [ ! -e "$WORKSPACE_DIR/.editorconfig" ]; then + cp /usr/share/team-config/editorconfig "$WORKSPACE_DIR/.editorconfig" + fi + mkdir -p /home/agent/.config/team + cp /usr/share/team-config/editorconfig /home/agent/.config/team/editorconfig + user: agent + env: [WORKSPACE_DIR] + description: Copy team defaults into the workspace and home ``` -`mode: "0755"` makes the generated file executable so the startup -command can invoke it directly. +Run it with the [sample shell workload](#create-a-workload-for-the-examples): + +```console +$ sbx run --name kit-team-config ./shell-v3 --kit ./team-config . +``` -Use `setup.files` instead of a static file whenever the content depends -on a runtime value. Use a static file otherwise. +`WORKSPACE_DIR` is a runtime value. Declaring it in the hook's `env` list +makes it available to the command. Running as `agent` keeps the copied files +writable by the workload user. With a directly mounted workspace, creating +`.editorconfig` also creates that file in the host project. -> [!TIP] -> This snippet is lifted from the -> [code-server kit](https://github.com/docker/sbx-kits-contrib/tree/main/code-server) -> in the contrib repository, which is also a runnable sample that demonstrates -> the full pattern. +Schema v3 has no automatic `files/workspace/` or `files/home/` placement. +The Dockerfile defines where static content lives in the image. The hook +handles the runtime destination. ## Ship a Claude Code skill -Claude Code reads project-scoped skills from -`.claude/skills//SKILL.md` in the workspace. Drop one into -`files/workspace/` and it's available in the sandbox. +Package a task your team repeats as an agent skill. This mixin adds a +Dockerfile review skill to Claude Code's project skills directory, +`.claude/skills/docker-review/`. -```text -docker-review/ -├── spec.yaml -└── files/ - └── workspace/ - └── .claude/ - └── skills/ - └── docker-review/ - └── SKILL.md -``` +Create a `docker-review` directory with a descriptor, a Dockerfile, and the +skill's Markdown file: -```yaml {title="docker-review/spec.yaml"} -schemaVersion: "2" -kind: mixin -name: docker-review -displayName: Dockerfile review skill -description: Ships a Claude Code skill that reviews Dockerfiles -``` - -```markdown {title="docker-review/files/workspace/.claude/skills/docker-review/SKILL.md"} +```markdown {title="docker-review/SKILL.md"} --- name: docker-review -description: Review a Dockerfile for best practices. Use when the user asks to review, audit, or improve a Dockerfile. +description: Review a Dockerfile for best practices. Use when asked to review, audit, or improve a Dockerfile. --- When reviewing a Dockerfile, check: -1. Base image — pinned tag or digest, appropriate for the workload -2. Layer order — dependencies copied before application source -3. Image size — multi-stage builds, `.dockerignore`, package-manager cache flags -4. Security — non-root `USER`, no secrets in `ARG`/`ENV` -5. Reproducibility — pinned package versions, frontend directive where relevant +1. Base image: an appropriate image with a pinned tag or digest +2. Layer order: dependencies copied before application source +3. Image size: multi-stage builds, `.dockerignore`, and package-manager caches +4. Security: a non-root user and no secrets in `ARG` or `ENV` +5. Reproducibility: pinned package versions and build inputs ``` -Kits have to target the workspace rather than `~/.claude/` because -sandboxes don't pick up user-level agent configuration from the host. -See the -[FAQ](../faq.md#why-doesnt-the-sandbox-use-my-user-level-agent-configuration) -for details. - -## Customize agent settings - -Some agents combine settings from several files. When the agent supports it, -place kit settings in a separate file instead of replacing -[sandbox-managed agent configuration](kits.md#sandbox-managed-agent-configuration). - -Claude Code's `--settings` option loads an additional settings file. Extend the -built-in `claude` kit to add the option without reproducing its configuration, -and place the additional file outside the path the sandbox manages: +The Dockerfile packages the skill outside the mounted workspace: -```text -claude-sonnet/ -├── spec.yaml -└── files/ - └── home/ - └── .config/ - └── claude/ - └── sonnet.json +```dockerfile {title="docker-review/docker-review.dockerfile"} +FROM scratch +COPY SKILL.md /usr/share/docker-review/SKILL.md ``` -```yaml {title="claude-sonnet/spec.yaml"} -schemaVersion: "2" -kind: sandbox -name: claude-sonnet -extends: claude - -sandbox: - command: - - --dangerously-skip-permissions - - --settings - - /home/agent/.config/claude/sonnet.json -``` +An install hook copies it into the project when the sandbox is created: + +```yaml {title="docker-review/docker-review.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +displayName: Dockerfile review skill -```json {title="claude-sonnet/files/home/.config/claude/sonnet.json"} -{ - "model": "sonnet" -} +capabilities: + - type: com.docker.runtime/lifecycle@1 + config: + install: + - command: | + set -eu + skill_dir="$WORKSPACE_DIR/.claude/skills/docker-review" + mkdir -p "$skill_dir" + if [ ! -e "$skill_dir/SKILL.md" ]; then + cp /usr/share/docker-review/SKILL.md "$skill_dir/SKILL.md" + fi + user: agent + env: [WORKSPACE_DIR] ``` -Claude Code merges the additional file with the sandbox-managed user settings. -Because the file is under `files/home/`, it stays inside the sandbox instead of -being written into a directly mounted host workspace. Launch the sandbox by -passing the child kit directory in place of a built-in agent name: +Use it with the Claude Code workload from [Build an agent](build-an-agent.md): ```console -$ sbx run ./claude-sonnet +$ sbx run ./claude-team --name claude-review --kit ./docker-review ``` -When you launch the kit for the first time, `sbx` prompts you to approve its -inherited Anthropic credentials. Because this is a third-party schema v2 kit, -`sbx` records your approval as a -[credential binding](../configuration/credentials.md#credential-bindings). +Ask Claude Code to review the project's Dockerfile. The hook preserves an +existing skill at the same path. With a directly mounted workspace, it also +creates the skill files in your host project. Other agents use their own +skill discovery paths; adapt the hook's destination for the agent you use. -OpenCode supports an additional config file through `OPENCODE_CONFIG`. Keep the -kit's config separate from the sandbox-managed -`/home/agent/.config/opencode/opencode.json`, for example at -`/home/agent/.config/opencode/team.json`: +## Install an internal CA certificate -```text -opencode-team/ -├── spec.yaml -└── files/ - └── home/ - └── .config/ - └── opencode/ - └── team.json +If your organization uses a proxy that inspects HTTPS traffic, add its root +CA certificate to the sandbox's trust store. Create an `internal-ca` +directory and save the PEM-encoded certificate as `internal-ca.crt` beside +these two files: + +```dockerfile {title="internal-ca/internal-ca.dockerfile"} +FROM scratch +COPY internal-ca.crt /usr/local/share/ca-certificates/team-internal-ca.crt ``` -```yaml {title="opencode-team/spec.yaml"} -schemaVersion: "2" +```yaml {title="internal-ca/internal-ca.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" kind: mixin -name: opencode-team -requires: - agent: opencode - -environment: - variables: - OPENCODE_CONFIG: /home/agent/.config/opencode/team.json +displayName: Internal CA certificate + +capabilities: + - type: com.docker.runtime/lifecycle@1 + config: + install: + - command: update-ca-certificates + user: "0" + description: Add the internal CA to the sandbox trust store ``` -```json {title="opencode-team/files/home/.config/opencode/team.json"} -{ - "$schema": "https://opencode.ai/config.json", - "autoupdate": false -} +The build includes the certificate at a fixed path with a `.crt` extension. +The install hook updates the workload's system trust store after the overlay +is applied. Tools that use that trust store can then verify certificates +signed by the internal CA. + +Run the mixin with the [sample shell workload](#create-a-workload-for-the-examples): + +```console +$ sbx run --name kit-ca ./shell-v3 --kit ./internal-ca . ``` -OpenCode merges the custom file with its global and project config files. See -the OpenCode [config precedence](https://opencode.ai/docs/config/#precedence-order) -for the complete order. +Use a distinct certificate filename for each CA. If the proxy uses several +root certificates, copy each one into `/usr/local/share/ca-certificates/` +before running `update-ca-certificates`. -Agent settings mechanisms differ. If an agent doesn't support an additional -config file, launch option, or environment variable for the setting, kits can't -replace the sandbox-managed user settings before the agent launches. -`setup.startup` doesn't gate the agent entrypoint, so don't use it for settings -the agent must read during initialization. +## Build a tool overlay -## Fork an existing agent +Build tools into a mixin so each sandbox can use the same image layers. +This example compiles [`gojq`](https://github.com/itchyny/gojq), a JSON query +tool with `jq` syntax, and ships its binary without the Go compiler. -Sandbox kits (`kind: sandbox`) define a full agent from scratch. The most -common variant is a fork of a built-in agent. Use `extends:` to inherit the -parent's complete configuration and declare only the fields you want to change. -This example replaces the built-in `claude` entrypoint so Claude Code uses -manual permission mode instead of bypassing approval prompts: +Create a `gojq` directory with these two files: -```yaml {title="claude-safe/spec.yaml"} -schemaVersion: "2" -kind: sandbox -name: claude-safe -displayName: Claude Code (with approval prompts) -description: Claude Code in manual permission mode +```yaml {title="gojq/gojq.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +displayName: JSON query tool -extends: claude +args: + version: + default: "0.12.17" + pattern: '^[0-9]+\.[0-9]+\.[0-9]+$' + buildArg: GOJQ_VERSION -sandbox: - entrypoint: [claude, "--permission-mode", "manual"] +provides: ["gojq@${{ kit.args.version }}"] ``` -The child inherits the built-in image, credentials, network permissions, -persistent volumes, settings, MCP integration, agent instructions, setup -entries, and environment variables. Its `sandbox.entrypoint` replaces the -inherited entrypoint. +```dockerfile {title="gojq/gojq.dockerfile"} +FROM golang:1.25 AS build +ARG GOJQ_VERSION +RUN CGO_ENABLED=0 go install github.com/itchyny/gojq/cmd/gojq@v${GOJQ_VERSION} -Launch by passing the sandbox kit in place of a built-in agent name: - -```console -$ sbx run ./claude-safe +FROM scratch +COPY --from=build /go/bin/gojq /usr/local/bin/gojq ``` -For a step-by-step walkthrough of building a new sandbox kit from -scratch, see [Build an agent](build-an-agent.md). +The final `FROM scratch` stage contains the tool overlay. The binary is +compiled without C dependencies, so it doesn't require shared libraries +from the workload. For other tools, include their runtime libraries or +declare a dependency on a compatible environment. -## More examples +There is no lifecycle install hook: the compiler and module downloads run +when the kit is built. The resulting tool is available as soon as the +composed filesystem is ready. -These patterns are all drawn from working kits in the -[sbx-kits-contrib](https://github.com/docker/sbx-kits-contrib) -repository, which contains each example as a complete, loadable kit. -Use it to study the full shape of a kit. Load a mixin with `--kit`: +Run the mixin with the [sample shell workload](#create-a-workload-for-the-examples): ```console -$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=" +$ sbx run --name kit-gojq ./shell-v3 --kit ./gojq . ``` -For a `kind: sandbox` kit, pass the reference in place of the agent name: +From another host terminal, use `sbx exec` to check the tool's output: ```console -$ sbx run "git+https://github.com/docker/sbx-kits-contrib.git#dir=" +$ sbx exec kit-gojq -- gojq --null-input '{"kit": "gojq", "ready": true} | .ready' +true ``` + +Use `sbx exec` for command output in scripts and automated checks. Use +`sbx run` for the interactive workload session. + +Another mixin can declare `requires: ["gojq >= 0.12.17"]`. You must include +both mixins in the launch command: a requirement checks the supplied set +and doesn't download a provider. See [Compose kits](kits.md#compose-kits). + +To build an agent workload step by step, see [Build an agent](build-an-agent.md). diff --git a/content/manuals/ai/sandboxes/customize/kit-reference.md b/content/manuals/ai/sandboxes/customize/kit-reference.md index c3df614e334..a567158d461 100644 --- a/content/manuals/ai/sandboxes/customize/kit-reference.md +++ b/content/manuals/ai/sandboxes/customize/kit-reference.md @@ -1,8 +1,8 @@ --- title: Kit spec reference linkTitle: Spec reference -description: Field-by-field reference for a kit's spec.yaml, including arguments, credentials, network rules, environment, setup, files, agent instructions, and the sandbox block. -keywords: sandboxes, sbx, kits, spec.yaml, reference, schema, fields +description: Reference for the v3 kit descriptor, including capabilities, composition, arguments, build recipes, lifecycle hooks, and the published image format. +keywords: sandboxes, sbx, kits, v3, schema, capabilities, workload, mixin weight: 50 --- @@ -10,573 +10,651 @@ weight: 50 > [!NOTE] > Kits are experimental. The kit file format, CLI commands, and experience -> for creating, loading, and managing kits are subject to change as the -> feature evolves. Share feedback and bug reports in the -> [docker/sbx-releases](https://github.com/docker/sbx-releases) repository. +> for creating, loading, and managing kits are subject to change. Share +> feedback in [docker/sbx-releases](https://github.com/docker/sbx-releases). -This page documents every field in a kit's `spec.yaml`. For an overview of -what kits are and how to use them, see [Kits](kits.md). +This page describes the v3 kit descriptor and its capability configs. Use it +when authoring a workload or mixin. For concepts and usage, see [Kits v3](kits.md). +For complete examples, see [Kit examples](kit-examples.md). -For the normative v2 grammar used by the parser and tests, see the -[`schemaVersion: "2"` specification](https://github.com/docker/sbx-kits-contrib/blob/main/spec/SPEC-v2.md) -in the `docker/sbx-kits-contrib` repository. - -A kit directory has a required `spec.yaml` and an optional `files/` tree: - -```text -my-kit/ -├── spec.yaml # required -└── files/ # optional — static files to inject - ├── home/ - └── workspace/ -``` +Some capability types describe functionality beyond the `sbx` integration. +The [capability table](#runtime-capabilities) identifies these types. ## Schema versions -Starting with Docker Sandboxes version 0.36, two schema versions are supported. -Use `schemaVersion: "2"` for new kits. Version `"1"` remains accepted through -the legacy path. - -The loader forks on `schemaVersion`. A v2 spec uses the v2 grammar only. Legacy -v1 fields in a `schemaVersion: "2"` spec are rejected during decode instead of -being folded into the v2 model. Keep each `spec.yaml` on one grammar. - -What changed in v2: - -| v1 | v2 | -| ------------------------------------------- | ---------------------------------------- | -| `credentials.sources.` | `credentials:` list entry with `service` | -| `network.allowedDomains` / `deniedDomains` | `permissions.network.allow` / `deny` | -| `network.serviceDomains` / `serviceAuth` | `credentials[].apiKey.inject` | -| `network.publishedPorts` / `publishedPorts` | top-level `ports` | -| standalone `oauth:` block | `credentials[].oauth` | -| `oauth.skipIfEnv` | Accepted but ignored | -| `environment.proxyManaged` | `credentials[].apiKey.proxyManaged` | -| `memory` / `agentContext` | `agentInstructions.content` | -| `kind: agent` / `agent:` block | `kind: sandbox` / `sandbox:` block | -| `sandbox.aiFilename` | `agentInstructions.filename` | -| `sandbox.entrypoint.run` | `sandbox.entrypoint` | -| `sandbox.entrypoint.args` | `sandbox.command.default` | -| `sandbox.entrypoint.ttyArgs` | `sandbox.command.interactive` | -| `tmpfs:` | `volumes:` entries with `type: tmpfs` | -| `volumes:` (mapping form) | `volumes:` sequence (`- path: `) | -| `commands:` / `commands.initFiles` | `setup:` / `setup.files` | -| `settings:` / `kitDir` / `persistence` | Removed | - -Credential discovery also moved out of the kit in v2: a kit declares which -credentials it needs and how to inject them, but where each value comes from is -controlled by the user through -[credential bindings](../configuration/credentials.md#credential-bindings). +Use `schemaVersion: "3"` for the syntax on this page. V3 workloads and mixins +must be used together: they can't compose with v1 or v2 kits. The built-in +agents use v2. V2 is deprecated but remains supported. For +`schemaVersion: "2"`, see the +[v2 spec reference](kits-v2/_index.md), including the +[v1-to-v2 field mapping](kits-v2/_index.md#schema-versions). +Each descriptor uses one grammar. V2 fields aren't accepted in a v3 +descriptor. -> [!NOTE] -> `mixins` and `sandbox.build` are accepted by the parser, but runtime support -> is pending. A kit that sets `sandbox.build` must also set `sandbox.image`. +## Descriptor fields -## Top-level fields +A descriptor is a YAML document with `schemaVersion: "3"` and a `kind`. +The `# syntax` line selects the kit BuildKit frontend when you build it. ```yaml -schemaVersion: "2" -kind: -name: -version: -displayName: -description: -sourceURL: -licenses: - - MIT -locked: - - sandbox.image -security: - privileged: false -args: - channel: - default: stable - enum: [stable, beta] +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +displayName: Team guidelines +description: Development conventions for the team +version: "1.0.0" +provides: [team-guidelines] +capabilities: + - type: com.docker.runtime/agent-context@1 + config: + content: | + Run the project's tests before committing changes. ``` -| Field | Required | Description | -| --------------- | -------- | ----------------------------------------------------------------------------------------------- | -| `schemaVersion` | Yes | Spec schema version. Use `"2"` for this grammar. | -| `kind` | Yes | `mixin` for kits that extend an agent; `sandbox` for kits that define one. | -| `name` | Yes | Unique identifier. Lowercase alphanumeric with hyphens, 1 to 64 characters. | -| `version` | No | Kit version. | -| `displayName` | No | Human-readable name. | -| `description` | No | Short description. | -| `sourceURL` | No | Source repository or documentation URL. | -| `licenses` | No | SPDX license identifiers. | -| `locked` | No | Dotted paths child kits may not override. | -| `security` | No | Container security settings. `security.privileged: true` runs the container in privileged mode. | -| `args` | No | Arguments supplied when the kit is loaded. Schema v2 only. | - -A kit also declares behavior blocks such as `agentInstructions`, -`permissions`, `ports`, `credentials`, `environment`, `setup`, and `volumes`. - -## Arguments - -A schema v2 kit can declare arguments and reference them anywhere in -`spec.yaml` or under `files/` as `${{ kit.args. }}`. Substitution happens -before the spec is decoded. +Only `schemaVersion` and `kind` are required at the top level. A workload also +needs a [build recipe](#authoring-forms) to supply its filesystem and launch +command. Unknown fields are errors, including unknown keys in the configs of +recognized capability types. + +| Field | Type | Description | +| --- | --- | --- | +| `schemaVersion` | String | Required. Exactly `"3"`. | +| `kind` | String | Required. `workload` or `mixin`. | +| `displayName` | String | Human-readable label. | +| `description` | String | Short summary. | +| `author` | String | Publisher display text, such as `Name `. Metadata, not verified publisher identity. | +| `sourceUrl` | String | Source repository or documentation URL. | +| `iconUrl` | String | Absolute HTTPS URL to an image for catalogs and pickers. | +| `version` | String | Fallback version for `provides` entries with no version. See [Versions](#versions). | +| `licenses` | List of strings | SPDX license identifiers. | +| `provides` | List of strings | Features this kit supplies. | +| `requires` | List of strings | Features other selected kits must supply. | +| `integrates` | List of strings | Features this kit integrates with when supplied by other selected kits. | +| `conflicts` | List of strings | Features other selected kits must not supply. | +| `capabilities` | List of objects | Typed requests for runtime resources and behavior. | +| `args` | Map | Named build-time or create-time arguments. | +| `build` | String | Inline Dockerfile. Mutually exclusive with `dockerfile`. | +| `dockerfile` | String | Path to a companion Dockerfile, relative to the descriptor's directory. Mutually exclusive with `build`. | + +Field names are case-sensitive. For example, use `sourceUrl`, not the v2 +spelling `sourceURL`. A v3 descriptor has no `name`, `extends`, `sandbox`, +`environment`, or `setup` field. Its consumed reference identifies the kit, +the image config defines how it runs, and capabilities declare runtime +behavior. + +## Composition fields + +Exactly one `workload` supplies the sandbox's root filesystem and launch +command. Zero or more `mixin` kits contribute filesystem overlays and +declarations. A mixin can contain declarations alone. + +The following fields describe relationships between kits. They are distinct +from `capabilities`, which requests something from the runtime. ```yaml -args: - version: - default: latest - description: Tool version to install - pattern: '^(latest|[0-9]+\.[0-9]+\.[0-9]+)$' - channel: - default: stable - enum: [stable, beta, nightly] - target: - required: true - description: Build target - -environment: - variables: - TOOL_VERSION: "${{ kit.args.version }}" +provides: ["com.example/tooling@1.4.0"] +requires: ["node >= 20.0.0"] +integrates: ["docker-engine >= 25.0.0"] +conflicts: [podman] ``` -Don't use kit arguments for API tokens, passwords, or other secrets. Use -[Credentials](../configuration/credentials.md) to provide sensitive values to -a sandbox. - -| Field | Description | -| ------------- | ------------------------------------------------------------------------------------------------------------ | -| Argument name | Starts with a letter or underscore and contains only letters, digits, underscores, and hyphens. | -| `default` | String to use when the caller supplies no value. Mutually exclusive with `required: true`. | -| `required` | Set to `true` when the caller must supply a value. Mutually exclusive with `default`. | -| `description` | Optional help text shown when a required value is missing. | -| `enum` | Optional list of accepted values. Mutually exclusive with `pattern`. | -| `pattern` | Optional Go RE2 regular expression matched against the complete value. Mutually exclusive with `enum`. | - -Each argument must declare either `default`, including an empty-string -default, or `required: true`. A declared default must satisfy its own `enum` or -`pattern`. Every `${{ kit.args. }}` reference must have a matching -declaration. +| Field | Entry syntax | Resolution rule | +| --- | --- | --- | +| `provides` | `name` or `name@version` | Advertises a feature. No feature is provided implicitly. | +| `requires` | `name` or `name >= version` | Another selected kit must provide a feature that satisfies the requirement. | +| `integrates` | `name` or `name >= version` | An absent provider is accepted. A provider among the other selected kits must satisfy the minimum. | +| `conflicts` | `name` | Resolution fails if another selected kit provides this name. | -Argument values are strings, but substitution happens before YAML decoding. -Quote a placeholder in a string-valued field so a value such as `1.20` isn't -decoded as a number. +Requirements validate the kits you select. They don't search a registry or +download dependencies. Providers compose before their dependents, including +matched `integrates` entries. The dependency graph determines this order, +rather than the order of `--kit` flags. -Supply values with `--kit-arg` or `--kit-args-file` when loading the kit. See -[Pass arguments to kits](kits.md#pass-arguments-to-kits) for scoping, -precedence, and validation behavior. +Bare names such as `node` normalize to `com.docker.kit/node`. A qualified +name such as `com.example/node` is a separate feature. Names use lowercase +letters, digits, and hyphens, with an optional namespace before `/`. -## Kit kinds +### Versions -### `kind: mixin` +Versions contain dot-separated segments, starting with digits, with no `v` +prefix. Examples include `20`, `20.0.0`, and `1.2.3-rc1`. Segments compare +numerically when numeric, and lexically otherwise. Requirements support +minimum versions only, with no exact pins or ranges. -A mixin layers capabilities onto an existing sandbox. It must not declare a -`sandbox:` block, `extends:`, or `mixins:`. A mixin can declare `requires:` to -pin the base agent it is designed for: +An explicit version in `provides`, such as `node@20.0.0`, takes precedence. +For a provide with no version, a version-shaped consumption tag takes precedence +over the descriptor's `version` fallback. At publication, each `provides` +entry must have an explicit version or a descriptor `version` fallback. -```yaml -schemaVersion: "2" -kind: mixin -name: github-tools -requires: - agent: claude -``` +Build-time argument references are accepted in `provides` and `version` and +expanded before publication. Keep `requires`, `integrates`, and `conflicts` +literal. -`requires.agent` takes one base-agent name. It is validated as a kit name and -enforced during composition. +## Arguments -### `kind: sandbox` +Declare arguments under `args` and reference them as +`${{ kit.args. }}`. Arguments resolve in one of two phases: -A sandbox kit defines a full agent. A root sandbox must declare a `sandbox:` -block. A sandbox that uses `extends:` can inherit the parent image and omit its -own `sandbox:` block: +- An argument with `buildArg` resolves while building the kit. Its value is + passed to the Dockerfile and expanded into the published descriptor. +- An argument without `buildArg` resolves when creating the sandbox. Its + value is substituted into the descriptor for that installation. ```yaml -schemaVersion: "2" -kind: sandbox -name: claude-safe -extends: claude +args: + version: + default: "2.98.0" + pattern: '^[0-9]+\.[0-9]+\.[0-9]+$' + buildArg: GH_VERSION + timeout: + default: "30000" + pattern: '^[0-9]+$' + env: BROWSER_TIMEOUT + team: + required: true + enum: [alpha, beta] ``` -`extends:` is sandbox-only. The parent must resolve to a sandbox kit. `mixins:` -is also sandbox-only and accepted by the parser, but runtime composition support -is pending. - -## Sandbox block +| Field | Description | +| --- | --- | +| Argument name | Must match `[A-Za-z_][A-Za-z0-9_]*`. Hyphens aren't accepted. | +| `default` | String used when no value is supplied. `""` is a valid default. Mutually exclusive with `required: true`. | +| `required` | If `true`, the caller must supply a value. Defaults to `false`. | +| `description` | Help text for the argument. | +| `enum` | List of accepted string values. Mutually exclusive with `pattern`. | +| `pattern` | Go RE2 regular expression matched against the whole value. Mutually exclusive with `enum`. | +| `env` | Exports a resolved create-time value under this environment variable name. Mutually exclusive with `buildArg`. | +| `buildArg` | Dockerfile build argument name. Mutually exclusive with `env`. | + +`env` and `buildArg` names follow the same identifier rules as argument names. +An optional argument without a default can be omitted only if the descriptor +doesn't reference it. Every argument reference must have a declaration and a +resolved value. Use `default: ""` when an empty value is valid. Missing required +values, unknown supplied arguments, and values outside their constraints are +errors. + +Arguments aren't exported as environment variables unless you set `env`. +Use credentials for secrets. Build arguments and published descriptor values +are part of the kit's build and distribution process. + +Create-time substitution inserts values into the serialized descriptor +before decoding, without escaping them. Quoting a placeholder in YAML doesn't +safely preserve arbitrary strings: double quotes, backslashes, and control +characters such as newlines can cause decoding errors or change the value. +Constrain interpolated values with `enum` or `pattern`, as in these examples, +to exclude those characters. + +The resulting descriptor is validated again, including capability configs. +The published descriptor remains unchanged. Shell expressions such as `$HOME` +and `${HOME}` aren't kit argument references. + +See [Pass arguments to kits](kits.md#pass-arguments-to-kits) for CLI syntax. + +## Authoring forms + +The content recipe is a Dockerfile. It builds the files that ship with the +kit and, for a workload, sets `ENTRYPOINT`, `CMD`, `ENV`, `USER`, and +`WORKDIR`. Choose one authoring form: + +| Form | Descriptor and recipe | +| --- | --- | +| Companion files | `.yaml` and `.dockerfile` in the same directory. Use `dockerfile: ` to name the recipe explicitly. | +| Inline recipe | A YAML descriptor with Dockerfile text in `build: \|`. | +| Comment descriptor | A Dockerfile with a `# kit:` comment block containing the descriptor. The remaining file is its recipe. | + +An explicitly named Dockerfile must exist and stay within the descriptor's +directory. A missing conventional companion is accepted for a mixin with no +content recipe. A workload must have a recipe. + +For the inline form, `build` contains Dockerfile text, not a map of build +options: ```yaml -sandbox: - image: - build: - context: . - dockerfile: Dockerfile - args: - AGENT_VERSION: "1.0.0" - target: runtime - platforms: - - linux/amd64 - entrypoint: [my-agent, "--flag"] - command: - default: ["--task-mode"] - interactive: [] - resources: - cpu: 2 - memory: 4g - gpu: "1" +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +build: | + FROM scratch + COPY review-checklist.md /usr/local/share/team/review-checklist.md ``` -| Field | Required | Description | -| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------- | -| `sandbox.image` | When `extends:` is omitted | Docker image reference. | -| `sandbox.build` | No | Build configuration. Runtime support is pending, so a kit with `build:` must also set `image:`. | -| `sandbox.entrypoint` | No | Fixed process prefix as a string array. The first element is the agent binary. | -| `sandbox.command` | No | Mode-specific argument tail. Use a list shorthand for `default`, or a mapping with `default` and `interactive`. | -| `sandbox.resources` | No | Optional CPU, memory, and GPU constraints. Memory uses byte-size strings such as `4096m` or `4g`. | +The comment form starts with a descriptor comment block: -The effective command is `entrypoint` plus `command.default` for non-interactive -launches, and `entrypoint` plus `command.interactive` for TTY sessions. If -`interactive` is omitted, it falls back to `default`. - -For a kit that uses `extends:`, `sandbox.command` replaces the full inherited -argument tail, including flags after the binary in the parent's -`sandbox.entrypoint`. It doesn't append to that tail. Define every argument the -child needs. For example, a child of `claude` that adds `--settings` must also -include `--dangerously-skip-permissions` to preserve that behavior. - -The agent's container image must provide: - -- A non-root `agent` user at UID 1000 with passwordless sudo. -- A `/home/agent/` home directory owned by `agent`. -- HTTP proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`) preserved across sudo. -- The agent binary, either baked in or installed with [`setup.install`](#setup). +```dockerfile +# syntax=docker/runtime-kit:3 +# kit: +# schemaVersion: "3" +# kind: mixin +FROM scratch +COPY review-checklist.md /usr/local/share/team/review-checklist.md +``` -Build on top of `docker/sandbox-templates:shell-docker` to get these base -requirements. +A comment descriptor can't also declare `build` or `dockerfile`. +Dockerfile semantics apply to every recipe, including multi-stage builds and +build mounts. See [Directory and build layout](kits.md#directory-and-build-layout) +for organizing source files, and +[Packaging and distribution](kits.md#packaging-and-distribution) for build commands. -## Agent instructions +### Base image requirements -```yaml -agentInstructions: - filename: CLAUDE.md - content: | - Ruff is installed. Run `ruff check` before committing. -``` +A workload must provide `bash`, `sh`, `curl`, `git`, a populated CA +certificate store, and a non-root `agent` user with UID 1000 and home +directory `/home/agent`. Its image must define an `ENTRYPOINT` or `CMD`. +Install or ship any additional tools the workload needs. -| Field | Description | -| ---------- | --------------------------------------------------------------------------------------------------- | -| `filename` | AI profile filename. Meaningful for `kind: sandbox`; ignored with a warning for `kind: mixin`. | -| `content` | Markdown instructions. For a sandbox, inlined into the profile. For a mixin, written to kit memory. | +A Docker sandbox template is optional. For a complete example of preparing +a base image and installing an agent, see +[Use your own base image](build-an-agent.md#use-your-own-base-image). -For mixins, the engine writes `content` to -`/kits-memory/.md` and adds a `## Kits` pointer -section to the base AI file. This keeps each mixin's instructions in a separate -file. +The workload's image config owns the launch command and working directory. +Mixin recipes add files and additive image settings such as environment +variables. A mixin's `ENTRYPOINT`, `CMD`, `USER`, and `WORKDIR` don't replace +the workload's launch contract during composition. -## Credentials +## Runtime capabilities -A kit declares the credentials it needs and how the proxy injects them into -outbound requests. It does not declare a host discovery source. The user -provides the value through the secret store or the first-run prompt, and a -[credential binding](../configuration/credentials.md) authorizes its use. A kit -can't read arbitrary host environment variables or files. +Each `capabilities` entry names a runtime contract and its config-schema +version. The type version is independent of `schemaVersion`. ```yaml -credentials: - - service: - description: # optional - required: # optional, default false - provider: # optional, reserved - apiKey: - name: - proxyManaged: true - inject: - - domain: - header:
- format: - - domain: - scheme: bearer - - domain: - scheme: basic - username: # required with scheme: basic - oauth: - tokenEndpoint: - host: - path: - sentinels: - accessToken: - refreshToken: - credentialFile: - path: - structure: - : - accessToken: "{{.AccessToken}}" - refreshToken: "{{.RefreshToken}}" - expiresAt: "{{.ExpiresAt}}" - scopes: "{{.Scopes}}" +capabilities: + - type: com.docker.runtime/port@1 + description: Development server + optional: false + config: + container: 3000 ``` -`credentials` is a list; each entry names a `service` and configures one or more -auth mechanisms. - -| Field | Description | -| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `service` | Credential identifier, matched against the value stored with `sbx secret set`. Lowercase kebab-case. | -| `description` | Optional. Shown to the user when approving a [binding](../configuration/credentials.md#credential-bindings). | -| `required` | Marks the credential as essential to the agent. If it has no binding, `sbx` warns and starts with the credential withheld. Default `false`. | -| `provider` | Reserved for a provider registry. Accepted with a warning and no runtime effect. | -| `apiKey` | API-key injection (see [apiKey](#apikey)). | -| `oauth` | OAuth interception (see [oauth](#oauth)). | - -Each service must declare `apiKey`, `oauth`, or both. When both resolve at -runtime, the API key takes precedence and OAuth acts as the fallback. - -### `apiKey` - -| Field | Description | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | Environment variable name for the credential (for example, `ANTHROPIC_API_KEY`). | -| `proxyManaged` | If `true`, `sbx` sets `name` inside the container to the `proxy-managed` sentinel. Default `false`. | -| `inject[].domain` | Domain to inject the credential into. Must also be allowed in [`permissions.network`](#network). | -| `inject[].header` | HTTP header the proxy sets (for example, `x-api-key`, `Authorization`). | -| `inject[].format` | Header value format, with one `%s` placeholder (for example, `"%s"` or `"Bearer %s"`). Mutually exclusive with `scheme`. | -| `inject[].scheme` | Shorthand for common auth schemes. `bearer` expands to `Authorization: Bearer %s`; `basic` requires `username`. Mutually exclusive with `format`. | -| `inject[].username` | Username for HTTP Basic auth, for example `x-access-token` for Git over HTTPS. | - -### `oauth` - -For agents that authenticate with OAuth (for example, Claude Code), the proxy -intercepts token responses and replaces real tokens with sentinels, then swaps -the real token back in on outbound requests. By default, the token never enters -the sandbox. Setting `passthrough: true` opts out of sentinel masking and sends -the real token response into the sandbox. - -| Field | Description | -| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `tokenEndpoint.host` / `path` | The OAuth token endpoint the proxy intercepts. | -| `sentinels.accessToken` / `refreshToken` | Sentinel values written into the container in place of the real tokens. | -| `credentialFile.path` | Where to write the credential file inside the container (`~` expands). | -| `credentialFile.structure` | Declarative JSON shape. Supports `{{.AccessToken}}`, `{{.RefreshToken}}`, `{{.ExpiresAt}}`, and `{{.Scopes}}`. | -| `credentialFile.template` | Go template. Supports `{{.AccessToken}}`, `{{.RefreshToken}}`, `{{.ExpiresAt}}`, `{{.Scopes}}`, and `{{.ScopesJSON}}`. | -| `resourceHosts` | API hosts where the proxy attaches the token on outbound requests, distinct from the token endpoint host. | -| `skipIfEnv` | Accepted for compatibility, but ignored for schema v2. A v2 binding is authoritative instead of host environment variables. | -| `responseFields` | Overrides the default field names the proxy reads from the token response. | -| `passthrough` | If `true`, the proxy passes the token response through unchanged instead of replacing the tokens with sentinels. | - -`credentialFile.structure` provides a declarative alternative to -`credentialFile.template`. The engine renders it as well-formed JSON. If both -fields are set, `structure` takes precedence. - -## Network - -Network egress is declared under `permissions.network`. Credentials no longer carry -their own domain mapping — the proxy injects a credential only into the domains -its [`apiKey.inject`](#apikey) lists, and every domain the -sandbox reaches must be allowed here. +| Field | Description | +| --- | --- | +| `type` | Required. `/@`, such as `com.docker.runtime/port@1`. Names use lowercase letters, digits, and hyphens. Namespaces can also contain dots. The version is a positive integer. | +| `optional` | Defaults to `false`. The spec requires a runtime to refuse unavailable required capabilities and to skip unavailable optional ones. | +| `description` | Human-readable explanation of the request. | +| `config` | Type-specific fields. Omit for capability types with no config. | + +The following table lists the standard types. Every type has the prefix +`com.docker.runtime/`. + +| Type | Entries per descriptor | `sbx` support | +| --- | --- | --- | +| [`network-policy@1`](#network-policy) | At most one | Install and runtime network policy. | +| [`credential@1`](#credentials) | One per service and phase | Credential bindings and proxy injection. See field limitations. | +| [`lifecycle@1`](#lifecycle) | At most one | Install, startup, generated files, and interactive arguments. See field limitations. | +| [`agent-context@1`](#agent-context) | At most one | Agent profile and kit instructions. | +| [`port@1`](#ports) | One per container port and transport | Publishes ports to the host. | +| [`resources@1`](#resources) | At most one | Workload CPU and memory settings. | +| [`kit-registry@1`](#kit-registry) | At most one | Restricted to approved builder kits. | +| [`volume@1`](#volumes) | One per path | Schema accepted. Runtime integration pending. | +| [`usb-device@1`](#usb-devices) | Multiple distinct requests | Schema accepted. Runtime integration pending. | +| [`privileged@1`](#privileged-mode) | At most one | Schema accepted. Runtime integration pending. | +| [`agent-sessions@1`](#agent-sessions) | At most one | Schema accepted. Runtime integration pending. | + +Exact duplicate entries are errors. Unknown capability types can carry a +custom config, but acceptance by the parser doesn't establish runtime support. +`sbx` doesn't implement general rejection of unsupported required capability +types. Don't rely on an unsupported request being enforced or blocking +sandbox creation. + +The following sections describe each type's `config` fields. + +## Network policy + +`com.docker.runtime/network-policy@1` declares outbound access separately for +install hooks and the running workload. These phases concern sandbox +execution. They don't control Dockerfile build networking. ```yaml -permissions: - network: - allow: [, ...] - deny: [, ...] +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + install: + allow: [registry.npmjs.org] + runtime: + allow: [api.github.com, "*.example.com"] + deny: [telemetry.example.com] ``` -| Field | Description | -| --------------------------- | --------------------------------------------------------------------------------------------------------------- | -| `permissions.network.allow` | Domains the sandbox can reach. | -| `permissions.network.deny` | Domains the sandbox is blocked from reaching. Deny takes precedence over allow, including across composed kits. | +| Field | Description | +| --- | --- | +| `install.allow` | Domains permitted while install hooks run. | +| `install.deny` | Domains blocked while install hooks run. | +| `runtime.allow` | Domains permitted during workload execution. | +| `runtime.deny` | Domains blocked during workload execution. | -Allow and deny patterns: +`sbx` makes runtime rules available during installation too, then removes the +install-only rules before launching the workload. An omitted phase adds no +grants for that phase. Rules from composed kits combine, and deny takes +precedence over allow. -| Pattern | Example | Status | -| --------------------- | ------------------------ | --------------------------- | -| Exact host | `api.example.com` | Enforced | -| Exact host and port | `api.example.com:8080` | Enforced | -| Single-label wildcard | `*.example.com` | Enforced | -| Multi-label wildcard | `**.example.com` | Parsed; enforcement pending | -| Port range | `api.example.com:80-443` | Parsed; enforcement pending | -| Port wildcard | `api.example.com:*` | Parsed; enforcement pending | -| CIDR | `10.0.0.0/8` | Parsed; enforcement pending | +Use exact hosts, hosts with ports such as `api.example.com:443`, or +single-label wildcards such as `*.example.com`. A bare `*` or `**` permits +all destinations. Quote wildcard strings in YAML. -In v1 this was the `network:` block (`allowedDomains` / `deniedDomains`, plus -`serviceDomains` / `serviceAuth`). In v2, those fields are decode errors. +Every credential injection domain must also appear in the matching phase's +allow list. Validation ignores port suffixes for this membership check. A +bare `*` or `**` covers all injection domains. A narrower wildcard doesn't +replace an explicit injection-domain entry for validation. -## Ports +## Credentials -Use `ports` to expose sandbox services to the host: +`com.docker.runtime/credential@1` declares a service and how its credential +is presented. The user stores the value in the secret store and approves its +use through [credential bindings](../configuration/credentials.md#credential-bindings). +The descriptor doesn't name a host file or environment variable to read. ```yaml -ports: - - container: 8080 - name: web +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: [api.github.com] + - type: com.docker.runtime/credential@1 + optional: true + description: GitHub API access + config: + service: github + phase: runtime + apiKey: + name: GH_TOKEN + proxyManaged: true + inject: + - domain: api.github.com + header: Authorization + format: "Bearer %s" ``` -| Field | Description | -| ----------- | ------------------------------------------------------------------- | -| `container` | Container port, 1 to 65535. | -| `protocol` | `tcp` or `udp`. Empty publishes one family; see below. | -| `name` | Optional label surfaced by tools that list published port bindings. | +| Field | Description | +| --- | --- | +| `service` | Required. Lowercase service identifier in the host credential store. | +| `phase` | Required. `install` or `runtime`. | +| `apiKey` | API key presentation. Declare `apiKey`, `oauth`, or both. | +| `oauth` | OAuth token interception and credential-file config. | + +Set `optional` on the capability entry, outside `config`. In `sbx`, a missing +binding withholds the credential instead of blocking sandbox creation, even +for a required request. Install-only credential injection is removed after +install hooks. When the same service is declared for both phases, `sbx` uses +the runtime entry's presentation config. + +### API keys + +| Field | Description | +| --- | --- | +| `apiKey.name` | Required. Environment variable name in the sandbox, such as `GH_TOKEN`. | +| `apiKey.proxyManaged` | If `true`, keeps the real credential on the host and puts a sentinel value in the sandbox. Defaults to `false`. | +| `apiKey.inject[].domain` | Required for each rule. Domain where the proxy injects the credential. Must be allowed in the same phase's network policy. | +| `apiKey.inject[].header` | HTTP header to set, such as `Authorization`. | +| `apiKey.inject[].format` | Header value format, such as `"Bearer %s"`. | +| `apiKey.inject[].scheme` | Schema field for an auth scheme, such as `basic`. Runtime mapping in `sbx` is pending. Use explicit `header` and `format` for header injection. | +| `apiKey.inject[].username` | Username for an authentication scheme that requires one. | + +### OAuth + +| Field | Description | +| --- | --- | +| `oauth.tokenEndpoint.host` | Required when `tokenEndpoint` is set. Host whose OAuth token response the proxy intercepts. | +| `oauth.tokenEndpoint.path` | Token endpoint path. | +| `oauth.resourceHosts` | API hosts where the proxy uses the token. | +| `oauth.sentinels.accessToken` | Placeholder access token presented inside the sandbox. | +| `oauth.sentinels.refreshToken` | Placeholder refresh token presented inside the sandbox. | +| `oauth.credentialFile.path` | Credential-file destination. `~` expands to the agent's home. | +| `oauth.credentialFile.structure` | Nested map rendered as JSON after placeholder substitution. | +| `oauth.responseFields.accessToken` | Provider field to read instead of `access_token`. | +| `oauth.responseFields.expiresIn` | Provider field to read instead of `expires_in`. | +| `oauth.passthrough` | If `true`, returns real tokens to the sandbox instead of sentinels. Defaults to `false`. | + +Credential-file leaf values support `{{.AccessToken}}`, +`{{.RefreshToken}}`, `{{.ExpiresAt}}`, `{{.Scopes}}`, and +`{{.PrimaryApiKey}}`. The last placeholder's containing key is omitted when +no primary API key is captured. Unknown placeholders are errors. + +The v2 fields `provider`, `skipIfEnv`, and `credentialFile.template` aren't +part of the v3 schema. + +## Lifecycle + +`com.docker.runtime/lifecycle@1` declares work performed inside the sandbox +after the kit image has been built. Use a Dockerfile to install software that +can ship in the image. Use lifecycle hooks for initialization or work that +needs the sandbox's runtime state. -Host ports are allocated ephemerally. Leave `protocol` empty unless the service -listens on IPv6: an empty value publishes IPv4 only (`127.0.0.1`), which is what -a service bound to `0.0.0.0` needs, while `tcp` publishes both `127.0.0.1` and -`::1` — and a client arriving over `::1` is accepted and then reset if nothing -in the sandbox is listening there. Users can pin host ports with -`sbx ports --publish :`. +```yaml +capabilities: + - type: com.docker.runtime/lifecycle@1 + config: + install: + - command: [sh, -c, 'printf "%s\n" "$WORKSPACE_DIR" > /home/agent/workspace-path'] + user: "1000" + env: [WORKSPACE_DIR] + startup: + - command: [my-service, --foreground] + background: true + files: + - path: /home/agent/.config/tool/settings.json + content: '{"telemetry": false}' + mode: "0644" + overwrite: false + interactive: [--interactive] +``` -## Environment +### Install and startup hooks + +| Field | Description | +| --- | --- | +| `install[].command` | Required. Shell string passed to `sh -c`, or a list of command arguments. Runs synchronously when the sandbox is created. | +| `install[].user` | Execution user. Defaults to root. Use a username or quoted UID. | +| `install[].env` | Additional environment variable names the hook can read. | +| `install[].description` | Human-readable explanation. | +| `startup[].command` | Required. Shell string or list of command arguments. Runs on each sandbox start. | +| `startup[].user` | Execution user. Defaults to the agent user. Use `"0"`, `"1000"`, or a login name. | +| `startup[].background` | If `true`, lets the startup dispatcher continue without waiting for this command. Defaults to `false`. | +| `startup[].env` | Schema field for the hook's environment inputs. Filtering in `sbx` is pending. | +| `startup[].description` | Human-readable explanation. | + +Install hooks retain basic process variables, proxy settings, and certificate +paths. Declare other inputs in `env`, including `WORKSPACE_DIR` and any +credential sentinel variables the hook reads. Hooks from dependency providers +run before hooks from their dependents. + +Startup hooks must tolerate running again. They run without an interactive +terminal. In `sbx`, the startup dispatcher runs alongside the workload: +`background: false` orders startup commands but doesn't delay the workload's +entrypoint. Use install hooks or generated files for initialization the +workload must see before it launches. + +### Generated files + +| Field | Description | +| --- | --- | +| `files[].path` | Required. Absolute path inside the sandbox. | +| `files[].content` | File body. Kit argument references expand at create time. | +| `files[].mode` | Octal permissions as a string. Set explicitly, such as `"0644"`, for consistent file permissions. | +| `files[].overwrite` | Defaults to `true`. Set to `false` to retain an existing file. | +| `files[].description` | Human-readable explanation. | + +Files are written after install hooks, as the agent user, before the workload +runs. An install hook can read files from the image but can't depend on +generated files. Choose a path the agent can write. For root-owned locations, +create the file in the image or use an install hook. For static content, use +Dockerfile `COPY` instead. + +### Interactive arguments + +`interactive` is a list of arguments for TTY sessions. For a workload with +image `ENTRYPOINT` and `CMD`, the launch forms are: + +| Mode | Command | +| --- | --- | +| Default | Image `ENTRYPOINT` followed by image `CMD`. | +| Interactive | Image `ENTRYPOINT` followed by lifecycle `interactive`. | + +An omitted or empty `interactive` list in `sbx` falls back to the default +command. A lifecycle capability must declare at least one hook, file, or +nonempty interactive argument list. + +## Agent context + +`com.docker.runtime/agent-context@1` supplies instructions the agent reads. +The workload chooses the profile filename. Mixins contribute instructions to +that profile's kit index. ```yaml -environment: - variables: - : +capabilities: + - type: com.docker.runtime/agent-context@1 + config: + filename: AGENTS.md + contentFile: ./agent-context.md ``` -| Field | Description | -| ----------- | ---------------------------------------------- | -| `variables` | Key-value pairs set directly in the container. | +| Field | Description | +| --- | --- | +| `filename` | Agent profile filename, such as `AGENTS.md` or `CLAUDE.md`. Accepted only on workload kits. | +| `contentFile` | Path to an instruction file relative to the build context. Mutually exclusive with `content`. | +| `content` | Inline instruction text. Mutually exclusive with `contentFile`. | -Variable names must be valid shell identifiers (`[A-Za-z_][A-Za-z0-9_]*`). +Docker Sandboxes generates the profile in the parent directory of the mounted +workspace inside the sandbox. Inline workload `content` is included directly +in the profile. Inline mixin `content` is written to a separate file under +`kits-agent-context/` beside the profile and indexed from the profile. -Do not set `DASH_`, `SBX_`, or `DOCKER_` variables, and avoid overriding -`HOME`, `USER`, `SHELL`, `PATH`, `LD_PRELOAD`, and `LD_LIBRARY_PATH`. The -runtime reserves these names and may override them. +For `contentFile`, the frontend copies the file into the kit image and +rewrites the published descriptor to point to that location. The profile +references the packaged file, either as workload guidance or in its mixin +index. A mixin omits `filename`. Mixins without a build recipe must use +inline `content`. Building them with `contentFile` fails. -## Setup +## Ports -```yaml -setup: - install: - - command: - user: - description: - startup: - - command: [, ...] - user: - background: - description: - files: - - path: - content: - mode: - onlyIfMissing: - description: -``` +`com.docker.runtime/port@1` publishes an inbound port. Declare a separate +entry for each port and transport. -### Execution order - -When a sandbox is created, kit content is applied in this order: - -1. Network permissions and environment variables. -2. Static files under `files/home/`. -3. `setup.install` commands, in declaration order. -4. `setup.files` entries. -5. `setup.startup` commands are registered for each sandbox start. -6. Static files under `files/workspace/`, after the workspace is ready. With - `--clone`, this means after the repository has been cloned. - -For stacked kits, entries in each stage are applied in `--kit` order. An install -command can consume a bundled file from `files/home/`, but not one from -`files/workspace/` or `setup.files`, because those files land later. - -`sbx kit add` recreates the sandbox rather than modifying it in place. It -supports mixin kits limited to -`environment.variables`, `setup.install`, and `permissions.network.allow`, -which follow the same order as sandbox creation. It rejects a kit that declares -static files, `setup.startup`, or `setup.files`. To use those fields, recreate -the sandbox with the kit. - -### install - -Runs synchronously when a kit is applied, either during sandbox creation or -through `sbx kit add`. Shell strings are passed to `sh -c`. - -Kit install commands start in the template image's configured `WORKDIR`. -Docker-provided templates use `/home/agent/workspace`, which isn't necessarily -the primary workspace in a direct-mounted or clone-mode sandbox. Don't rely on -the current directory to locate workspace files. Use absolute paths for bundled -assets from `files/home/`. - -| Field | Default | Description | -| ------------- | ------- | ----------------------------- | -| `command` | — | Shell command string. | -| `user` | `"0"` | User to run as. `"0"` = root. | -| `description` | — | Human-readable description. | - -### startup - -Runs at every sandbox start. String array, not interpreted by a shell. - -| Field | Default | Description | -| ------------- | -------- | ----------------------------------- | -| `command` | — | Command and args as a string array. | -| `user` | `"1000"` | User to run as. `"1000"` = agent. | -| `background` | `false` | Block later startup commands until this command finishes. Set to `true` to let later commands run without waiting. | -| `description` | — | Human-readable description. | - -Startup commands are non-interactive. They run before the agent -attaches, with no terminal connected, so they can't prompt the user -(for example, an interactive `aws login` will hang or fail). They also -don't gate the agent's entrypoint: the agent launches once startup -commands have been dispatched, regardless of `background`. A value of -`false` waits within the startup dispatcher before it runs the next command; -it doesn't delay the agent entrypoint. Use startup commands -for work that can run alongside the agent. Use `setup.files` for any value that -needs to land on disk before the agent runs. - -Startup commands must be idempotent. They run on every sandbox start -and replay on container restarts, so a command that fails or -misbehaves on a second invocation breaks the restart path. Guard -work with existence checks, use upserts instead of inserts, and -prefer commands that converge to the same end state regardless of -how many times they run. - -### files - -Files written at sandbox start, with runtime substitution. - -| Field | Default | Description | -| --------------- | -------- | --------------------------------------------------------- | -| `path` | — | Absolute container path. | -| `content` | — | File content. `${WORKDIR}` expands to the workspace path. | -| `mode` | `"0644"` | File permissions in octal. | -| `onlyIfMissing` | `false` | Skip if the file already exists. | - -The runtime writes these files as the agent user with UID 1000. The target -path must be writable by that user. To write to a root-owned path such as -`/etc`, use an `install` command, which runs as root by default. Set ownership -in the install command if the agent needs to modify the file later. - -## Static files - -```text -my-kit/files/ -├── home/ → /home/agent/ -└── workspace/ → primary workspace path -``` +| Field | Description | +| --- | --- | +| `container` | Required. Container port from 1 to 65535. | +| `transport` | `tcp` or `udp`. TCP is the default. | +| `name` | Optional label for port listings. | -| Kit path | Container destination | -| ------------------ | --------------------------------------- | -| `files/home/` | `/home/agent/` (config files, dotfiles) | -| `files/workspace/` | The primary workspace path | +The runtime allocates the host port. To choose a host port, use +`sbx ports --publish :` instead of a descriptor +field. Port publication doesn't grant outbound network access. -Parent directories are created automatically. Existing files are -overwritten. Absolute paths and path-traversal sequences (`../../`) are -rejected. +## Resources -## Volumes +`com.docker.runtime/resources@1` declares workload resource settings. -```yaml -volumes: - - path: /workspace - size: 10g - mode: "0755" - - path: /tmp/scratch - type: tmpfs - size: 512m - mode: "1777" -``` +| Field | Description | +| --- | --- | +| `cpu` | Non-negative number of CPU cores in the schema. `sbx` requires a whole number. | +| `memory` | Byte-size string, such as `4096m` or `8g`. | +| `gpu` | GPU selector interpreted by the runtime. Enforcement in `sbx` is pending. | -| Field | Description | -| ------ | ------------------------------------------------------------------- | -| `path` | Required absolute container path. | -| `type` | Empty for a block-backed volume, or `tmpfs` for RAM-backed storage. | -| `size` | Optional byte-size string. | -| `mode` | Optional octal permissions. | +Unset fields add no resource constraint. `sbx` applies CPU and memory from the +workload kit unless overridden at creation. Resource declarations on mixins +aren't applied. + +## Kit registry + +`com.docker.runtime/kit-registry@1` requests access to the runtime's registry +for kit builds. It takes no `config`. + +In `sbx`, this capability is restricted to approved OCI builder kits, with +`docker/sbx-kit-builder` approved by default. Local and Git kit sources don't +receive this grant. Declaring it doesn't grant general network access. + +## Volumes -Volumes are applied only when a sandbox is created. `sbx kit add` cannot attach -volumes to a running container. +`com.docker.runtime/volume@1` describes a storage mount. `sbx` accepts this +schema, but doesn't apply v3 volume requests to the sandbox. + +| Field | Description | +| --- | --- | +| `path` | Required. Absolute mount path inside the sandbox. | +| `size` | Optional byte-size string, such as `2g`. | +| `tmpfs` | If `true`, requests RAM-backed storage. Defaults to `false`. | +| `mode` | Optional octal permissions, such as `"0755"`. | + +## USB devices + +`com.docker.runtime/usb-device@1` describes a USB device request. `sbx` +accepts this schema, but doesn't apply v3 USB requests. + +| Field | Description | +| --- | --- | +| `vendorId` | Vendor identifier. Must be paired with `productId`. | +| `productId` | Product identifier. Must be paired with `vendorId`. | +| `class` | Device class. Mutually exclusive with the vendor and product pair. | + +Declare exactly one match form: a vendor/product pair, or a class. + +## Privileged mode + +`com.docker.runtime/privileged@1` requests elevated runtime privileges. It +takes no `config`. `sbx` accepts this schema, but doesn't apply v3 privileged +requests. + +## Agent sessions + +`com.docker.runtime/agent-sessions@1` describes a workload's session commands. +`sbx` accepts this schema, but doesn't use it to drive sessions. + +| Field | Description | +| --- | --- | +| `prompt` | Argument list appended to the workload's launch command. Must contain `{{.Prompt}}`. | +| `resume` | Argument list appended to the launch command. Must contain `{{.SessionID}}`. | +| `continue` | Argument list to reopen the most recent session. | +| `list` | Complete command, as a shell string or argument list, that outputs session IDs, one per line, most recent first. | + +At least one command is required. `prompt`, `resume`, and `continue` are +argument tails. `list` is a complete command. + +## Published image format + +A published v3 kit is an OCI image. Its manifest annotations carry the +descriptor, its image config carries the launch settings, and its layers +carry content. + +| Annotation | Value | +| --- | --- | +| `vnd.docker.runtime.kit.descriptor` | Published descriptor as compact JSON. | +| `vnd.docker.runtime.kit.schema-version` | `"3"`. | +| `vnd.docker.runtime.kit.capabilities` | Sorted, comma-separated capability types with duplicates removed. Omitted when none are requested. | + +Each image stages its descriptor at +`/usr/share/runtime/kit//kit.yaml` and its recipe, when present, at +`/usr/share/runtime/kit//kit.dockerfile`. Instruction files referenced +by `contentFile` are staged under the same kit directory. Even a mixin with +no recipe has a layer containing its descriptor. + +The published descriptor has a 512 KiB limit, with a build warning above +64 KiB. For kits with a build recipe, keep substantial instruction text in +`contentFile` and other content in image layers. See +[Compose kits](kits.md#compose-kits) for combining images, and +[Packaging and distribution](kits.md#packaging-and-distribution) for publishing them. + +## Move from v2 to v3 + +A v3 kit combines image content with a descriptor. Changing `schemaVersion` +alone doesn't convert a v2 kit. Separate reusable build work from runtime +initialization, then declare the runtime capabilities the kit needs. + +Plan the conversion for the entire sandbox environment. A converted mixin +needs a v3 workload, and a converted workload needs v3 versions of all its +mixins. V2 kits can continue to serve sandboxes that use the built-in agents. +Create a separate sandbox to use the v3 kit set. + +| V2 surface | V3 equivalent | +| --- | --- | +| `kind: sandbox` | `kind: workload` with a Dockerfile recipe | +| `sandbox.image` | Dockerfile `FROM` | +| `sandbox.entrypoint`, `sandbox.command`, `environment.variables` | Dockerfile `ENTRYPOINT`, `CMD`, and `ENV` | +| `extends` | A mixin for composition, or a derived workload image with its own descriptor | +| `setup.install` | Dockerfile `RUN` for reusable content; lifecycle `install` for sandbox initialization | +| `setup.startup` and `setup.files` | Lifecycle capability `startup` and `files` | +| `setup.files[].onlyIfMissing: true` | Lifecycle `files[].overwrite: false` | +| Automatic `files/home/` and `files/workspace/` injection | Dockerfile `COPY`, with lifecycle hooks for destinations provided by runtime mounts | +| `permissions.network` and `credentials` | Network-policy and credential capabilities | +| `agentInstructions` | Agent-context capability | + +Review the [runtime support table](#runtime-capabilities) before migrating +features such as volumes. Publish the converted kit as an image, and select +v3 workload and mixin kits together. diff --git a/content/manuals/ai/sandboxes/customize/kits-v2/_index.md b/content/manuals/ai/sandboxes/customize/kits-v2/_index.md new file mode 100644 index 00000000000..98ac35034e9 --- /dev/null +++ b/content/manuals/ai/sandboxes/customize/kits-v2/_index.md @@ -0,0 +1,708 @@ +--- +title: Kits v2 (deprecated) +linkTitle: Kits v2 +description: Reference for deprecated v2 kits, including usage, schema fields, maintenance examples, signing, and migration to v3. +keywords: sandboxes, sbx, kits, v2, deprecated, migration, spec.yaml +weight: 60 +params: + sidebar: + badge: + color: gray + text: Deprecated +--- + +{{< summary-bar feature_name="Docker Sandboxes sbx" >}} + +V2 kits are deprecated but remain supported. Use [v3 kits](../kits.md) for +further kit development. This page covers the v2 format for maintaining +existing customizations. + +Built-in shortcuts such as `claude` and `codex` select v2 kits and still work +with v2 mixins. V3 workloads and mixins can't be combined with v1 or v2 kits. +V1 also remains supported. + +## Move an environment to v3 + +Select a v3 workload, convert or replace its mixins, and create a separate +sandbox with a different `--name`. Use the explicit workload reference in +place of the built-in shortcut. Every selected kit must use v3. + +Changing `schemaVersion` alone doesn't convert a kit. See the +[v2-to-v3 field mapping](../kit-reference.md#move-from-v2-to-v3), the +[v3 runtime support table](../kit-reference.md#runtime-capabilities), and the +[agent authoring tutorial](../build-an-agent.md). Running an existing sandbox +keeps its recorded configuration; it doesn't migrate the kit set. + +## Use existing kits + +A v2 kit contains `spec.yaml` and an optional `files/` tree. Pass a sandbox kit +in place of the agent name and add mixins with `--kit`: + +```console +$ sbx run ./my-agent --name my-project --kit ./team-config +$ sbx run claude --name claude-project --kit ./team-config +``` + +Use `sbx create` with the same arguments to create without launching the +agent. References can be local directories, ZIP files, OCI artifacts, or Git +URLs. Start relative paths with `./` or `../` and include `docker.io/` for +Docker Hub references. In Git URLs, `ref` selects a revision and `dir` the kit +directory. Quote URLs containing `&`: + +```console +$ sbx run "git+https://github.com//.git#ref=&dir=my-agent" +``` + +`git+ssh://` URLs work with your local SSH agent and Git credentials. See +[Restrict kit sources](../kits.md#restrict-kit-sources) for source policies; +`kit.allowLocalKits` also governs v2 ZIP files. For private registries, see +[Registry credentials](../../configuration/credentials.md#registry-credentials). + +Kit selection with `--kit` applies at creation. Recreate the sandbox to change +its kit set, except for the limited updates supported by +[`sbx kit add`](#execution-order). That command restarts the sandbox while +preserving packages, images, volumes, and agent history. Kits can't be +removed from a running sandbox. + +## Schema versions + +Schema v2 is supported starting with Docker Sandboxes version 0.36. Use +`schemaVersion: "2"` for the syntax on this page. Version `"1"` also remains +accepted. V3 is a separate format for environments built entirely +with v3 workloads and mixins. V3 kits can't compose with v1 or v2 kits. +See [Kits v3](../kits.md) for that workflow. + +The loader forks on `schemaVersion`. A v2 spec uses the v2 grammar only. Legacy +v1 fields in a `schemaVersion: "2"` spec are rejected during decode instead of +being folded into the v2 model. Keep each `spec.yaml` on one grammar. + +What changed in v2: + +| v1 | v2 | +| ------------------------------------------- | ---------------------------------------- | +| `credentials.sources.` | `credentials:` list entry with `service` | +| `network.allowedDomains` / `deniedDomains` | `permissions.network.allow` / `deny` | +| `network.serviceDomains` / `serviceAuth` | `credentials[].apiKey.inject` | +| `network.publishedPorts` / `publishedPorts` | top-level `ports` | +| standalone `oauth:` block | `credentials[].oauth` | +| `oauth.skipIfEnv` | Accepted but ignored | +| `environment.proxyManaged` | `credentials[].apiKey.proxyManaged` | +| `memory` / `agentContext` | `agentInstructions.content` | +| `kind: agent` / `agent:` block | `kind: sandbox` / `sandbox:` block | +| `sandbox.aiFilename` | `agentInstructions.filename` | +| `sandbox.entrypoint.run` | `sandbox.entrypoint` | +| `sandbox.entrypoint.args` | `sandbox.command.default` | +| `sandbox.entrypoint.ttyArgs` | `sandbox.command.interactive` | +| `tmpfs:` | `volumes:` entries with `type: tmpfs` | +| `volumes:` (mapping form) | `volumes:` sequence (`- path: `) | +| `commands:` / `commands.initFiles` | `setup:` / `setup.files` | +| `settings:` / `kitDir` / `persistence` | Removed | + +Credential discovery also moved out of the kit in v2: a kit declares which +credentials it needs and how to inject them, but where each value comes from is +controlled by the user through +[credential bindings](../../configuration/credentials.md#credential-bindings). + +> [!NOTE] +> `mixins` and `sandbox.build` are accepted by the parser, but runtime support +> is pending. A kit that sets `sandbox.build` must also set `sandbox.image`. + +## Top-level fields + +For the normative grammar, see the +[v2 specification](https://github.com/docker/sbx-kits-contrib/blob/main/spec/SPEC-v2.md). + +| Field | Required | Description | +| --------------- | -------- | ----------------------------------------------------------------------------------------------- | +| `schemaVersion` | Yes | Spec schema version. Use `"2"` for this grammar. | +| `kind` | Yes | `mixin` for kits that extend an agent; `sandbox` for kits that define one. | +| `name` | Yes | Unique identifier. Lowercase alphanumeric with hyphens, 1 to 64 characters. | +| `version` | No | Kit version. | +| `displayName` | No | Human-readable name. | +| `description` | No | Short description. | +| `sourceURL` | No | Source repository or documentation URL. | +| `licenses` | No | SPDX license identifiers. | +| `locked` | No | Dotted paths child kits may not override. | +| `security` | No | Container security settings. `security.privileged: true` runs the container in privileged mode. | +| `args` | No | Arguments supplied when the kit is loaded. Schema v2 only. | + +A kit also declares behavior blocks such as `agentInstructions`, +`permissions`, `ports`, `credentials`, `environment`, `setup`, and `volumes`. + +## Arguments + +A schema v2 kit can declare arguments and reference them anywhere in +`spec.yaml` or under `files/` as `${{ kit.args. }}`. Substitution happens +before the spec is decoded. + +```yaml +args: + version: + default: latest + description: Tool version to install + pattern: '^(latest|[0-9]+\.[0-9]+\.[0-9]+)$' + channel: + default: stable + enum: [stable, beta, nightly] + target: + required: true + description: Build target + +environment: + variables: + TOOL_VERSION: "${{ kit.args.version }}" +``` + +Don't use kit arguments for API tokens, passwords, or other secrets. Use +[Credentials](../../configuration/credentials.md) to provide sensitive values to +a sandbox. + +| Field | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------ | +| Argument name | Starts with a letter or underscore and contains only letters, digits, underscores, and hyphens. | +| `default` | String to use when the caller supplies no value. Mutually exclusive with `required: true`. | +| `required` | Set to `true` when the caller must supply a value. Mutually exclusive with `default`. | +| `description` | Optional help text shown when a required value is missing. | +| `enum` | Optional list of accepted values. Mutually exclusive with `pattern`. | +| `pattern` | Optional Go RE2 regular expression matched against the complete value. Mutually exclusive with `enum`. | + +Each argument must declare either `default`, including an empty-string +default, or `required: true`. A declared default must satisfy its own `enum` or +`pattern`. Every `${{ kit.args. }}` reference must have a matching +declaration. + +Argument values are strings, but substitution happens before YAML decoding. +Quote a placeholder in a string-valued field so a value such as `1.20` isn't +decoded as a number. + +### Pass arguments to kits + +Use `--kit-arg name=value` for every kit declaring that argument, or prefix +with the kit's `name` to target one kit. Scoped values override shared values: + +```console +$ sbx run ./my-agent --kit ./my-mixin --kit-arg channel=stable \ + --kit-arg my-mixin.channel=beta +``` + +`--kit-args-file ` reads `name=value` entries, ignoring blank lines and +`#` comments. Later files override earlier files; `--kit-arg` overrides files. +For repeated CLI keys, the last value wins. Missing required values, unknown +arguments, undeclared placeholders, and invalid values fail before creation. +Pass the same flags to `sbx kit validate` or `sbx kit inspect` when needed. +Argument values can remain in shell history and are stored unencrypted in +argument files. + +## Kit kinds + +### `kind: mixin` + +A mixin layers capabilities onto an existing sandbox. It must not declare a +`sandbox:` block, `extends:`, or `mixins:`. A mixin can declare `requires:` to +pin the base agent it is designed for: + +```yaml +schemaVersion: "2" +kind: mixin +name: github-tools +requires: + agent: claude +``` + +`requires.agent` takes one base-agent name. It is validated as a kit name and +enforced during composition. + +### `kind: sandbox` + +A sandbox kit defines a full agent. A root sandbox must declare a `sandbox:` +block. A sandbox that uses `extends:` can inherit the parent image and omit its +own `sandbox:` block: + +```yaml +schemaVersion: "2" +kind: sandbox +name: claude-safe +extends: claude +``` + +`extends:` is sandbox-only. The parent must resolve to a sandbox kit. `mixins:` +is also sandbox-only and accepted by the parser, but runtime composition support +is pending. + +## Sandbox block + +```yaml +sandbox: + image: + build: + context: . + dockerfile: Dockerfile + args: + AGENT_VERSION: "1.0.0" + target: runtime + platforms: + - linux/amd64 + entrypoint: [my-agent, "--flag"] + command: + default: ["--task-mode"] + interactive: [] + resources: + cpu: 2 + memory: 4g + gpu: "1" +``` + +| Field | Required | Description | +| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------- | +| `sandbox.image` | When `extends:` is omitted | Docker image reference. | +| `sandbox.build` | No | Build configuration. Runtime support is pending, so a kit with `build:` must also set `image:`. | +| `sandbox.entrypoint` | No | Fixed process prefix as a string array. The first element is the agent binary. | +| `sandbox.command` | No | Mode-specific argument tail. Use a list shorthand for `default`, or a mapping with `default` and `interactive`. | +| `sandbox.resources` | No | Optional CPU, memory, and GPU constraints. Memory uses byte-size strings such as `4096m` or `4g`. | + +The effective command is `entrypoint` plus `command.default` for non-interactive +launches, and `entrypoint` plus `command.interactive` for TTY sessions. If +`interactive` is omitted, it falls back to `default`. + +For a kit that uses `extends:`, `sandbox.command` replaces the full inherited +argument tail, including flags after the binary in the parent's +`sandbox.entrypoint`. It doesn't append to that tail. Define every argument the +child needs. For example, a child of `claude` that adds `--settings` must also +include `--dangerously-skip-permissions` to preserve that behavior. + +The agent's container image must provide: + +- A non-root `agent` user at UID 1000 with passwordless sudo. +- A `/home/agent/` home directory owned by `agent`. +- HTTP proxy environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`) preserved across sudo. +- The agent binary, either baked in or installed with [`setup.install`](#setup). + +Build on top of `docker/sandbox-templates:shell-docker` to get these base +requirements. + +## Agent instructions + +Declare these fields under `agentInstructions`: + +| Field | Description | +| ---------- | --------------------------------------------------------------------------------------------------- | +| `filename` | AI profile filename. Meaningful for `kind: sandbox`; ignored with a warning for `kind: mixin`. | +| `content` | Markdown instructions. For a sandbox, inlined into the profile. For a mixin, written to kit memory. | + +For mixins, the engine writes `content` to +`/kits-memory/.md` and adds a `## Kits` pointer +section to the base AI file. This keeps each mixin's instructions in a separate +file. + +The generated profile lives in the parent directory of the mounted workspace +inside the sandbox. It sits outside the mount and doesn't replace an +instruction file in the project. The sandbox kit's inline instructions go +directly into that profile. + +## Credentials + +A kit declares the credentials it needs and how the proxy injects them into +outbound requests. It does not declare a host discovery source. The user +provides the value through the secret store or the first-run prompt, and a +[credential binding](../../configuration/credentials.md) authorizes its use. A kit +can't read arbitrary host environment variables or files. + +`credentials` is a list; each entry names a `service` and configures one or more +auth mechanisms. + +| Field | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| `service` | Credential identifier, matched against the value stored with `sbx secret set`. Lowercase kebab-case. | +| `description` | Optional. Shown to the user when approving a [binding](../../configuration/credentials.md#credential-bindings). | +| `required` | Marks the credential as essential to the agent. If it has no binding, `sbx` warns and starts with the credential withheld. Default `false`. | +| `provider` | Reserved for a provider registry. Accepted with a warning and no runtime effect. | +| `apiKey` | API-key injection (see [apiKey](#apikey)). | +| `oauth` | OAuth interception (see [oauth](#oauth)). | + +Each service must declare `apiKey`, `oauth`, or both. When both resolve at +runtime, the API key takes precedence and OAuth acts as the fallback. + +### `apiKey` + +| Field | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Environment variable name for the credential (for example, `ANTHROPIC_API_KEY`). | +| `proxyManaged` | If `true`, `sbx` sets `name` inside the container to the `proxy-managed` sentinel. Default `false`. | +| `inject[].domain` | Domain to inject the credential into. Must also be allowed in [`permissions.network`](#network). | +| `inject[].header` | HTTP header the proxy sets (for example, `x-api-key`, `Authorization`). | +| `inject[].format` | Header value format, with one `%s` placeholder (for example, `"%s"` or `"Bearer %s"`). Mutually exclusive with `scheme`. | +| `inject[].scheme` | Shorthand for common auth schemes. `bearer` expands to `Authorization: Bearer %s`; `basic` requires `username`. Mutually exclusive with `format`. | +| `inject[].username` | Username for HTTP Basic auth, for example `x-access-token` for Git over HTTPS. | + +### `oauth` + +For agents that authenticate with OAuth (for example, Claude Code), the proxy +intercepts token responses and replaces real tokens with sentinels, then swaps +the real token back in on outbound requests. By default, the token never enters +the sandbox. Setting `passthrough: true` opts out of sentinel masking and sends +the real token response into the sandbox. + +| Field | Description | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `tokenEndpoint.host` / `path` | The OAuth token endpoint the proxy intercepts. | +| `sentinels.accessToken` / `refreshToken` | Sentinel values written into the container in place of the real tokens. | +| `credentialFile.path` | Where to write the credential file inside the container (`~` expands). | +| `credentialFile.structure` | Declarative JSON shape. Supports `{{.AccessToken}}`, `{{.RefreshToken}}`, `{{.ExpiresAt}}`, and `{{.Scopes}}`. | +| `credentialFile.template` | Go template. Supports `{{.AccessToken}}`, `{{.RefreshToken}}`, `{{.ExpiresAt}}`, `{{.Scopes}}`, and `{{.ScopesJSON}}`. | +| `resourceHosts` | API hosts where the proxy attaches the token on outbound requests, distinct from the token endpoint host. | +| `skipIfEnv` | Accepted for compatibility, but ignored for schema v2. A v2 binding is authoritative instead of host environment variables. | +| `responseFields` | Overrides the default field names the proxy reads from the token response. | +| `passthrough` | If `true`, the proxy passes the token response through unchanged instead of replacing the tokens with sentinels. | + +`credentialFile.structure` provides a declarative alternative to +`credentialFile.template`. The engine renders it as well-formed JSON. If both +fields are set, `structure` takes precedence. + +## Network + +Network egress is declared under `permissions.network`. Credentials no longer carry +their own domain mapping — the proxy injects a credential only into the domains +its [`apiKey.inject`](#apikey) lists, and every domain the +sandbox reaches must be allowed here. + +| Field | Description | +| --------------------------- | --------------------------------------------------------------------------------------------------------------- | +| `permissions.network.allow` | Domains the sandbox can reach. | +| `permissions.network.deny` | Domains the sandbox is blocked from reaching. Deny takes precedence over allow, including across composed kits. | + +Allow and deny patterns: + +| Pattern | Example | Status | +| --------------------- | ------------------------ | --------------------------- | +| Exact host | `api.example.com` | Enforced | +| Exact host and port | `api.example.com:8080` | Enforced | +| Single-label wildcard | `*.example.com` | Enforced | +| Multi-label wildcard | `**.example.com` | Parsed; enforcement pending | +| Port range | `api.example.com:80-443` | Parsed; enforcement pending | +| Port wildcard | `api.example.com:*` | Parsed; enforcement pending | +| CIDR | `10.0.0.0/8` | Parsed; enforcement pending | + +In v1 this was the `network:` block (`allowedDomains` / `deniedDomains`, plus +`serviceDomains` / `serviceAuth`). In v2, those fields are decode errors. + +## Ports + +Declare `ports` as a list of entries to expose sandbox services to the host: + +| Field | Description | +| ----------- | ------------------------------------------------------------------- | +| `container` | Container port, 1 to 65535. | +| `protocol` | `tcp` or `udp`. Empty publishes one family; see below. | +| `name` | Optional label surfaced by tools that list published port bindings. | + +Host ports are allocated ephemerally. Leave `protocol` empty unless the service +listens on IPv6: an empty value publishes IPv4 only (`127.0.0.1`), which is what +a service bound to `0.0.0.0` needs, while `tcp` publishes both `127.0.0.1` and +`::1` — and a client arriving over `::1` is accepted and then reset if nothing +in the sandbox is listening there. Users can pin host ports with +`sbx ports --publish :`. + +## Environment + +| Field | Description | +| ----------- | ---------------------------------------------- | +| `environment.variables` | Key-value pairs set directly in the container. | + +Variable names must be valid shell identifiers (`[A-Za-z_][A-Za-z0-9_]*`). + +Do not set `DASH_`, `SBX_`, or `DOCKER_` variables, and avoid overriding +`HOME`, `USER`, `SHELL`, `PATH`, `LD_PRELOAD`, and `LD_LIBRARY_PATH`. The +runtime reserves these names and may override them. + +## Setup + +`setup.install`, `setup.startup`, and `setup.files` are lists of commands or +files with the fields described here. + +### Execution order + +When a sandbox is created, kit content is applied in this order: + +1. Network permissions and environment variables. +2. Static files under `files/home/`. +3. `setup.install` commands, in declaration order. +4. `setup.files` entries. +5. `setup.startup` commands are registered for each sandbox start. +6. Static files under `files/workspace/`, after the workspace is ready. With + `--clone`, this means after the repository has been cloned. + +For stacked kits, entries in each stage are applied in `--kit` order. An install +command can consume a bundled file from `files/home/`, but not one from +`files/workspace/` or `setup.files`, because those files land later. + +`sbx kit add` recreates the sandbox rather than modifying it in place. It +supports mixin kits limited to +`environment.variables`, `setup.install`, and `permissions.network.allow`, +which follow the same order as sandbox creation. It rejects a kit that declares +static files, `setup.startup`, or `setup.files`. To use those fields, recreate +the sandbox with the kit. + +### install + +Runs synchronously when a kit is applied, either during sandbox creation or +through `sbx kit add`. Shell strings are passed to `sh -c`. + +Kit install commands start in the template image's configured `WORKDIR`. +Docker-provided templates use `/home/agent/workspace`, which isn't necessarily +the primary workspace in a direct-mounted or clone-mode sandbox. Don't rely on +the current directory to locate workspace files. Use absolute paths for bundled +assets from `files/home/`. + +| Field | Default | Description | +| ------------- | ------- | ----------------------------- | +| `command` | — | Shell command string. | +| `user` | `"0"` | User to run as. `"0"` = root. | +| `description` | — | Human-readable description. | + +### startup + +Runs at every sandbox start. String array, not interpreted by a shell. + +| Field | Default | Description | +| ------------- | -------- | ----------------------------------- | +| `command` | — | Command and args as a string array. | +| `user` | `"1000"` | User to run as. `"1000"` = agent. | +| `background` | `false` | Block later startup commands until this command finishes. Set to `true` to let later commands run without waiting. | +| `description` | — | Human-readable description. | + +Startup commands are non-interactive. They run before the agent +attaches, with no terminal connected, so they can't prompt the user +(for example, an interactive `aws login` will hang or fail). They also +don't gate the agent's entrypoint: the agent launches once startup +commands have been dispatched, regardless of `background`. A value of +`false` waits within the startup dispatcher before it runs the next command; +it doesn't delay the agent entrypoint. Use startup commands +for work that can run alongside the agent. Use `setup.files` for any value that +needs to land on disk before the agent runs. + +Startup commands must be idempotent. They run on every sandbox start +and replay on container restarts, so a command that fails or +misbehaves on a second invocation breaks the restart path. Guard +work with existence checks, use upserts instead of inserts, and +prefer commands that converge to the same end state regardless of +how many times they run. + +### files + +Files written at sandbox start, with runtime substitution. + +| Field | Default | Description | +| --------------- | -------- | --------------------------------------------------------- | +| `path` | — | Absolute container path. | +| `content` | — | File content. `${WORKDIR}` expands to the workspace path. | +| `mode` | `"0644"` | File permissions in octal. | +| `onlyIfMissing` | `false` | Skip if the file already exists. | + +The runtime writes these files as the agent user with UID 1000. The target +path must be writable by that user. To write to a root-owned path such as +`/etc`, use an `install` command, which runs as root by default. Set ownership +in the install command if the agent needs to modify the file later. + +### Shell initialization and service logs + +With Docker templates, append shell initialization to +`/etc/sandbox-persistent.sh` in an install command. Keep existing content and +omit completion scripts: interactive and non-interactive Bash commands source +this file. For a background service, redirect startup output to a file and +read it with `sbx exec`. Use `background: true` instead of a trailing `&`. + +## Static files + +```text +my-kit/files/ +├── home/ → /home/agent/ +└── workspace/ → primary workspace path +``` + +| Kit path | Container destination | +| ------------------ | --------------------------------------- | +| `files/home/` | `/home/agent/` (config files, dotfiles) | +| `files/workspace/` | The primary workspace path | + +Parent directories are created automatically. Existing files are +overwritten. Absolute paths and path-traversal sequences (`../../`) are +rejected. + +Static files can supply linter settings, helper scripts, or agent skills. +For example, a Claude Code project skill belongs at +`files/workspace/.claude/skills//SKILL.md`. + +## Volumes + +Declare `volumes` as a list of mounts with these fields: + +| Field | Description | +| ------ | ------------------------------------------------------------------- | +| `path` | Required absolute container path. | +| `type` | Empty for a block-backed volume, or `tmpfs` for RAM-backed storage. | +| `size` | Optional byte-size string. | +| `mode` | Optional octal permissions. | + +Volumes are applied only when a sandbox is created. `sbx kit add` cannot attach +volumes to a running container. + +## Fork an existing agent + +Sandbox kits (`kind: sandbox`) define a full agent from scratch. The most +common variant is a fork of a built-in agent. Use `extends:` to inherit the +parent's complete configuration and declare only the fields you want to change. +This example replaces the built-in `claude` entrypoint so Claude Code uses +manual permission mode instead of bypassing approval prompts: + +```yaml {title="claude-safe/spec.yaml"} +schemaVersion: "2" +kind: sandbox +name: claude-safe +displayName: Claude Code (with approval prompts) +description: Claude Code in manual permission mode + +extends: claude + +sandbox: + entrypoint: [claude, "--permission-mode", "manual"] +``` + +The child inherits the built-in image, credentials, network permissions, +persistent volumes, settings, MCP integration, agent instructions, setup +entries, and environment variables. Its `sandbox.entrypoint` replaces the +inherited entrypoint. + +Launch by passing the sandbox kit in place of a built-in agent name: + +```console +$ sbx run ./claude-safe +``` + +## Install an internal CA certificate + +Put each PEM-encoded root certificate under `files/home/` with a `.crt` +extension. For `files/home/internal-ca.crt`, use: + +```yaml {title="internal-ca/spec.yaml"} +schemaVersion: "2" +kind: mixin +name: internal-ca +setup: + install: + - command: "install -m 0644 /home/agent/internal-ca.crt /usr/local/share/ca-certificates/internal-ca.crt && update-ca-certificates" + user: "0" +``` + +This updates the system trust store. For several CAs, install every +certificate before running `update-ca-certificates`. + +## Sandbox-managed agent configuration + +Built-in agent kits reserve the following paths for sandbox setup. Treat these +paths as sandbox-managed, even if a file is only needed for a particular +feature. Don't target them with static files, `setup.files`, or install +commands. Later setup can replace your content or depend on settings that your +file removes. In this table, `~` is `/home/agent`. + +| Built-in agent kit | Managed configuration paths | +| ------------------ | --------------------------- | +| `claude` | `~/.claude.json`, `~/.claude/settings.json`, `~/.claude/.config.json` | +| `codex` | `~/.codex/config.toml` | +| `copilot` | `~/.copilot/config.json` | +| `cursor` | `~/.cursor/cli-config.json` | +| `devin` | `~/.config/devin/config.json`, `~/.config/devin/mcp_config.json` | +| `gemini` | `~/.gemini/settings.json` | +| `kiro` | `~/.kiro/settings/mcp.json` | +| `opencode` | `~/.config/opencode/opencode.json` | + +Use separate settings files when supported: Claude Code accepts `--settings`, +and OpenCode reads `OPENCODE_CONFIG`. Don't use `setup.startup` for settings +the agent must read during initialization; startup commands don't gate the +entrypoint. + +## Packaging and distribution + +The `sbx kit` subcommands validate, inspect, and publish kits: + +- `sbx kit validate ` — check that a kit directory or ZIP is + well-formed. +- `sbx kit inspect ` — display kit details. Add `--json` for + machine-readable output. +- `sbx kit pack -o ` — package a directory as a ZIP file + for sharing. +- `sbx kit push ` — publish to an OCI registry (for example, + `ghcr.io/myorg/my-kit:1.0`). +- `sbx kit pull ` — download a kit from a registry as a ZIP file to + the working directory. + +For Docker Hub, include the full `docker.io` prefix — `sbx` doesn't add it +automatically. + +For Docker Hub, `sbx kit pull` and `sbx kit push` use the session from +`sbx login`. For other registries, they prefer credentials stored with +[`sbx secret set --registry`](../../configuration/credentials.md#registry-credentials). +Both commands fall back to the Docker credential store, so credentials from +`docker login` also work. + +## Sign and verify kits + +Use cosign-compatible Sigstore signatures to verify who approved a kit and +that its signed content hasn't changed. Signing is keyless by default. Verify a +keyless signature with the certificate identity and OpenID Connect (OIDC) +issuer: + +```console +$ sbx kit sign ./my-kit/ +$ sbx kit verify \ + --certificate-identity user@example.com \ + --certificate-oidc-issuer https://accounts.google.com \ + ./my-kit/ +``` + +For key-based signing, use an ECDSA P-256 key pair: + +```console +$ sbx kit sign --key cosign.key ./my-kit/ +$ sbx kit verify --key cosign.pub ./my-kit/ +``` + +For a local directory, `sbx kit sign` writes a `kit.sig.bundle` file next to +`spec.yaml`. Commit this file so consumers can verify a kit loaded from the Git +repository. For an OCI kit, the signature is stored as an OCI referrer. You can +sign an OCI kit after pushing it, or push and sign it in one step: + +```console +$ sbx kit push ./my-kit/ ghcr.io/myorg/my-kit:1.0 --sign +``` + +ZIP kits can't carry verifiable signatures. + +### Require signed kits + +Set a trusted signer policy for the identities or keys you trust before +requiring signatures. Otherwise, `sbx` uses the default policy, which trusts +Docker employee identities attested by Google's OpenID Connect issuer. A +keyless policy must specify both the certificate identity and its OpenID +Connect issuer: + +```console +$ sbx settings set kit.trustedSigners \ + '[{"identity":"release-bot@example.com","issuer":"https://accounts.google.com"}]' +$ sbx settings set kit.requireSignature true +``` + +To trust a key-based signature, set the policy to the public key path: + +```console +$ sbx settings set kit.trustedSigners '[{"key":"/path/to/cosign.pub"}]' +$ sbx settings set kit.requireSignature true +``` + +When `kit.requireSignature` is `true`, `sbx` rejects unsigned kits, signatures +that don't match `kit.trustedSigners`, and ZIP kits. This policy applies when a +kit is loaded from a local directory, Git repository, or OCI registry. + +The signature covers `spec.yaml` and the kit's `files/` content, but not mutable +dependencies such as image tags or content downloaded by install and startup +commands. Pin those dependencies by digest or checksum when they must remain +immutable. diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 685f60f5529..f82d94798bf 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -1,558 +1,693 @@ --- title: Kits -description: Extend a sandbox with tools, credentials, network rules, and configuration using declarative YAML artifacts. -keywords: sandboxes, sbx, kits, mixins, customization, extensions, agents +description: Build, compose, and distribute sandbox workloads and extensions with kits v3, using image content and declared runtime capabilities. +keywords: sandboxes, sbx, kits, v3, workloads, mixins, capabilities, builds, composition weight: 20 --- {{< summary-bar feature_name="Docker Sandboxes sbx" >}} +Kits let you shape a sandbox around the way you work. Choose its base image, +add the tools your project needs, and give your agent instructions for using +them. You also control which services the sandbox can access, how it +authenticates, and what runs when the sandbox starts. + +A kit can define the whole environment or add something to an existing one, +such as a toolchain or your team's shared configuration. Package those choices +once, then reuse them across projects and share them with your team. + +This page covers v3, the recommended format for kit development. V3 brings +image builds and runtime capabilities together in the kit format. + > [!NOTE] -> Kits are experimental. The kit file format, CLI commands, and experience -> for creating, loading, and managing kits are subject to change as the -> feature evolves. Share feedback and bug reports in the +> Kits are experimental. The format and CLI commands are subject to change. +> Share feedback in the > [docker/sbx-releases](https://github.com/docker/sbx-releases) repository. -A kit packages a set of capabilities a sandbox can use, such as: +## Workloads and mixins + +A sandbox runs one workload kit. You can add mixin kits to customize that +workload: -- Tools to install -- Environment variables to set -- Credentials to inject -- Network rules to allow or deny domains -- Files to drop in -- Startup commands to run -- Memory instructions to give the agent +| Kind | What it supplies | How you use it | +| --- | --- | --- | +| `workload` | The environment and command to run, such as an agent or a shell | Pass it to `sbx run` or `sbx create` | +| `mixin` | Additional tools, configuration, or runtime behavior | Add it with `--kit` | -You declare these in a single `spec.yaml` file, point the CLI at the -directory (or a ZIP, OCI artifact, or Git URL), and the sandbox applies -and enforces them at runtime. Credentials stay on the host and go through -a proxy instead of entering the VM, and outbound traffic is restricted to -the domains permitted by the kit's network rules. +For example, a team might use an OpenCode workload with a mixin that adds a +linter and another that supplies the team's review instructions. Each kit can +be maintained and shared separately. -A kit is either a mixin or a sandbox: +## Version compatibility -- Mixin kits (`kind: mixin`) extend an existing agent with extra - capabilities. Stack several on the same sandbox. -- Sandbox kits (`kind: sandbox`) define a full agent from scratch: its image, - entrypoint, network policies, and everything else the agent needs. +V3 kits cannot be combined with v1 or v2 kits in the same sandbox. To use +v3, select a v3 workload and use v3 for every mixin you add. + +The built-in agent names, such as `claude` and `codex`, select v2 kits. +You can't add a v3 mixin to these built-ins. For example, +`sbx run claude --kit ./some-v3-mixin` fails because it mixes kit versions. +Instead, select a v3 workload by its published image, local path, or Git +reference, as shown in [Run a kit](#run-a-kit). + +V2 is deprecated but remains supported, including the built-in agents and +existing v2 customizations. See [Kits v2](kits-v2/_index.md) for maintenance +and migration guidance. ## What kits can do -### Run commands +Use kits to give agents a repeatable working environment and share it with +your team: -A kit can run commands inside the sandbox automatically. **Install -commands** run once at creation; **startup commands** run each time -the sandbox starts. +- Package a custom agent, or configure an existing agent for your team's + projects. +- Include the tools the agent needs, such as linters, language runtimes, + test runners, and compilers. +- Share linter rules, editor settings, helper scripts, and reference material. + Give the agent instructions and skills for using them. +- Connect the agent to services through network rules and credentials, + including internal APIs and private package registries. +- Initialize each sandbox and run supporting services when it starts, such + as a development server for previewing the agent's work. -Install commands are the place to put anything an agent needs into the -image, via `apt`, `pip`, `npm`, `curl | bash`, or whatever fits: +## Use kits -```yaml -setup: - install: - - command: "apt-get update && apt-get install -y jq" -``` +You can use a kit someone else has published, or build your own. Kit authors +describe the environment in a YAML file and package any software and files it +needs into an image. To use it, give `sbx` the kit's reference. Docker +Sandboxes prepares the environment and applies its settings. -Startup commands are for work that can run alongside the agent, such as a -background service. They must be idempotent — see the -[`startup`](kit-reference.md#startup) spec reference: +### Run a kit -```yaml -setup: - startup: - - command: ["my-daemon"] - background: true +Run a workload by passing its kit reference to `sbx run`, followed by the +project directory to use as the sandbox's workspace. You can choose a +published v3 workload or supply one from a local directory or Git repository: + +```console +$ sbx run ``` -### Inject files +To create a sandbox without launching the workload, use `sbx create` with +the same arguments. -Kits can inject files into the sandbox in two ways: **static files** bundled -with the kit, and **`setup.files`** written at startup with runtime values -substituted in. +If you don't have a v3 workload to run, the +[OpenCode workload example](#build-a-workload) provides a complete source kit. +For a step-by-step authoring walkthrough, see [Build an agent](build-an-agent.md). -Static files work well for content that doesn't vary between sandboxes, such -as tool configurations, shared linter rules, helper scripts the agent can -invoke, or reference material like a style guide or API cheatsheet. +### Add mixins -```text -my-kit/ -├── spec.yaml -└── files/ - ├── home/ - │ └── .config/my-tool/settings.json - └── workspace/ - └── .editorconfig +A sandbox runs one workload kit. Add v3 mixins with `--kit`, repeating the +flag for each one. For example, run a local v3 workload with two v3 mixins: + +```console +$ sbx run ./my-agent --kit ./linter --kit ./team-config ``` -`setup.files` cover content that depends on runtime values, such as an -absolute workspace path that a tool needs to bake into its config file -at startup: +The workload and its mixins form a composition: their tools, files, and +runtime settings combine to define the sandbox's environment. See +[Compose kits](#compose-kits) for dependency and compatibility rules, and +[Kit examples](kit-examples.md) for complete mixins. -```yaml -setup: - files: - - path: /home/agent/.my-tool/config.json - content: '{"workspace": "${WORKDIR}"}' - onlyIfMissing: true +### Choose a kit source + +Workload and mixin references can point to a published image, a local +directory, or a kit in a Git repository: + +| Source | Example reference | +| --- | --- | +| Published image | `docker.io//my-agent:1.0.0` | +| Local directory | `./my-agent` | +| Git repository | `git+https://github.com//.git#ref=&dir=my-agent` | + +For Git sources, `ref` selects a revision and `dir` selects the kit's +subdirectory. Quote Git URLs in shell commands because they can contain `&`: + +```console +$ sbx run "git+https://github.com//.git#ref=&dir=my-agent" ``` -See [`setup.files`](kit-reference.md#files) in the spec reference for all -fields. - -#### Sandbox-managed agent configuration - -Built-in agent kits reserve the following paths for sandbox setup. Treat these -paths as sandbox-managed, even if a file is only needed for a particular -feature. Don't target them with static files, `setup.files`, or install -commands. Later setup can replace your content or depend on settings that your -file removes. In this table, `~` is `/home/agent`. - -| Built-in agent kit | Managed configuration paths | -| ------------------ | --------------------------- | -| `claude` | `~/.claude.json`, `~/.claude/settings.json`, `~/.claude/.config.json` | -| `codex` | `~/.codex/config.toml` | -| `copilot` | `~/.copilot/config.json` | -| `cursor` | `~/.cursor/cli-config.json` | -| `devin` | `~/.config/devin/config.json`, `~/.config/devin/mcp_config.json` | -| `gemini` | `~/.gemini/settings.json` | -| `kiro` | `~/.kiro/settings/mcp.json` | -| `opencode` | `~/.config/opencode/opencode.json` | - -Use a separate settings layer when the agent supports one. For example, Claude -Code can load an additional settings file with `--settings`, and OpenCode can -load one from the path in `OPENCODE_CONFIG`. See -[Customize agent settings](kit-examples.md#customize-agent-settings) for -examples. Don't use `setup.startup` for settings the agent must read during -initialization because startup commands don't gate the agent entrypoint. +`sbx` pulls published images and builds local or Git sources when creating +the sandbox. Builds with unchanged source content and supplied kit arguments +reuse cached results. See +[Packaging and distribution](#packaging-and-distribution) for publishing kits +and configuring access to remote sources. -### Set environment variables +### Name and reuse a sandbox -Environment variables set by the kit are available to the agent at -runtime: +Use `--name` to give the sandbox a name: -```yaml -environment: - variables: - MY_TOOL_WORKSPACE: /home/agent/my-tool +```console +$ sbx run ./my-agent --name my-project ``` -For credentials, see -[Authenticate to external services](#authenticate-to-external-services). -Don't put secret values directly in `environment.variables` — they'd -be visible inside the sandbox VM. - -> [!IMPORTANT] -> The sandbox manages proxy settings for you. It sets `HTTP_PROXY`, -> `HTTPS_PROXY`, `NO_PROXY`, and their lowercase equivalents automatically so -> that traffic flows through its built-in forward proxy, which enforces -> network policy and injects credentials. Leave these variables to the -> sandbox — setting them in a kit points traffic away from the forward proxy, -> so it can no longer apply network policy or inject credentials, and those -> requests typically fail to connect. To send sandbox traffic through an -> upstream corporate proxy, configure it on the host. See -> [Upstream proxy](../architecture.md#upstream-proxy). +Running an existing sandbox reuses its recorded kit configuration. Kit +selection applies when creating a sandbox. To use a different v3 workload or +mixin set, choose another name or recreate the sandbox with the desired kits. -### Control network access +## Author kits -Network rules define which domains the sandbox can reach or block. Kit -network rules apply only to sandboxes that use the kit: +When you author a kit, you work in a directory of source files. A typical kit +has a YAML file and a Dockerfile: -```yaml -permissions: - network: - allow: - - api.example.com - - "*.cdn.example.com" - deny: - - telemetry.example.com +```text +opencode-python/ +├── opencode-python.yaml +└── opencode-python.dockerfile ``` -Use `allow` for hosts the agent needs, such as package -registries, install endpoints, or external APIs. Use `deny` for -hosts the agent should not reach, such as telemetry endpoints. If a domain -matches both an allow rule and a deny rule, the deny rule wins. +The YAML file is the kit's descriptor. It identifies the kit as a workload or +mixin and declares what Docker Sandboxes should do when running it. The +Dockerfile defines the software and files to include and, for a workload, +the command to launch. -> [!IMPORTANT] -> When organization governance is active, only organization allow rules grant -> access, so kit-defined `allow` rules are ignored — including any domains a kit -> allows for the agent to reach. Kit-defined `deny` rules still apply, because a -> deny can only restrict access further. For details, see -> [Policy precedence](../governance/concepts.md#precedence). +Building this directory produces a container image containing the kit's files and +its descriptor. You can share that image through a container registry. During +development, `sbx` can build directly from the directory when you create a +sandbox. -For authenticated services, see -[Authenticate to external services](#authenticate-to-external-services). +### Build a workload -### Authenticate to external services +Suppose your team uses OpenCode to work on Python projects. Package it with +Ruff and instructions to check Python changes before handing work back to you. +Everyone using the kit gets the same linter version and review workflow. -A kit can attach credentials to outbound requests through the -host-side proxy. The agent inside the VM works with a sentinel value; -the proxy reads the real credential on the host and overwrites the -auth header before the request leaves the sandbox. +The Dockerfile starts from Docker's OpenCode [base image](base-images.md) and +installs Ruff: -A kit declares the service, the in-container environment variable, and how -to inject the credential. It doesn't declare a host discovery source. The user -provides the value through the secret store or first-run prompt, and a -[credential binding](../configuration/credentials.md) authorizes its use: +```dockerfile {title="opencode-python/opencode-python.dockerfile"} +FROM docker/sandbox-templates:opencode +USER agent +RUN uv tool install ruff==0.12.12 +ENTRYPOINT ["opencode"] +CMD [] +``` -```yaml -credentials: - - service: my-service - apiKey: - name: MY_SERVICE_API_KEY # in-VM env var, set to a sentinel - proxyManaged: true - inject: - - domain: api.example.com # inject on requests to this domain - header: Authorization # overwrite this header - format: "Bearer %s" - -permissions: - network: - allow: - - api.example.com # the domain must also be reachable +The template supplies OpenCode, Python, uv, and the `agent` user. Ruff is +installed during the build, so it is ready when the agent starts. + +The descriptor declares this as a workload, connects OpenCode to the +Anthropic API, and gives it the team's review instructions: + +```yaml {title="opencode-python/opencode-python.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: workload + +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: + - api.anthropic.com + - opencode.ai + - models.dev + - registry.npmjs.org + - pypi.org + - files.pythonhosted.org + - type: com.docker.runtime/credential@1 + config: + service: anthropic + phase: runtime + apiKey: + name: ANTHROPIC_API_KEY + proxyManaged: true + inject: + - domain: api.anthropic.com + header: x-api-key + format: "%s" + - type: com.docker.runtime/agent-context@1 + config: + filename: AGENTS.md + content: | + Ruff is installed. Run `ruff check` on Python files you change, + and fix any lint errors before reporting completion. + Follow the project's existing configuration and test commands. ``` -The agent boots with `MY_SERVICE_API_KEY=proxy-managed`, sends a -request with that sentinel in `Authorization`, and the proxy overwrites -the header with the real credential before forwarding. The real -secret never enters the VM. +The `capabilities` list describes what the sandbox provides at runtime: +network access, authentication, and instructions for the agent. The credential +entry names the service; you store the actual API key on your host. -See [Credentials](../configuration/credentials.md) for how to provide the -credential value on your host, other approaches for cases the example -above doesn't fit, and what the proxy does at request time. See -[Credential bindings](../configuration/credentials.md) to approve the mechanisms -and domains declared by a third-party v2 kit. +This example extends an existing agent environment. To prepare your own Linux +base image, install an agent, and configure its runtime needs step by step, +see [Build an agent](build-an-agent.md). -### Inject agent memory +## Capabilities -A kit can append content to the agent's memory file, such as `CLAUDE.md` -or `AGENTS.md`. The agent reads this file at startup. Use it to give -the agent project conventions, usage tips for a tool the kit installs, -or other guidance that should be in scope when the sandbox runs. +A capability declares a resource or behavior that a kit needs from Docker +Sandboxes at runtime, such as network access, credentials, lifecycle hooks, +or agent instructions. Declare these requests in the descriptor's +`capabilities` list. -```yaml -agentInstructions: - content: | - Ruff is installed. Run `ruff check` before committing. - Shared config lives at `/workspace/ruff.toml`. +Workloads and mixins use the same capability format. For example, a workload +can declare the network access its agent needs, and a mixin can request access +to an additional service. + +Each entry's `type` identifies the capability and its settings version, and +`config` contains those settings. For a complete descriptor using network, +credential, and instruction capabilities, see the +[OpenCode workload example](#build-a-workload). The +[capability reference](kit-reference.md#runtime-capabilities) lists the +available types, their fields, and Docker Sandboxes support. + +### Control network access + +Use the network-policy capability to declare which domains a sandbox can +reach. For example, this mixin permits requests to the GitHub API: + +```yaml {title="github-access/github-access.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin + +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: [api.github.com] ``` -Both mixin and sandbox kits can declare `agentInstructions.content`. The active -sandbox kit sets `agentInstructions.filename`, which determines the memory -file's name. The sandbox kit's content is written inline in that file. Each -mixin's content is written to its own `.md` file under a sibling -`kits-memory/` directory, and the main memory file gets a `## Kits` section that -points to each mixin file: +The type `com.docker.runtime/network-policy@1` selects version 1 of the +network-policy settings. `runtime.allow` lists domains the running sandbox +can reach. -```text -/Users/you/ -├── myproject/ # workspace -├── AGENTS.md # main memory file with a "## Kits" index -└── kits-memory/ - ├── ruff-lint.md - ├── vale.md - └── git-ssh-sign.md +This kit needs only its YAML file: it configures network access without adding +software or files to the image. Save the descriptor in `github-access` and +run it with a v3 workload, such as the +[OpenCode workload example](#build-a-workload): + +```console +$ sbx run ./opencode-python --name python-github --kit ./github-access ``` -See [`agentInstructions`](kit-reference.md#agent-instructions) in the spec -reference for the full field schema. +Network rules from the selected kits combine. A kit's `deny` entries take +precedence over kit allow entries, and the resulting rules participate in the +sandbox's [policy precedence](../governance/concepts.md#precedence). Use +`sbx policy log` to investigate refused connections. -### Define an agent +When organization governance is active, only organization allow rules grant +access. Kit allow rules don't grant additional access, but kit deny rules +still restrict it. -Sandbox kits declare a `sandbox:` block with the image the agent runs in and -the command the user attaches to when they launch the sandbox: +### Authenticate to external services -```yaml -sandbox: - image: "my-registry/my-agent:latest" - entrypoint: [my-agent, "--yolo"] +A credential capability names the service a kit needs and declares how to +authenticate to it. The [OpenCode workload example](#build-a-workload) +declares `service: anthropic`. Store its API key on the host using that +service name: + +```console +$ sbx secret set anthropic ``` -See [Sandbox kits](#sandbox-kits) for use cases and an example. +The example sets `proxyManaged: true`. OpenCode receives a placeholder in +`ANTHROPIC_API_KEY`, and the host proxy inserts the real key into requests to +`api.anthropic.com`. The key stays on the host. First-run approval authorizes +the kit to use it through that mechanism and on that domain. -## Mixin kits +See [Credentials](kit-reference.md#credentials) for a descriptor example and +API key and OAuth fields. [Credential configuration](../configuration/credentials.md) +covers host-side storage and approval, including preparation for unattended runs. -A mixin kit extends an existing agent with extra capabilities. Common use -cases: +### Give the agent instructions -- Pre-install tools: linters, libraries, or other custom programs -- Grant the agent access to a new authenticated service (a database, a - vendor API) -- Inject shared team config (linter rules, editor settings, dotfiles) +Installing a tool makes it available. Agent instructions tell the agent when +and how to use it, where to find shared configuration, and which checks to run +before reporting a task complete. -See [Drop a shared config file](kit-examples.md#drop-a-shared-config-file) and -[Install a tool at sandbox creation](kit-examples.md#install-a-tool-at-sandbox-creation) -for complete mixin examples. +A mixin can contribute instructions through the agent-context capability: -## Sandbox kits +```yaml +capabilities: + - type: com.docker.runtime/agent-context@1 + config: + content: | + Follow the project's contribution guide when changing code. + Run the project's lint and test commands before reporting completion. + If a check fails, include the failure in your response. +``` -A sandbox kit defines a full agent from scratch — image, entrypoint, and -everything the agent needs. Common use cases: +The workload chooses the profile filename, such as `AGENTS.md` or `CLAUDE.md`. +Docker Sandboxes generates this file in the parent directory of the mounted +workspace inside the sandbox. For example, a workspace at +`/home/agent/workspace` has its generated profile at `/home/agent/AGENTS.md`. +The profile sits outside the mount and doesn't replace instructions in your +project. + +Inline workload instructions, such as those in the +[OpenCode workload example](#build-a-workload), go directly into the profile. +Inline mixin instructions go into separate files under `kits-agent-context/` +beside the profile. The profile indexes those files for the agent to read +when needed. See +[Contribute agent instructions](kit-examples.md#contribute-agent-instructions). + +For kits with a build recipe, use `contentFile` to keep longer guidance in a +Markdown file beside the descriptor. The build packages that file in the +image, and the profile points to it instead of copying its text. Mixins +without a build recipe must use inline `content`. See +[Add agent instructions](build-an-agent.md#add-agent-instructions) for a +workload that uses `contentFile`. + +You can also package an agent skill: a reusable set of instructions for a +particular task, such as reviewing a Dockerfile. Skills are files installed +where that agent looks for them. See +[Ship a Claude Code skill](kit-examples.md#ship-a-claude-code-skill). + +## Build content and runtime setup + +Build tools and static content into the kit's image so sandboxes can reuse +them. Reserve runtime setup for work that needs an individual sandbox's +state, such as its mounted workspace path or a host-provided credential. + +Use lifecycle hooks for that work. A hook is a command Docker Sandboxes runs +at a particular point in the sandbox's life. Install hooks initialize each +sandbox during creation; startup hooks run each time it starts. The lifecycle +capability can also write configuration files during creation. + +| Mechanism | When it runs | Use it for | +| --- | --- | --- | +| Dockerfile `RUN` and `COPY` | Kit build | Install tools, compile binaries, and package static content | +| Lifecycle `install` hooks | Once per sandbox, during creation | Initialize state that needs runtime credentials or mounted paths | +| Lifecycle `startup` hooks | Each sandbox start | Start a service or refresh state after a restart | +| Lifecycle `files` | Sandbox creation | Write configuration for an individual sandbox | + +Build results are reusable across sandboxes. Install hooks run for each +sandbox you create. Put software installation and compilation in the build +when they don't require sandbox-specific inputs. + +### Lifecycle hooks + +Hooks are declared through `com.docker.runtime/lifecycle@1`. For example, write +a default config and start a service that the kit's image already contains: -- Package a custom agent you've built so others can run it -- Ship a team-internal agent with defaults baked in -- Run a fork of an existing agent with your own config -- Prototype a new agent integration +```yaml +capabilities: + - type: com.docker.runtime/lifecycle@1 + config: + files: + - path: /home/agent/.config/my-service/config.json + content: '{"port": 8080}' + overwrite: false + startup: + - command: [my-service, --config, /home/agent/.config/my-service/config.json] + user: "1000" + background: true +``` -Sandbox kits declare everything a mixin kit can, plus an -[`sandbox:` block](kit-reference.md#sandbox-block) that tells the sandbox how to launch the -agent. For a step-by-step walkthrough, see -[Build your own agent kit](build-an-agent.md). +Startup hooks must tolerate repeated execution. In Docker Sandboxes they run +through a background dispatcher and don't block the agent's launch. Use an +install hook or a workload entrypoint script for setup the agent must wait for. +String commands run through `sh -c`; an argument list invokes the command +directly. See [Lifecycle](kit-reference.md#lifecycle) for command fields, +file permissions, and runtime behavior. -### Extend a built-in agent +### Static files -Use `extends:` to create a variant of a built-in agent without reproducing its -configuration. The child kit inherits the parent's image, credentials, network -permissions, persistent volumes, settings, MCP integration, and agent -instructions. It also inherits the parent's environment variables and all -`setup.install`, `setup.startup`, and `setup.files` entries. Parent setup entries -run before child entries. If both kits set the same environment variable, the -child's value wins. Use `extends:` for a single parent agent; use a mixin to add -an independent capability that can work with one or more agents. See -[Fork an existing agent](kit-examples.md#fork-an-existing-agent) for an example -that changes Claude Code's permission mode. +Static content belongs in the image. Use Dockerfile `COPY` to put a tool config, +helper script, or reference document at its destination, or stage it at a +kit-specific path for a lifecycle hook to copy later. -## Using kits +Files destined for a mounted workspace or persistent volume need that second +step: a mount can hide files baked into the image at its mount path. Copy from +the staged image path after the mount is available. See +[Copy shared configuration](kit-examples.md#copy-shared-configuration). -Kits can be loaded from a local path (a directory or ZIP file), a Git -repository, or an OCI registry. To launch a sandbox kit, pass its reference in -place of a built-in agent name to `sbx run` or `sbx create`. Use `--kit` for -mixins, and repeat the flag to apply multiple mixins to the same sandbox. +### Customize agent settings -Starting with Docker Sandboxes version 0.42.0, pass the sandbox kit reference -as the first argument: +When an agent supports additional settings files, keep team defaults in a +separate file and point the agent to it. For example, Claude Code has a +`--settings` launch option, and OpenCode reads the path in `OPENCODE_CONFIG`. -```console -$ sbx run [PATH...] -$ sbx create [PATH...] -``` +Package fixed settings with Dockerfile `COPY`. Use lifecycle `files` for +settings that contain kit argument values chosen when creating the sandbox. +The workload controls its launch options; a mixin's `ENTRYPOINT` doesn't +change how the agent starts. See +[Write the model settings](build-an-agent.md#write-the-model-settings) for a +complete example. -The previous form, `sbx run --kit `, is -deprecated. +### Set environment variables -> [!IMPORTANT] -> A mixin passed with `--kit` only takes effect when a sandbox is created. -> Passing it against an -> existing sandbox name fails with -> `--kit can only be used when creating a new sandbox`. To add a supported -> mixin kit to a running sandbox, use [`sbx kit add`](#local) instead. -> `sbx kit add` restarts the sandbox to apply the updated kit set. -> VM state — installed packages, Docker images, volumes, and agent history -> — is preserved across the restart. It supports mixin kits limited to -> `environment.variables`, `setup.install`, and `permissions.network.allow`. -> To use other fields, recreate the sandbox with the mixin. +Use Dockerfile `ENV` for tool settings that apply to every sandbox using the +kit. For example, make Python write output without buffering: -### Pass arguments to kits +```dockerfile +ENV PYTHONUNBUFFERED=1 +``` -A schema v2 kit can declare inputs in a top-level `args:` block and reference -them in `spec.yaml` or static files with `${{ kit.args. }}`. Supply a -value with `--kit-arg name=value`: +For values the user chooses when creating a sandbox, declare a kit argument +with an `env` mapping. See [Pass arguments to kits](#pass-arguments-to-kits). +Use credential capabilities for secrets. -```console -$ sbx run ./my-agent/ --kit-arg channel=beta -``` +Docker Sandboxes sets `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, and their +lowercase equivalents to route traffic through its policy and credential +proxy. Leave those variables to the sandbox. Configure a corporate proxy on +the host using [Upstream proxy](../architecture.md#upstream-proxy). -Kit argument values are plain text. Values supplied with `--kit-arg` can remain -in your shell history, and argument files store their values unencrypted. Don't -use kit arguments for secrets. Use [Credentials](../configuration/credentials.md) -instead. +Some tools need a shell initialization script, such as a version manager's +`init.sh`. With Docker sandbox templates, append the initialization commands +to `/etc/sandbox-persistent.sh` in a lifecycle install hook. The templates +source this file for interactive and non-interactive Bash commands. Append +to it so other kits' settings remain, and keep shell completion scripts out +of it because they can fail in non-interactive shells. -An argument without a kit name prefix applies to every kit that declares it. -To target one kit, prefix the argument with the value of that kit's `name` -field and a period: +## Compose kits -```console -$ sbx run ./my-agent/ \ - --kit ./my-mixin/ \ - --kit-arg version=1.2.3 \ - --kit-arg my-mixin.version=2.0.0 -``` +Composition combines one workload kit with its mixins. Select the kits +when [creating a sandbox](#add-mixins). A kit's descriptor can also declare +relationships with other kits, such as a dependency on a tool they supply. -The kit-specific value takes precedence over the shared value for `my-mixin`. +Every selected kit must use v3. A v3 mixin can't extend a v2 built-in agent, +and a v2 mixin can't extend a v3 workload. Choose the workload and compatible +mixins together when creating the sandbox. -Use `--kit-args-file` for a reusable set of `name=value` entries. Blank lines -and lines that start with `#` are ignored: +For example, a tool kit can advertise what it supplies: -```text {title="kit.args"} -version=1.2.3 -my-mixin.channel=beta +```yaml +provides: ["my-tool@1.0.0"] ``` -```console -$ sbx create ./my-agent/ . \ - --kit ./my-mixin/ \ - --kit-args-file ./kit.args \ - --kit-arg my-mixin.channel=stable +A mixin that needs that tool can declare a requirement: + +```yaml +requires: ["my-tool >= 1.0.0"] ``` -When you pass multiple argument files, a value in a later file overrides the -same key in an earlier file. Values passed with `--kit-arg` override every -file. For repeated `--kit-arg` entries with the same key, the last value wins. +Include both kits when creating the sandbox. Docker Sandboxes checks that +the selected set satisfies the requirement; it doesn't search a registry or +install a package to satisfy it. Kit authors declare these names explicitly. +Installing a tool in a Dockerfile doesn't automatically add a `provides` entry. -Argument validation happens before the sandbox is created. `sbx` rejects a -missing required value, a value outside its declared `enum` or `pattern`, a -placeholder without a declaration, and a supplied argument that no resolved -kit declares. Pass the same argument flags to `sbx kit validate` or -`sbx kit inspect` when the kit requires values. See -[Kit arguments](kit-reference.md#arguments) for the declaration fields. +Providers are applied before the kits that require them. Independent mixins +are ordered by reference, so reordering `--kit` flags isn't an override +mechanism. Use `integrates` for a relationship that applies only when another +kit is present, and `conflicts` to reject an incompatible combination. See +[Composition fields](kit-reference.md#composition-fields) for these rules. -### Local +### Avoid conflicting customizations -Launch a local sandbox kit by passing its directory or ZIP file in place of the -agent name. Relative paths must start with `./` or `../` so `sbx` can -distinguish them from agent and sandbox names: +The workload supplies the environment and launch command. Mixins add files +and runtime declarations. Two kits contributing the same image file cause a +composition error. Conflicting image environment values also cause an error, +while `PATH` additions are combined. Give each kit its own paths for staged +content and avoid having multiple kits manage the same config. -```console -$ sbx run ./my-agent/ -$ sbx create ../my-agent-1.0.zip . -``` +To derive a workload, build its Dockerfile from the base image you want and +declare its runtime capabilities in its descriptor. Dockerfile `FROM` inherits +image content and config, but doesn't merge a parent kit's descriptor. V3 has +no `extends` field. To add to a workload without deriving another workload, +use a mixin. -Pass a local mixin with `--kit`: +Select all kits when creating the sandbox. To change a v3 kit set, recreate the +sandbox with the desired workload and mixins. `sbx kit add` doesn't apply v3 +changes to an existing sandbox. -```console -$ sbx run claude --kit ./my-mixin/ -$ sbx run claude --kit ../my-mixin-1.0.zip -``` +## Pass arguments to kits -While iterating on a supported mixin kit, apply changes to a running sandbox -with `sbx kit add`: +Kit arguments let users choose values without editing the kit's source. For +example, a tool kit can offer a mode that becomes an environment variable in +the sandbox: -```console -$ sbx kit add my-sandbox ./my-kit/ +```yaml +args: + mode: + default: check + enum: [check, fix] + env: TOOL_MODE ``` -`sbx kit add` restarts the sandbox to apply the updated kit set. -VM state — installed packages, Docker images, volumes, and agent history — is -preserved across the restart. Kits can't be removed from a running sandbox — -remove and recreate it to start clean. +```console +$ sbx run ./my-agent --kit ./my-tool --kit-arg mode=fix . +``` -### Git repository +`args.mode` declares the input, its default, and its accepted values. The `env` +field exports the chosen value as `TOOL_MODE`. You can also reference the value +in the descriptor as `${{ kit.args.mode }}`, for example in the content of a +configuration file. Without `env`, the value is available only through that +substitution. -Launch a sandbox kit from a Git repository: +A bare argument name applies to every kit that declares it. To target one kit, +prefix the name with its handle and a period: ```console -$ sbx run "git+https://github.com/docker/sbx-kits-contrib.git#ref=v0.1.0&dir=amp" +$ sbx run ./my-agent --kit ./my-tool --kit-arg my-tool.mode=fix . ``` -Pass a Git-hosted mixin with `--kit`: +The handle is the local directory name, the Git subdirectory or repository +name, or the last repository segment in an OCI reference. Scoped values take +precedence over shared values. Use `--kit-args-file ` for reusable +`name=value` entries; `--kit-arg` values take precedence over file values. -```console -$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#ref=v0.1.0&dir=code-server" -``` +Arguments can also select build-time inputs, such as a tool version. Declare +those with `buildArg` and supply them to `docker buildx build --build-arg` +using the kit argument's name. These values are resolved into the published +kit, so changing them requires rebuilding. For an example, see +[Build a tool overlay](kit-examples.md#build-a-tool-overlay). -- `#ref=` pins to a specific revision. Defaults to the - repository's default branch. -- `#dir=` loads a kit from a subdirectory. -- `git+ssh://` URLs also work, using your local SSH agent, Git credential - helpers, and `.netrc`. -- Quote the URL in shells where `&` starts a background job. +Argument values are plain text and can be recorded in shell history and +sandbox state. Use credential capabilities for secrets. See +[Arguments](kit-reference.md#arguments) for validation and mapping fields. -### OCI registry +## Directory and build layout -Launch a sandbox kit from an OCI registry: +Keep a kit's descriptor, Dockerfile, and supporting files in one source +directory. Use matching filename stems for the YAML descriptor and its +companion Dockerfile so the build can discover the recipe. For a kit with +instructions and a configuration file, the layout could be: -```console -$ sbx run docker.io/sbx/droid-kit:latest +```text +my-kit/ +├── my-kit.yaml +├── my-kit.dockerfile +├── context.md +└── files/ + └── settings.json ``` -Pass an OCI-hosted mixin with `--kit`: +The descriptor's first line, `# syntax=docker/runtime-kit:3`, selects the kit +BuildKit frontend. Pass the YAML file to `docker buildx build -f`, with its +directory as the build context. The frontend finds the same-stem +`my-kit.dockerfile`, builds the content, validates the declarations, and +publishes them together. + +For `sbx` to discover a local source kit, keep exactly one v3 descriptor at the +directory root, named with the `.yaml` extension. A `.dockerfile` with a +comment descriptor also works. Avoid `spec.yaml` and `spec.yml`: these names +select the v1/v2 loader. Matching the descriptor stem to the directory name +also keeps build and create argument scopes consistent. + +A workload needs a Dockerfile to supply its environment and launch command. +A mixin needs one when it adds image content. A mixin that only declares +runtime behavior, such as the [GitHub network mixin](#control-network-access), +can omit it. + +For a single-file kit, use `build: |` with literal Dockerfile text in the +YAML. You can also select a differently named recipe with `dockerfile:` or +embed a descriptor in a Dockerfile comment block. See +[Authoring forms](kit-reference.md#authoring-forms) for syntax and discovery +rules. The `files/` name in this example is an authoring convention, not a +special runtime directory. + +### Build a mixin + +A mixin contributes an overlay: files added or changed by its recipe. The +recipe's base image is a build environment, and its unchanged filesystem +doesn't become part of the overlay. For a tool built in a separate stage, +use a final `FROM scratch` stage and copy the tool and everything it needs +into that stage. + +A copied binary must be compatible with the workload's architecture and +libraries. Ship dependencies that the workload doesn't supply, and declare +any requirements on other kits as described in [Compose kits](#compose-kits). +See [Build a tool overlay](kit-examples.md#build-a-tool-overlay). + +A mixin's environment additions become part of the composed image, but its +`ENTRYPOINT`, `CMD`, `USER`, and `WORKDIR` don't replace the workload's launch +configuration. -```console -$ sbx run claude --kit ghcr.io/myorg/my-kit:1.0 -``` +## Packaging and distribution -For Docker Hub, include the full `docker.io` prefix. See -[Packaging and distribution](#packaging-and-distribution) for publishing. - -> [!IMPORTANT] -> For Docker Hub, `sbx` reuses your `sbx login` session to pull private -> kits. For other registries, store pull credentials with -> [`sbx secret set --registry`](../configuration/credentials.md#registry-credentials) -> before running the sandbox. These credentials take priority over credentials -> in the Docker credential store: -> -> ```console -> $ gh auth token | sbx secret set --registry ghcr.io --password-stdin -> ``` -> -> Without credentials from either store, pulls from non-Docker Hub registries -> are anonymous and private kits fail to pull. +Share kits as published images in a container registry or as source files in +Git. Consumers can [run a kit](#run-a-kit) using either type of reference. -### Restrict kit sources +### Publish an image -`sbx` restricts which sources a kit can install from. A kit's install -commands run with root privileges inside the sandbox, so limiting where kits -come from reduces supply-chain risk. By default, only kits hosted on Docker -Hub (`docker.io/`) are allowed. Loading a kit from any other source fails: +A published v3 kit is an OCI image. Use Docker Buildx to build and push it, +passing the descriptor with `-f` and the source directory as the build context: ```console -$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=vale" -ERROR: resolve kits: kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=vale" cannot be installed — its source is not in your allowlist. +$ docker login +$ docker buildx build ./my-kit -f ./my-kit/my-kit.yaml \ + -t docker.io//my-kit:1.0.0 --push ``` -To allow another publisher, add its host or host/path prefix to the -`kit.allowedSources` setting. The setting replaces the whole list, so include -the entries you want to keep: +Replace `` with a Docker Hub namespace you can push to. Buildx +uses your `docker login` credentials. Include `docker.io/` explicitly in +Docker Hub references. For pulling from private registries in a sandbox, +configure [Registry credentials](../configuration/credentials.md#registry-credentials). + +Build for both supported Linux architectures when distributing across machines: ```console -$ sbx settings set kit.allowedSources '["docker.io/","github.com/docker/"]' +$ docker buildx build ./my-kit -f ./my-kit/my-kit.yaml \ + --platform linux/amd64,linux/arm64 \ + -t docker.io//my-kit:1.0.0 --push ``` -Entries match as prefixes on a path-segment boundary, so `github.com/docker/` -allows `github.com/docker/sbx-kits-contrib` but not `github.com/docker-evil/kit`. -To remove the restriction and allow any remote source, set the list to -`["*"]`. This isn't recommended. +An image present only in the host Docker image store isn't available to the +sandbox runtime by registry reference. Push it to a registry, or pass a local +source directory to `sbx` for development. The `sbx kit pack`, `push`, and +`pull` packaging commands are for v1 and v2 kits. V3 uses the Buildx workflow +shown here. -Installing from a local directory or ZIP file is governed separately by the -`kit.allowLocalKits` setting, which defaults to `true`. Set it to `false` to -require a remote source: +### Share source through Git -```console -$ sbx settings set kit.allowLocalKits false +Commit the kit's source directory to a Git repository. Share a reference that +identifies the kit directory with `dir` and pins a revision with `ref`: + +```text +git+https://github.com//.git#ref=&dir=my-kit ``` -For non-interactive use, both settings have environment-variable equivalents: -`DOCKER_SANDBOXES_KIT_ALLOWED_SOURCES` and `DOCKER_SANDBOXES_KIT_ALLOW_LOCAL`. +Docker Sandboxes builds the source when a consumer creates a sandbox from +that reference. -## Sign and verify kits +### Restrict kit sources -Use cosign-compatible Sigstore signatures to verify who approved a kit and -that its signed content hasn't changed. Signing is keyless by default. Verify a -keyless signature with the certificate identity and OpenID Connect (OIDC) -issuer: +`kit.allowedSources` controls permitted remote kit sources. Its default permits +Docker Hub. To include a Git publisher, set the complete list of prefixes you +want to permit: ```console -$ sbx kit sign ./my-kit/ -$ sbx kit verify \ - --certificate-identity user@example.com \ - --certificate-oidc-issuer https://accounts.google.com \ - ./my-kit/ +$ sbx settings set kit.allowedSources '["docker.io/","github.com/docker/"]' ``` -For key-based signing, use an ECDSA P-256 key pair: +Prefixes match at path-segment boundaries. Local source directories are +controlled separately by `kit.allowLocalKits`, which defaults to `true`: ```console -$ sbx kit sign --key cosign.key ./my-kit/ -$ sbx kit verify --key cosign.pub ./my-kit/ +$ sbx settings set kit.allowLocalKits false ``` -For a local directory, `sbx kit sign` writes a `kit.sig.bundle` file next to -`spec.yaml`. Commit this file so consumers can verify a kit loaded from the Git -repository. For an OCI kit, the signature is stored as an OCI referrer. You can -sign an OCI kit after pushing it, or push and sign it in one step: +For non-interactive configuration, use `DOCKER_SANDBOXES_KIT_ALLOWED_SOURCES` +and `DOCKER_SANDBOXES_KIT_ALLOW_LOCAL`. + +### Sign and verify kits + +Sign the OCI image after pushing it: ```console -$ sbx kit push ./my-kit/ ghcr.io/myorg/my-kit:1.0 --sign +$ sbx kit sign docker.io//my-kit:1.0.0 +$ sbx kit verify docker.io//my-kit:1.0.0 \ + --certificate-identity \ + --certificate-oidc-issuer ``` -ZIP kits can't carry verifiable signatures. - -### Require signed kits +These commands use Cosign-compatible Sigstore signatures. For keyless signing, +verification must specify the signer's certificate identity and OpenID Connect +issuer. For key-based signing, pass `--key cosign.key` to `sign` and +`--key cosign.pub` to `verify`. -Set a trusted signer policy for the identities or keys you trust before -requiring signatures. Otherwise, `sbx` uses the default policy, which trusts -Docker employee identities attested by Google's OpenID Connect issuer. A -keyless policy must specify both the certificate identity and its OpenID -Connect issuer: +To require trusted signatures when loading kits, configure the trusted signer +policy, then turn on the requirement: ```console $ sbx settings set kit.trustedSigners \ @@ -560,71 +695,43 @@ $ sbx settings set kit.trustedSigners \ $ sbx settings set kit.requireSignature true ``` -To trust a key-based signature, set the policy to the public key path: +V3 source directories and Git sources don't support the source-signing +workflow. Publish and sign an OCI image when signatures are required. + +## Published format + +A published kit image contains both the kit's content and its descriptor. +Docker image tools can inspect and distribute it, and Docker Sandboxes reads +the descriptor when creating the sandbox. + +The built kit also includes its descriptor under +`/usr/share/runtime/kit//`, so you can inspect it inside the sandbox. +For the image annotations and file layout, see +[Published image format](kit-reference.md#published-image-format). + +Running a workload with `docker run` uses its image configuration, but doesn't +apply the kit's capability declarations or lifecycle hooks. Use `sbx` to run +it with those behaviors. + +## Debug kits + +When a tool is missing or a request fails, inspect the running sandbox: ```console -$ sbx settings set kit.trustedSigners '[{"key":"/path/to/cosign.pub"}]' -$ sbx settings set kit.requireSignature true +$ sbx exec -- which +$ sbx exec -- cat /home/agent/.config//settings.json +$ sbx policy log ``` -When `kit.requireSignature` is `true`, `sbx` rejects unsigned kits, signatures -that don't match `kit.trustedSigners`, and ZIP kits. This policy applies when a -kit is loaded from a local directory, Git repository, or OCI registry. - -The signature covers `spec.yaml` and the kit's `files/` content, but not mutable -dependencies such as image tags or content downloaded by install and startup -commands. Pin those dependencies by digest or checksum when they must remain -immutable. +The policy log shows outbound requests and the rules they matched. Use it to +find blocked package registries or API hosts. If downloads fail after adding +credential injection, check that the injection rule targets only the service +hosts that need the credential. -## Packaging and distribution +For a background service, redirect its startup command's output to a file +inside the sandbox, then read that file with `sbx exec`. Set +`background: true` on the hook rather than adding `&` to the shell command. +See [Run a hook on every start](kit-examples.md#run-a-hook-on-every-start). -The `sbx kit` subcommands validate, inspect, and publish kits: - -- `sbx kit validate ` — check that a kit directory or ZIP is - well-formed. -- `sbx kit inspect ` — display kit details. Add `--json` for - machine-readable output. -- `sbx kit pack -o ` — package a directory as a ZIP file - for sharing. -- `sbx kit push ` — publish to an OCI registry (for example, - `ghcr.io/myorg/my-kit:1.0`). -- `sbx kit pull ` — download a kit from a registry as a ZIP file to - the working directory. - -For Docker Hub, include the full `docker.io` prefix — `sbx` doesn't add it -automatically. - -For Docker Hub, `sbx kit pull` and `sbx kit push` use the session from -`sbx login`. For other registries, they prefer credentials stored with -[`sbx secret set --registry`](../configuration/credentials.md#registry-credentials). -Both commands fall back to the Docker credential store, so credentials from -`docker login` also work. - -## Spec reference - -For a field-by-field reference of every `spec.yaml` block — top-level -fields, arguments, credentials, network, environment, setup, static files, -agent instructions, and the sandbox block — see [Kit spec reference](kit-reference.md). - -## Debugging - -When a kit doesn't behave as expected, start with the network policy log -and direct inspection inside the sandbox: - -- `sbx policy log` shows every outbound request the sandbox proxy saw, - the rule it matched, extra context when available, and its `PROXY` - value, such as `forward`, `forward-bypass`, `transparent`, or - `browser-open`. Use it to diagnose install-time download failures, - blocked domains, and unexpected TLS interception. If downloads fail or - arrive corrupted after you add a credential's `apiKey.inject`, check - whether an injection domain is too broad. Inject only on the hosts that - need credentials. -- `sbx exec -- ` runs an arbitrary command inside an - existing sandbox. Useful for inspecting post-install state without - recreating: `which mytool`, `ls /home/agent/.local/bin/`, - `cat /home/agent/.config/...`, and so on. - -Install and startup command output is only emitted during `sbx run` or -`sbx create`; `sbx` doesn't retain it for later inspection. To repeat -setup with fresh output, remove and recreate the sandbox: -`sbx rm && sbx run ...`. +To test changes to a v3 kit, create a sandbox with a different name. Reusing +an existing sandbox keeps its recorded kit configuration. diff --git a/content/manuals/ai/sandboxes/customize/templates.md b/content/manuals/ai/sandboxes/customize/templates.md deleted file mode 100644 index 48ac92cdd56..00000000000 --- a/content/manuals/ai/sandboxes/customize/templates.md +++ /dev/null @@ -1,257 +0,0 @@ ---- -title: Templates -weight: 10 -description: Build reusable sandbox images with tools and configuration baked in, or save a running sandbox as a template. -keywords: sandboxes, sbx, templates, images, dockerfile, snapshot, custom environments ---- - -{{< summary-bar feature_name="Docker Sandboxes sbx" >}} - -Every sandbox is customizable — agents install packages, pull images, and -configure tools as they work, and those changes persist for the sandbox's -lifetime. Templates capture a configured environment into a reusable image -so you don't have to set it up again every time. - -## Custom templates - -Custom templates are reusable sandbox images that extend one of the built-in -agent environments with additional tools and configuration baked in. Instead -of asking the agent to install packages every time, build a template once and -reuse it across sandboxes and team members. - -Templates make sense when multiple people need the same environment, when -setup involves steps that are tedious to repeat, or when you need pinned -versions of specific tools. For one-off work, the default image is fine — -ask the agent to install what's needed. - -> [!NOTE] -> Custom templates customize an existing agent's environment — they don't -> create new agent runtimes. The agent that launches inside the sandbox is -> determined by the base image variant you extend and the agent you specify -> in the `sbx run` command, not by binaries installed in the template. To -> define a new agent from scratch, see [Kits](kits.md#define-an-agent). - -### Base images - -All sandbox templates are published as -`docker/sandbox-templates:`. They are based on Ubuntu and run as a -non-root `agent` user with sudo access. Most variants include Git, Docker -CLI, and common development tools like Node.js, Python, Go, and Java. - -| Variant | Agent | -| --------------------- | -------------------------------------------------------------------- | -| `claude-code` | [Claude Code](https://claude.ai/download) | -| `claude-code-minimal` | Claude Code with a minimal toolset (no Node.js, Python, Go, or Java) | -| `codex` | [OpenAI Codex](https://github.com/openai/codex) | -| `copilot` | [GitHub Copilot](https://github.com/github/copilot-cli) | -| `cursor-agent` | [Cursor](https://cursor.com/cli) | -| `devin` | [Devin CLI](https://docs.devin.ai/work-with-devin/devin-cli) | -| `docker-agent` | [Docker Agent](https://github.com/docker/docker-agent) | -| `droid` | [Droid](https://www.factory.ai) | -| `gemini` | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | -| `kiro` | [Kiro](https://kiro.dev) | -| `opencode` | [OpenCode](https://opencode.ai) | -| `shell` | No agent pre-installed. Use for manual agent setup. | - -Each variant also has a `-docker` version (for example, `claude-code-docker`) -that includes a full Docker Engine running inside the sandbox — no local Docker -daemon required. When you pick a built-in agent without specifying a custom -template, `sbx run` and `sbx create` use the `-docker` template variants by -default. - -The agent containers created from the `-docker` templates run in privileged -mode inside the microVM (not on your host), with a dedicated block volume at -`/var/lib/docker`, and `dockerd` starts automatically inside the sandbox. The -block volume defaults to 10 GB and uses a sparse file, so it only consumes -disk space as Docker writes to it. - -To change the volume size for a sandbox, set -`DOCKER_SANDBOXES_DOCKER_SIZE` when you create it: - -```console -$ DOCKER_SANDBOXES_DOCKER_SIZE=20g sbx run claude -``` - -The volume size must be at least 512 MiB. The environment variable doesn't -resize existing volumes. - -Use the non-Docker variant if you don't need to build or run containers -inside the sandbox and want a lighter, non-privileged environment. Specify -it explicitly with `--template`: - -```console -$ sbx run claude --template docker.io/docker/sandbox-templates:claude-code -``` - -### Build a custom template - -Building a custom template requires -[Docker Desktop](/manuals/desktop/_index.md). - -Write a Dockerfile that extends one of the base images. Pick the variant -that matches the agent you plan to run. For example, extend `claude-code` -to customize a Claude Code environment, or `codex` to customize an OpenAI -Codex environment. - -The following example creates a Claude Code template with Rust and -protocol buffer tools pre-installed: - -```dockerfile -FROM docker/sandbox-templates:claude-code -USER root -RUN apt-get update && apt-get install -y protobuf-compiler -USER agent -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -``` - -Use `root` for system-level package installations (`apt-get`), and switch -back to `agent` before installing user-level tools. Tools that install into -the home directory, such as `rustup`, `nvm`, or `pyenv`, must run as -`agent` — otherwise they install under `/root/` and aren't available in -the sandbox. - -Build the image and push it to an OCI registry, such as Docker Hub: - -```console -$ docker build -t my-org/my-template:v1 --push . -``` - -> [!NOTE] -> The Docker daemon used by Docker Sandboxes pulls templates from a -> registry directly; it doesn't share the image store of your local Docker -> daemon on the host. To route Docker Hub image pulls through your -> organization's registry infrastructure, configure a -> [registry mirror](../configuration/registry-mirror.md). - -> [!IMPORTANT] -> For Docker Hub, `sbx` reuses your `sbx login` session to pull private -> images. For other registries (GitHub Container Registry, ECR, ACR, a -> self-hosted Nexus, and so on), store pull credentials with -> [`sbx secret set --registry`](../configuration/credentials.md#registry-credentials) -> before running the sandbox: -> -> ```console -> $ gh auth token | sbx secret set --registry ghcr.io --password-stdin -> ``` -> -> Without stored credentials, pulls from non-Docker Hub registries are -> anonymous and private images fail to pull. - -For locally-built images, save the image to a tar and load it directly -into the sandbox runtime instead of pulling from a registry: - -```console -$ docker image save my-org/my-template:v1 -o my-template.tar -$ sbx template load my-template.tar -$ sbx run --template my-org/my-template:v1 claude -``` - -`sbx template load` imports the tar into the sandbox runtime's image -store, so the image doesn't need to be reachable from a registry at -sandbox creation time. - -Unless you use the permissive `allow-all` network policy, you may also need -to allow-list any domains that your custom tools depend on: - -```console -$ sbx policy allow network "*.example.com:443,example.com:443" -``` - -Then run a sandbox with your template. The agent you specify must match -the base image variant your template extends: - -```console -$ sbx run --template docker.io/my-org/my-template:v1 claude -``` - -Because this template extends the `claude-code` base image, you run it -with `claude`. If you extend `codex`, use `codex`; if you extend `shell`, -use `shell` (which drops you into a Bash shell with no agent). - -> [!NOTE] -> Unlike Docker commands, `sbx` does not automatically resolve the Docker -> Hub domain (`docker.io`) in image references. - -### Template caching - -Template images are cached locally. The first use pulls from the registry; -subsequent sandboxes reuse the cache. Cached images persist across sandbox -creation and deletion, and are cleared when you run `sbx reset`. - -## Saving a sandbox as a template - -Instead of writing a Dockerfile, you can save a running sandbox's state as -a template. This captures installed packages, configuration changes, and -files into a reusable image — useful when you've set up an environment -interactively and want to preserve it. - -> [!WARNING] -> Saving a sandbox captures its entire filesystem, including any secrets -> stored on it. If you manually added API keys, tokens, or other -> credentials to the sandbox, they're embedded in the saved template and -> shared with anyone you distribute it to. To keep credentials out of -> templates, manage them with `sbx secret set` instead — the proxy injects -> them at runtime so they're never written to the filesystem. For more -> information, see [Manage credentials](../configuration/credentials.md). - -### Save and reuse - -Stop the sandbox (or let the CLI prompt you), then save it with a name and -tag: - -```console -$ sbx template save my-sandbox my-template:v1 -``` - -The image is stored in the sandbox runtime's local image store. Create a -new sandbox from it with the `-t` flag: - -```console -$ sbx run -t my-template:v1 claude -``` - -### List and remove templates - -List all saved templates: - -```console -$ sbx template ls -``` - -Remove a template you no longer need: - -```console -$ sbx template rm my-template:v1 -``` - -### Export and import - -To share a saved template or move it to another machine, export it as a -tar file: - -```console -$ sbx template save my-sandbox my-template:v1 --output my-template.tar -``` - -On the other machine, load the tar file and use it: - -```console -$ sbx template load my-template.tar -$ sbx run -t my-template:v1 claude -``` - -### Limitations - -Agent configuration files are always recreated when a sandbox is created. -Changes to user-level agent configuration files, such as -`/home/agent/.claude/settings.json` and `/home/agent/.claude.json`, do not -persist in saved templates. - -If the saved template was built for a different agent than the one you -specify in `sbx run`, you get a warning. For example, saving a Claude -sandbox and running it with `codex` produces: - -```text -⚠ WARNING: template "my-template:v1" was built for the "claude" agent but you are using "codex". - The sandbox may not work correctly. Consider using: sbx run -t my-template:v1 claude -``` diff --git a/content/manuals/ai/sandboxes/faq.md b/content/manuals/ai/sandboxes/faq.md index a451158c5c6..49036afda1b 100644 --- a/content/manuals/ai/sandboxes/faq.md +++ b/content/manuals/ai/sandboxes/faq.md @@ -114,23 +114,13 @@ inside the session. Most agents let you switch permission modes after startup. In Claude Code, use the `/permissions` command to change the mode interactively. -To make approval prompts the default for every session, define a custom -sandbox kit that overrides the agent's entrypoint to drop the -permission-skipping flag. For example, a kit that launches Claude Code -without `--dangerously-skip-permissions`: - -```yaml {title="claude-safe/spec.yaml"} -schemaVersion: "1" -kind: sandbox -name: claude-safe -sandbox: - image: "docker/sandbox-templates:claude-code-docker" - entrypoint: - run: [claude] -``` +To make approval prompts the default for every session, create a v2 sandbox +kit that extends the built-in agent and changes its launch options. See +[Fork an existing agent](customize/kits-v2/_index.md#fork-an-existing-agent) +for a complete example. -Run it with `sbx run ./claude-safe/`. See -[Sandbox kits](customize/kits.md#sandbox-kits) for the full pattern. +For an environment built entirely with v3 kits, set the launch command in +the workload's Dockerfile. See [Build a v3 agent kit](customize/build-an-agent.md). ## How do I know if my agent is running in a sandbox? diff --git a/content/manuals/ai/sandboxes/integrations/t3-code.md b/content/manuals/ai/sandboxes/integrations/t3-code.md index fe7ddffb575..30b71b813b7 100644 --- a/content/manuals/ai/sandboxes/integrations/t3-code.md +++ b/content/manuals/ai/sandboxes/integrations/t3-code.md @@ -49,8 +49,8 @@ $ sbx exec -- sh -lc 'command -v g++ && command -v make && command -v A manual install lasts only until the sandbox is recreated, and the first connection still builds `node-pty` from source. For a setup that persists, -recreate the sandbox with the [kit](../customize/kits.md) or a custom -[template](../customize/templates.md). +recreate the sandbox with the [v2 kit](../customize/kits-v2/_index.md) or a custom +[template](../customize/base-images.md). ## Connect diff --git a/content/manuals/ai/sandboxes/release-notes.md b/content/manuals/ai/sandboxes/release-notes.md index 960be918218..1795b9c979f 100644 --- a/content/manuals/ai/sandboxes/release-notes.md +++ b/content/manuals/ai/sandboxes/release-notes.md @@ -198,7 +198,7 @@ for ASCII-only content. ### Highlights -**Kit spec v2.** A new schema is available for authoring kits, with a clearer structure for setup, permissions, agent instructions, networking, and credentials. Use `schemaVersion: "2"` for new kits; existing v1 kits continue to load through the legacy path. See the [kit spec reference](https://docs.docker.com/ai/sandboxes/customize/kit-reference/#schema-versions) for migration details. +**Kit spec v2.** A new schema is available for authoring kits, with a clearer structure for setup, permissions, agent instructions, networking, and credentials. Use `schemaVersion: "2"` for new kits; existing v1 kits continue to load through the legacy path. See the [kit spec reference](/manuals/ai/sandboxes/customize/kits-v2/_index.md#schema-versions) for migration details. **MCP management is now a first-class feature.** Register remote or local MCP servers once with `sbx mcp`, then reuse them across supported agents and sandboxes through a built-in MCP gateway. OAuth credentials stay on the host, and organizations can govern server registration and tool calls with Cedar policies. See the [MCP gateway documentation](https://docs.docker.com/ai/sandboxes/mcp-gateway/). diff --git a/content/manuals/ai/sandboxes/troubleshooting.md b/content/manuals/ai/sandboxes/troubleshooting.md index 0ecaa572e67..75950d6d29e 100644 --- a/content/manuals/ai/sandboxes/troubleshooting.md +++ b/content/manuals/ai/sandboxes/troubleshooting.md @@ -189,9 +189,10 @@ your organization's internal root CA inside the sandbox so the agent and its SDKs trust certificates signed by the proxy. Certificate errors can stop a request before the credential proxy can inject credentials. -For repeatable setup, create a [sandbox kit](customize/kits.md) that installs -the CA when the sandbox is created. See -[Install an internal CA certificate](customize/kit-examples.md#install-an-internal-ca-certificate) +For repeatable setup with a built-in agent, create a +[v2 mixin kit](customize/kits-v2/_index.md) that installs the CA when the +sandbox is created. See +[Install an internal CA certificate](customize/kits-v2/_index.md#install-an-internal-ca-certificate) for an example kit. Use a PEM-encoded certificate with a `.crt` extension. If traffic can be signed diff --git a/content/manuals/ai/sandboxes/usage.md b/content/manuals/ai/sandboxes/usage.md index da85d47b075..5134db004a7 100644 --- a/content/manuals/ai/sandboxes/usage.md +++ b/content/manuals/ai/sandboxes/usage.md @@ -2,7 +2,7 @@ title: Usage weight: 30 description: Basic sbx commands for creating, managing, and connecting to Docker Sandboxes. -keywords: docker sandboxes, sbx, usage, run, create, stop, remove, ports, workspaces +keywords: docker sandboxes, sbx, usage, run, create, stop, remove, ports, workspaces, templates, save, load --- Use this page as a command-oriented guide to day-to-day `sbx` operations. For @@ -385,5 +385,139 @@ changes, command history, and mountless workspace files all persist across stops and restarts. When you remove a sandbox, everything inside is deleted. Host workspace files, including repositories used as clone sources, and the [shared agent skills store](workflows/agent-skills.md) remain on your host. To -preserve a configured environment, create a [custom -template](customize/templates.md) or use a [kit](customize/kits.md). +capture changes in the container filesystem, [save a template](#saving-a-sandbox-as-a-template). +For a reproducible environment defined in source, use a +[workload kit](customize/kits.md). + +## Saving a sandbox as a template + +Save a sandbox's container filesystem as a reusable template image after +setting up tools or configuration interactively. + +A saved template isn't a backup of the whole sandbox. Mounted filesystems, +including host workspaces and the Docker store at `/var/lib/docker`, aren't +included. Save any data from those mounts separately. + +> [!WARNING] +> Saving a sandbox captures files in its container filesystem, including any +> secrets stored there. If you manually added API keys, tokens, or other +> credentials to the sandbox, they're embedded in the saved template and +> shared with anyone you distribute it to. To keep credentials out of +> templates, manage them with `sbx secret set` instead — the proxy injects +> them at runtime so they're never written to the filesystem. For more +> information, see [Manage credentials](configuration/credentials.md). + +### Save and reuse + +Stop the sandbox (or let the CLI prompt you), then save it with a name and +tag: + +```console +$ sbx template save my-sandbox my-template:v1 +``` + +The image is stored in the sandbox runtime's local image store. Create a +new sandbox from it with the `-t` flag: + +```console +$ sbx run -t my-template:v1 claude +``` + +### List and remove templates + +List all saved templates: + +```console +$ sbx template ls +``` + +Remove a template you no longer need: + +```console +$ sbx template rm my-template:v1 +``` + +### Export and import + +To share a saved template or move it to another machine, export it as a +tar file: + +```console +$ sbx template save my-sandbox my-template:v1 --output my-template.tar +``` + +On the other machine, load the tar file and use it: + +```console +$ sbx template load my-template.tar +$ sbx run -t my-template:v1 claude +``` + +### Limitations + +Agent configuration files are always recreated when a sandbox is created. +Changes to user-level agent configuration files, such as +`/home/agent/.claude/settings.json` and `/home/agent/.claude.json`, do not +persist in saved templates. + +If the saved template was built for a different agent than the one you +specify in `sbx run`, you get a warning. For example, saving a Claude +sandbox and running it with `codex` produces: + +```text +⚠ WARNING: template "my-template:v1" was built for the "claude" agent but you are using "codex". + The sandbox may not work correctly. Consider using: sbx run -t my-template:v1 claude +``` + +## Load a template + +To create a sandbox from a template image in a registry, pass its full image +reference to `--template`. Use the agent the image was prepared for: + +```console +$ sbx run --template docker.io/my-org/my-template:v1 claude +``` + +Unlike Docker commands, `sbx` doesn't automatically add the Docker Hub domain +(`docker.io`) to image references. For available images and the built-in agent +workflow, see [Base images](customize/base-images.md). + +> [!NOTE] +> The Docker daemon used by Docker Sandboxes pulls templates from a +> registry directly; it doesn't share the image store of your local Docker +> daemon on the host. To route Docker Hub image pulls through your +> organization's registry infrastructure, configure a +> [registry mirror](configuration/registry-mirror.md). + +> [!IMPORTANT] +> For Docker Hub, `sbx` reuses your `sbx login` session to pull private +> images. For other registries (GitHub Container Registry, ECR, ACR, a +> self-hosted Nexus, and so on), store pull credentials with +> [`sbx secret set --registry`](configuration/credentials.md#registry-credentials) +> before running the sandbox: +> +> ```console +> $ gh auth token | sbx secret set --registry ghcr.io --password-stdin +> ``` +> +> Without stored credentials, pulls from non-Docker Hub registries are +> anonymous and private images fail to pull. + +For locally-built images, save the image to a tar and load it directly +into the sandbox runtime instead of pulling from a registry: + +```console +$ docker image save my-org/my-template:v1 -o my-template.tar +$ sbx template load my-template.tar +$ sbx run --template my-org/my-template:v1 claude +``` + +`sbx template load` imports the tar into the sandbox runtime's image +store, so the image doesn't need to be reachable from a registry at +sandbox creation time. + +### Template caching + +Template images are cached locally. The first use pulls from the registry; +subsequent sandboxes reuse the cache. Cached images persist across sandbox +creation and deletion, and are cleared when you run `sbx reset`. diff --git a/content/manuals/ai/sandboxes/workflows/authentication.md b/content/manuals/ai/sandboxes/workflows/authentication.md index 0edd3012888..e78c57cdeb3 100644 --- a/content/manuals/ai/sandboxes/workflows/authentication.md +++ b/content/manuals/ai/sandboxes/workflows/authentication.md @@ -50,7 +50,7 @@ The token is never stored in plaintext inside the sandbox. See When using Docker Hub, authentication is handled automatically; `sbx` reuses your existing login session. For other registries, you need to configure -credentials for `sbx` so it can pull private [templates](../customize/templates.md) +credentials for `sbx` so it can pull private [templates](../usage.md#load-a-template) and kits when creating a sandbox: ```console diff --git a/content/manuals/ai/sandboxes/workflows/git.md b/content/manuals/ai/sandboxes/workflows/git.md index 0d8f555a4c7..bdffa1f14b4 100644 --- a/content/manuals/ai/sandboxes/workflows/git.md +++ b/content/manuals/ai/sandboxes/workflows/git.md @@ -213,8 +213,8 @@ you turned off forwarding or use a fixed SSH agent socket, see To apply this configuration automatically to every sandbox, use the [`git-ssh-sign`](https://github.com/docker/sbx-kits-contrib/tree/main/git-ssh-sign) -community kit, which handles all of the above setup. See [Kits](../customize/kits.md) -if you want to package it alongside other sandbox customizations. +community kit, which handles all of the above setup. For using it with the +built-in agents, see [Kits v2](../customize/kits-v2/_index.md). For troubleshooting, see [Sandbox commits aren't signed](../troubleshooting.md#sandbox-commits-arent-signed). diff --git a/data/whats-new.json b/data/whats-new.json index a819220b6b4..095db4b5896 100644 --- a/data/whats-new.json +++ b/data/whats-new.json @@ -15,7 +15,7 @@ "product": "Docker Sandboxes", "title": "Sign and enforce trusted sandbox kits", "description": "Sign kits with cosign-compatible Sigstore signatures, verify keyless or key-based signatures, and reject kits outside a trusted-signer policy.", - "url": "/ai/sandboxes/customize/kits/#sign-and-verify-kits", + "url": "/ai/sandboxes/customize/kits-v2/#sign-and-verify-kits", "published": "2026-08-20", "source_prs": [25791, 25896], "featured": false @@ -78,7 +78,7 @@ "product": "Docker Sandboxes", "title": "Author version 2 sandbox kits", "description": "Define kit setup, permissions, networking, and API key or OAuth credential requirements with the version 2 kit schema.", - "url": "/ai/sandboxes/customize/kit-reference/#schema-versions", + "url": "/ai/sandboxes/customize/kits-v2/#schema-versions", "published": "2026-08-06", "source_prs": [25467, 25707], "featured": false