From b992a488acefcd278da03dccedffb4dd3a6ef687 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 11 Sep 2026 14:07:36 +0000 Subject: [PATCH 1/6] docs: document kits v3 and preserve v2 The kit documentation described v2 runtime customizations and a v1 agent walkthrough, without the v3 workload, build, or capability contracts. Make v3 the primary guide, schema reference, and examples, retain the v2 pages in a separate section, and align navigation and credential guidance. Co-authored-by: Codex --- .../ai/sandboxes/configuration/credentials.md | 65 +- .../manuals/ai/sandboxes/customize/_index.md | 79 +- .../ai/sandboxes/customize/build-an-agent.md | 475 ++++---- .../ai/sandboxes/customize/kit-examples.md | 638 ++++------ .../ai/sandboxes/customize/kit-reference.md | 1038 +++++++++-------- .../ai/sandboxes/customize/kits-v2/_index.md | 636 ++++++++++ .../customize/kits-v2/kit-examples.md | 472 ++++++++ .../customize/kits-v2/kit-reference.md | 586 ++++++++++ .../manuals/ai/sandboxes/customize/kits.md | 829 ++++++------- .../ai/sandboxes/customize/templates.md | 7 +- content/manuals/ai/sandboxes/faq.md | 25 +- 11 files changed, 3146 insertions(+), 1704 deletions(-) create mode 100644 content/manuals/ai/sandboxes/customize/kits-v2/_index.md create mode 100644 content/manuals/ai/sandboxes/customize/kits-v2/kit-examples.md create mode 100644 content/manuals/ai/sandboxes/customize/kits-v2/kit-reference.md diff --git a/content/manuals/ai/sandboxes/configuration/credentials.md b/content/manuals/ai/sandboxes/configuration/credentials.md index 503016b7976..fdfea8cbd0f 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,33 @@ 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, and v2 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 +248,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 @@ -434,8 +466,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,25 +510,23 @@ 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 @@ -561,10 +591,11 @@ 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, which uses +the credentials from `docker login`. ### Remove registry credentials diff --git a/content/manuals/ai/sandboxes/customize/_index.md b/content/manuals/ai/sandboxes/customize/_index.md index 94e3434250a..f3ef2e833ad 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 reusable sandbox workloads and extensions with kits, or customize and save sandbox template images. +keywords: sandboxes, sbx, customize, templates, kits, mixins, workloads, custom agents weight: 90 aliases: - /ai/sandboxes/agents/custom-environments/ @@ -15,56 +15,45 @@ params: {{< summary-bar feature_name="Docker Sandboxes sbx" >}} -Docker Sandboxes offers two ways to customize a sandbox beyond the built-in -defaults: +Use kits and templates to package the tools, configuration, and runtime +behavior your sandboxes need. -- [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. +- [Kits](kits.md) combine reusable image content with runtime declarations for + credentials, network access, storage, lifecycle hooks, and agent instructions. + A workload kit defines what the sandbox runs. Mixin kits extend it. +- [Templates](templates.md) are reusable sandbox images. Extend a base image + with a Dockerfile, or save a configured running sandbox as a template. -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 +## Builds and runtime configuration -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. +Kits v3 include building as part of authoring a kit. Use the kit's Dockerfile +to install packages, compile tools, and copy static content into an image. +Use its descriptor to declare what the runtime must provide when the sandbox +runs, such as a credential, network access, or a startup hook. -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. +A workload kit can build on a sandbox template with Dockerfile `FROM`. The +template supplies the base environment, and the kit adds its content and +runtime declarations. A mixin can also build and ship its own tools as an +overlay, so adding a tool doesn't require rebuilding the workload image. -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. +Lifecycle install hooks remain available for initialization that needs a +sandbox's runtime inputs. Build steps produce content that sandboxes reuse; +install hooks initialize each sandbox separately. -## When to use which +## Choose a customization -| 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) | +| Goal | Option | +| --- | --- | +| Define an agent or another sandbox workload | [Workload kit](build-an-agent.md) | +| Add a tool, configuration, or runtime capability to a v3 workload | [Mixin kit](kit-examples.md) | +| Customize the image used by a built-in agent | [Template](templates.md#build-a-custom-template) | +| Capture a configured running sandbox for reuse | [Saved template](templates.md#saving-a-sandbox-as-a-template) | +| Maintain an existing v2 sandbox or mixin kit | [Kits v2](kits-v2/_index.md) | -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. +Use v3 for authoring kits. Docker Sandboxes also supports v1 and v2, whose +`spec.yaml` workflow is documented in [Kits v2](kits-v2/_index.md). A single +composition cannot combine v3 kits with v1 or v2 kits. diff --git a/content/manuals/ai/sandboxes/customize/build-an-agent.md b/content/manuals/ai/sandboxes/customize/build-an-agent.md index 3d7f111dc30..cd42f393085 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 Claude Code workload kit with a pinned agent binary, runtime configuration, proxy-managed credentials, and agent instructions. +keywords: sandboxes, sbx, kits, agent, tutorial, claude, workload, build weight: 30 --- @@ -10,317 +10,252 @@ 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 schema v3 workload kit that runs Claude Code with a pinned binary, +configurable model, and an Anthropic API key held on the host. The same steps +apply to other agents: build the software into an image, declare its runtime +requirements, and provide instructions about the environment. -- `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. +This example uses API-key authentication. For the complete schema, see the +[Kit spec reference](kit-reference.md). -## Install Amp +## Prepare the kit directory -Amp installs via a curl-to-bash script: +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: -```yaml -commands: - install: - - command: "curl -fsSL https://ampcode.com/install.sh | bash" - user: "1000" - description: Install Amp +```console +$ mkdir claude-team ``` -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" +The completed directory contains three files: + +```text +claude-team/ +├── claude-team.yaml +├── claude-team.dockerfile +└── context.md ``` -`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. +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. + +## Build the agent into the image + +Create the companion Dockerfile: + +```dockerfile {title="claude-team/claude-team.dockerfile"} +FROM docker/sandbox-templates:shell + +USER agent +ARG CLAUDE_VERSION +ENV PATH="/home/agent/.local/bin:${PATH}" \ + IS_SANDBOX=1 \ + CLAUDE_ENV_FILE=/etc/sandbox-persistent.sh + +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 [] ``` -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. +The `shell` template supplies the sandbox environment, including Bash, Git, +curl, certificates, and the `agent` user at UID 1000. 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. + +The Dockerfile owns the image's `ENTRYPOINT`, `CMD`, environment, user, and +working directory. `CMD []` clears any inherited arguments. Claude Code's +`--settings` option reads an additional settings file that the kit writes +during sandbox creation. + +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. Reserve +lifecycle install hooks for configuration that depends on an individual +sandbox, such as registering a runtime endpoint. + +## Declare the runtime requirements + +Create the descriptor: + +```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 }}"] + +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: + - api.anthropic.com:443 + + - 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" + + - type: com.docker.runtime/lifecycle@1 + config: + files: + - path: /home/agent/.config/claude-team/settings.json + content: | + {"model": "${{ kit.args.model }}"} + mode: "0644" + + - type: com.docker.runtime/agent-context@1 + config: + filename: CLAUDE.md + contentFile: ./context.md ``` -## Register your API key +The descriptor separates what the kit supplies from what it asks the runtime +to do: -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. +- `kind: workload` makes this kit the environment and launch command for + the sandbox. A composition has one workload. +- `provides` identifies the installed agent and version so other kits can + declare a dependency on Claude Code. +- `capabilities` requests network access, credential injection, settings + file creation, and agent instructions. -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: +The arguments resolve at different times. `version` has a `buildArg`, so +the frontend validates it, passes it to the Dockerfile as `CLAUDE_VERSION`, +and records the installed version in `provides`. `model` resolves when you +create a sandbox, so changing it doesn't require rebuilding the binary. -```console -$ sbx secret set-custom \ - --host ampcode.com \ - --env AMP_API_KEY \ - --placeholder "sgamp-{rand}" \ - --value "$AMP_API_KEY" -``` +The credential capability exposes a placeholder in `ANTHROPIC_API_KEY`. +The proxy substitutes the host's key in the `x-api-key` header for requests +to `api.anthropic.com`. The matching network allow entry permits those +requests. Declaring a credential doesn't store or grant access to a key. -`{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`. +The network policy describes sandbox execution. Downloading Claude Code in +the Dockerfile is build-time work. An `install` network policy would apply +to lifecycle install hooks, rather than to Dockerfile `RUN` instructions. -> [!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. +## Add agent instructions -> [!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. +Create the context file: -## Run it +```markdown {title="claude-team/context.md"} +## Team workflow -Validate the spec: +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. -```console -$ sbx kit validate ./amp/ +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: +The frontend includes this file in the kit image. At runtime, `sbx` adds an +entry to `CLAUDE.md` that points Claude Code to the kit's instructions. The +content stays in its own file, so composing more kits doesn't put all their +instructions into the main 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` caches local builds by source content: changed sources trigger +a rebuild and unchanged sources reuse the cache. 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](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..49aa29da372 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,332 @@ 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 example includes the files needed to run it locally. +For schema v2 patterns, see [Schema v2 kit examples](kits-v2/kit-examples.md). +For field definitions, see the [Kit spec reference](kit-reference.md). + +## Create a workload for the examples + +Create a `shell-v3` directory and save this descriptor inside it: + +```yaml {title="shell-v3/shell-v3.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: workload +displayName: Example shell + +build: | + FROM docker/sandbox-templates:shell + USER agent + ENTRYPOINT ["bash"] + CMD ["-l"] + +capabilities: + - type: com.docker.runtime/agent-context@1 + config: + filename: AGENTS.md + content: This shell is an environment for testing composed kits. +``` + +The inline `build` is a Dockerfile that produces a complete workload. Run it +from the parent directory, using your current directory as the workspace: -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). +```console +$ sbx run --name kit-shell ./shell-v3 . +``` -## Drop a shared config file +Use this workload with the mixins that follow. Each example uses a different +sandbox name because adding kits requires creating a sandbox. Schema v3 +mixins need a schema v3 workload. The built-in agent names use earlier kit +formats and can't be combined with these mixins. -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. +## Copy shared configuration + +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. + +Create this directory: ```text -ruff-lint/ -├── spec.yaml -└── files/ - └── workspace/ - └── ruff.toml +team-config/ +├── team-config.yaml +├── team-config.dockerfile +└── editorconfig ``` -```yaml {title="ruff-lint/spec.yaml"} -schemaVersion: "2" -kind: mixin -name: ruff-lint -displayName: Ruff -description: Python linting with shared team config - -setup: - install: - - command: "uv tool install ruff@latest" - user: "1000" +```ini {title="team-config/editorconfig"} +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true ``` -```toml {title="ruff-lint/files/workspace/ruff.toml"} -line-length = 80 +The Dockerfile places the file outside the workspace, where a runtime mount +won't hide it: -[lint] -select = ["E", "F", "I"] +```dockerfile {title="team-config/team-config.dockerfile"} +FROM scratch +COPY editorconfig /usr/share/team-config/editorconfig ``` -## 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" -``` +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: -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" +```yaml {title="team-config/team-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: 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 ``` -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. +Run it with the example workload: -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`. +```console +$ sbx run --name kit-team-config ./shell-v3 --kit ./team-config . +``` -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. +`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. + +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. ## Install an internal CA certificate -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. +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: -```text -internal-ca/ -├── spec.yaml -└── files/ - └── home/ - └── internal-ca.crt +```dockerfile {title="internal-ca/internal-ca.dockerfile"} +FROM scratch +COPY internal-ca.crt /usr/local/share/ca-certificates/team-internal-ca.crt ``` -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`. - -```yaml {title="internal-ca/spec.yaml"} -schemaVersion: "2" +```yaml {title="internal-ca/internal-ca.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" 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" - description: Install internal CA certificate +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 ``` -`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. - -## Run a background service +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. -`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: - -```yaml -setup: - startup: - - command: ["my-service", "--port", "8080"] - user: "1000" - background: true -``` - -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 +```console +$ sbx run --name kit-ca ./shell-v3 --kit ./internal-ca . ``` -An empty log file tells you the wrapper ran; a populated one tells you why -the service failed. - -## Write runtime values to a file +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`. -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. +## Build a tool overlay -```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" -``` - -`mode: "0755"` makes the generated file executable so the startup -command can invoke it directly. - -Use `setup.files` instead of a static file whenever the content depends -on a runtime value. Use a static file otherwise. +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. -> [!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. +Create a `gojq` directory with these two files: -## Ship a Claude Code skill +```yaml {title="gojq/gojq.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +displayName: JSON query tool -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. +args: + version: + default: "0.12.17" + pattern: '^[0-9]+\.[0-9]+\.[0-9]+$' + buildArg: GOJQ_VERSION -```text -docker-review/ -├── spec.yaml -└── files/ - └── workspace/ - └── .claude/ - └── skills/ - └── docker-review/ - └── SKILL.md +provides: ["gojq@${{ kit.args.version }}"] ``` -```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 +```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} + +FROM scratch +COPY --from=build /go/bin/gojq /usr/local/bin/gojq ``` -```markdown {title="docker-review/files/workspace/.claude/skills/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. ---- +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. -When reviewing a Dockerfile, check: +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. -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 +```console +$ sbx run --name kit-gojq ./shell-v3 --kit ./gojq . ``` -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. +Inside the sandbox, query some JSON: -## 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). +```console +$ printf '%s\n' '{"kit": "gojq", "ready": true}' | gojq '.ready' +true +``` -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: +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 [Kit composition](kits.md). -```text -claude-sonnet/ -├── spec.yaml -└── files/ - └── home/ - └── .config/ - └── claude/ - └── sonnet.json -``` +## Write runtime configuration -```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 -``` +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: -```json {title="claude-sonnet/files/home/.config/claude/sonnet.json"} -{ - "model": "sonnet" -} +```yaml {title="workspace-config/workspace-config.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +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" ``` -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: +Create the `workspace-config` directory, save the descriptor, and supply the +project name when creating the sandbox: ```console -$ sbx run ./claude-sonnet +$ sbx run --name kit-project ./shell-v3 --kit ./workspace-config \ + --kit-arg workspace-config.project=payments . ``` -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). +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. -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`: +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]`, as in the shared configuration +example. -```text -opencode-team/ -├── spec.yaml -└── files/ - └── home/ - └── .config/ - └── opencode/ - └── team.json -``` +## Run a hook on every start -```yaml {title="opencode-team/spec.yaml"} -schemaVersion: "2" -kind: mixin -name: opencode-team -requires: - agent: opencode +Use lifecycle `startup` for work that must repeat after the sandbox stops +and starts. This example records the time and workspace at each start: -environment: - variables: - OPENCODE_CONFIG: /home/agent/.config/opencode/team.json +```yaml {title="start-log/start-log.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +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 ``` -```json {title="opencode-team/files/home/.config/opencode/team.json"} -{ - "$schema": "https://opencode.ai/config.json", - "autoupdate": false -} +```console +$ sbx run --name kit-start-log ./shell-v3 --kit ./start-log . ``` -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. - -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. - -## Fork an existing agent +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. -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: +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. -```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"] -``` +## Contribute agent instructions -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. +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: -Launch by passing the sandbox kit in place of a built-in agent name: +```yaml {title="team-review/team-review.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +displayName: Team review instructions -```console -$ sbx run ./claude-safe +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. ``` -For a step-by-step walkthrough of building a new sandbox kit from -scratch, see [Build an agent](build-an-agent.md). - -## More examples - -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`: +The example shell workload 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. ```console -$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=" +$ sbx run --name kit-team-review ./shell-v3 --kit ./team-review . ``` -For a `kind: sandbox` kit, pass the reference in place of the agent name: +For longer instructions, set `contentFile: ./review.md` instead of `content` +and keep the Markdown file beside the descriptor. The build frontend stages +the file in the image. Only workload kits can set `filename`. -```console -$ sbx run "git+https://github.com/docker/sbx-kits-contrib.git#dir=" -``` +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..ed8516b82e7 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,625 @@ 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](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 kit authoring. Docker Sandboxes also supports v1 +and v2 kits. For `schemaVersion: "2"`, see the +[v2 spec reference](kits-v2/kit-reference.md), including the +[v1-to-v2 field mapping](kits-v2/kit-reference.md#schema-versions). +Each descriptor uses one grammar. V2 fields aren't accepted in a v3 +descriptor. + +### 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. + +| 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. + +## Descriptor 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. -> [!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`. +```yaml +# 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. +``` -## Top-level fields +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 the composition must supply. | +| `integrates` | List of strings | Features this kit integrates with when present. | +| `conflicts` | List of strings | Features that must be absent from the composition. | +| `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 -schemaVersion: "2" -kind: -name: -version: -displayName: -description: -sourceURL: -licenses: - - MIT -locked: - - sandbox.image -security: - privileged: false -args: - channel: - default: stable - enum: [stable, beta] +provides: ["com.example/tooling@1.4.0"] +requires: ["node >= 20.0.0"] +integrates: ["docker-engine >= 25.0.0"] +conflicts: [podman] ``` -| 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`. +| Field | Entry syntax | Resolution rule | +| --- | --- | --- | +| `provides` | `name` or `name@version` | Advertises a feature. No feature is provided implicitly. | +| `requires` | `name` or `name >= version` | A provider in the selected kit set must satisfy the requirement. | +| `integrates` | `name` or `name >= version` | An absent provider is accepted. A present provider must satisfy the minimum. | +| `conflicts` | `name` | Resolution fails if the selected set provides this name. | + +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. + +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 `/`. + +### Versions + +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. + +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. + +Build-time argument references are accepted in `provides` and `version` and +expanded before publication. Keep `requires`, `integrates`, and `conflicts` +literal. ## 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. +Declare arguments under `args` and reference them as +`${{ kit.args. }}`. Arguments resolve in one of two phases: + +- 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 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: + 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 - description: Build target + enum: [alpha, beta] +``` -environment: - variables: - TOOL_VERSION: "${{ kit.args.version }}" +| 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. + +Substitution happens before decoding. Quote placeholders in string fields. +Create-time values produce an effective descriptor that 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 +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +build: | + FROM scratch + COPY review-checklist.md /usr/local/share/team/review-checklist.md ``` -Don't use kit arguments for API tokens, passwords, or other secrets. Use -[Credentials](../configuration/credentials.md) to provide sensitive values to -a sandbox. +The comment form starts with a descriptor comment block: -| 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`. | +```dockerfile +# syntax=docker/runtime-kit:3 +# kit: +# schemaVersion: "3" +# kind: mixin +FROM scratch +COPY review-checklist.md /usr/local/share/team/review-checklist.md +``` -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. +A comment descriptor can't also declare `build` or `dockerfile`. +Dockerfile semantics apply to every recipe, including multi-stage builds and +build mounts. For directory layouts, build commands, and static files, see +[Kits](kits.md). -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. +### Base image requirements -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. +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. -## Kit kinds +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. -### `kind: mixin` +## Runtime capabilities -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: +Each `capabilities` entry names a runtime contract and its config-schema +version. The type version is independent of `schemaVersion`. ```yaml -schemaVersion: "2" -kind: mixin -name: github-tools -requires: - agent: claude +capabilities: + - type: com.docker.runtime/port@1 + description: Development server + optional: false + config: + container: 3000 ``` -`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: +| 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 -schemaVersion: "2" -kind: sandbox -name: claude-safe -extends: claude +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] ``` -`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. +| 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. | -## Sandbox block +`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. -```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" -``` +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. -| 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`. | +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. -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`. +## Credentials -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. +`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. -The agent's container image must provide: +```yaml +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" +``` -- 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). +| 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. -Build on top of `docker/sandbox-templates:shell-docker` to get these base -requirements. +```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] +``` -## Agent instructions +### 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 -agentInstructions: - filename: CLAUDE.md - content: | - Ruff is installed. Run `ruff check` before committing. +capabilities: + - type: com.docker.runtime/agent-context@1 + config: + filename: AGENTS.md + contentFile: ./agent-context.md ``` -| 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. | +| 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`. | -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 frontend copies a `contentFile` into the kit image and rewrites the +published descriptor to point to that location. The agent profile indexes +each kit's instructions so the agent can read them when needed. A mixin omits +`filename` and uses `contentFile` or `content`. -## Credentials +## Ports -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. +`com.docker.runtime/port@1` publishes an inbound port. Declare a separate +entry for each port and transport. -```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}}" -``` +| Field | Description | +| --- | --- | +| `container` | Required. Container port from 1 to 65535. | +| `transport` | `tcp` or `udp`. TCP is the default. | +| `name` | Optional label for port listings. | -`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. +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. -```yaml -permissions: - network: - allow: [, ...] - deny: [, ...] -``` +## Resources -| 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. | +`com.docker.runtime/resources@1` declares workload resource settings. -Allow and deny patterns: +| 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. | -| 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 | +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. -In v1 this was the `network:` block (`allowedDomains` / `deniedDomains`, plus -`serviceDomains` / `serviceAuth`). In v2, those fields are decode errors. +## Kit registry -## Ports +`com.docker.runtime/kit-registry@1` requests access to the runtime's registry +for kit builds. It takes no `config`. -Use `ports` to expose sandbox services to the host: +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. -```yaml -ports: - - container: 8080 - name: web -``` +## Volumes -| 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. | +`com.docker.runtime/volume@1` describes a storage mount. `sbx` accepts this +schema, but doesn't apply v3 volume requests to the sandbox. -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 :`. +| 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"`. | -## Environment +## USB devices -```yaml -environment: - variables: - : -``` +`com.docker.runtime/usb-device@1` describes a USB device request. `sbx` +accepts this schema, but doesn't apply v3 USB requests. -| Field | Description | -| ----------- | ---------------------------------------------- | -| `variables` | Key-value pairs set directly in the container. | +| 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. | -Variable names must be valid shell identifiers (`[A-Za-z_][A-Za-z0-9_]*`). +Declare exactly one match form: a vendor/product pair, or a class. -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. +## Privileged mode -## Setup +`com.docker.runtime/privileged@1` requests elevated runtime privileges. It +takes no `config`. `sbx` accepts this schema, but doesn't apply v3 privileged +requests. -```yaml -setup: - install: - - command: - user: - description: - startup: - - command: [, ...] - user: - background: - description: - files: - - path: - content: - mode: - onlyIfMissing: - description: -``` +## Agent sessions -### 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 -``` +`com.docker.runtime/agent-sessions@1` describes a workload's session commands. +`sbx` accepts this schema, but doesn't use it to drive sessions. -| Kit path | Container destination | -| ------------------ | --------------------------------------- | -| `files/home/` | `/home/agent/` (config files, dotfiles) | -| `files/workspace/` | The primary workspace path | +| 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. | -Parent directories are created automatically. Existing files are -overwritten. Absolute paths and path-traversal sequences (`../../`) are -rejected. +At least one command is required. `prompt`, `resume`, and `continue` are +argument tails. `list` is a complete command. -## Volumes +## Published image format -```yaml -volumes: - - path: /workspace - size: 10g - mode: "0755" - - path: /tmp/scratch - type: tmpfs - size: 512m - mode: "1777" -``` +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. | -| 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. | +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. -Volumes are applied only when a sandbox is created. `sbx kit add` cannot attach -volumes to a running container. +The published descriptor has a 512 KiB limit, with a build warning above +64 KiB. Keep substantial instruction text in `contentFile` and other content +in image layers. For image composition and distribution, see [Kits](kits.md). 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..e76e3f9ee95 --- /dev/null +++ b/content/manuals/ai/sandboxes/customize/kits-v2/_index.md @@ -0,0 +1,636 @@ +--- +title: Kits v2 +linkTitle: Kits v2 +description: Extend a sandbox with tools, credentials, network rules, and configuration using declarative YAML artifacts. +keywords: sandboxes, sbx, kits, mixins, customization, extensions, agents +weight: 60 +--- + +{{< summary-bar feature_name="Docker Sandboxes sbx" >}} + +This page covers kits with `schemaVersion: "2"`. Use [Kits v3](../kits.md) +for authoring kits with image builds and runtime capabilities. Docker Sandboxes +also supports v1 and v2 kits. A composition cannot combine v3 kits with v1 or v2 +kits. + +> [!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. + +A kit packages a set of capabilities a sandbox can use, such as: + +- 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 + +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. + +A kit is either a mixin or a sandbox: + +- 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. + +## What kits can do + +### Run commands + +A kit can run commands inside the sandbox automatically. **Install +commands** run once at creation; **startup commands** run each time +the sandbox starts. + +Install commands are the place to put anything an agent needs into the +image, via `apt`, `pip`, `npm`, `curl | bash`, or whatever fits: + +```yaml +setup: + install: + - command: "apt-get update && apt-get install -y jq" +``` + +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: + +```yaml +setup: + startup: + - command: ["my-daemon"] + background: true +``` + +### Inject files + +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. + +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. + +```text +my-kit/ +├── spec.yaml +└── files/ + ├── home/ + │ └── .config/my-tool/settings.json + └── workspace/ + └── .editorconfig +``` + +`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: + +```yaml +setup: + files: + - path: /home/agent/.my-tool/config.json + content: '{"workspace": "${WORKDIR}"}' + onlyIfMissing: true +``` + +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. + +### Set environment variables + +Environment variables set by the kit are available to the agent at +runtime: + +```yaml +environment: + variables: + MY_TOOL_WORKSPACE: /home/agent/my-tool +``` + +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). + +### Control network access + +Network rules define which domains the sandbox can reach or block. Kit +network rules apply only to sandboxes that use the kit: + +```yaml +permissions: + network: + allow: + - api.example.com + - "*.cdn.example.com" + deny: + - telemetry.example.com +``` + +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. + +> [!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). + +For authenticated services, see +[Authenticate to external services](#authenticate-to-external-services). + +### Authenticate to external services + +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. + +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: + +```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 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. + +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. + +### Inject agent memory + +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. + +```yaml +agentInstructions: + content: | + Ruff is installed. Run `ruff check` before committing. + Shared config lives at `/workspace/ruff.toml`. +``` + +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: + +```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 +``` + +See [`agentInstructions`](kit-reference.md#agent-instructions) in the spec +reference for the full field schema. + +### Define an agent + +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: + +```yaml +sandbox: + image: "my-registry/my-agent:latest" + entrypoint: [my-agent, "--yolo"] +``` + +See [Sandbox kits](#sandbox-kits) for use cases and an example. + +## Mixin kits + +A mixin kit extends an existing agent with extra capabilities. Common use +cases: + +- 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) + +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. + +## Sandbox kits + +A sandbox kit defines a full agent from scratch — image, entrypoint, and +everything the agent needs. Common use cases: + +- 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 + +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 walkthrough of the v3 workload format, see +[Build your own agent kit](../build-an-agent.md). + +### Extend a built-in agent + +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. + +## Using kits + +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. + +Starting with Docker Sandboxes version 0.42.0, pass the sandbox kit reference +as the first argument: + +```console +$ sbx run [PATH...] +$ sbx create [PATH...] +``` + +The previous form, `sbx run --kit `, is +deprecated. + +> [!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. + +### Pass arguments to kits + +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`: + +```console +$ sbx run ./my-agent/ --kit-arg channel=beta +``` + +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. + +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: + +```console +$ sbx run ./my-agent/ \ + --kit ./my-mixin/ \ + --kit-arg version=1.2.3 \ + --kit-arg my-mixin.version=2.0.0 +``` + +The kit-specific value takes precedence over the shared value for `my-mixin`. + +Use `--kit-args-file` for a reusable set of `name=value` entries. Blank lines +and lines that start with `#` are ignored: + +```text {title="kit.args"} +version=1.2.3 +my-mixin.channel=beta +``` + +```console +$ sbx create ./my-agent/ . \ + --kit ./my-mixin/ \ + --kit-args-file ./kit.args \ + --kit-arg my-mixin.channel=stable +``` + +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. + +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. + +### Local + +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: + +```console +$ sbx run ./my-agent/ +$ sbx create ../my-agent-1.0.zip . +``` + +Pass a local mixin with `--kit`: + +```console +$ sbx run claude --kit ./my-mixin/ +$ sbx run claude --kit ../my-mixin-1.0.zip +``` + +While iterating on a supported mixin kit, apply changes to a running sandbox +with `sbx kit add`: + +```console +$ sbx kit add my-sandbox ./my-kit/ +``` + +`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. + +### Git repository + +Launch a sandbox kit from a Git repository: + +```console +$ sbx run "git+https://github.com/docker/sbx-kits-contrib.git#ref=v0.1.0&dir=amp" +``` + +Pass a Git-hosted mixin with `--kit`: + +```console +$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#ref=v0.1.0&dir=code-server" +``` + +- `#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. + +### OCI registry + +Launch a sandbox kit from an OCI registry: + +```console +$ sbx run docker.io/sbx/droid-kit:latest +``` + +Pass an OCI-hosted mixin with `--kit`: + +```console +$ sbx run claude --kit ghcr.io/myorg/my-kit:1.0 +``` + +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. + +### Restrict kit sources + +`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: + +```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. +``` + +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: + +```console +$ sbx settings set kit.allowedSources '["docker.io/","github.com/docker/"]' +``` + +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. + +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: + +```console +$ sbx settings set kit.allowLocalKits false +``` + +For non-interactive use, both settings have environment-variable equivalents: +`DOCKER_SANDBOXES_KIT_ALLOWED_SOURCES` and `DOCKER_SANDBOXES_KIT_ALLOW_LOCAL`. + +## 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. + +## 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. + +## 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 ...`. diff --git a/content/manuals/ai/sandboxes/customize/kits-v2/kit-examples.md b/content/manuals/ai/sandboxes/customize/kits-v2/kit-examples.md new file mode 100644 index 00000000000..a51371c235f --- /dev/null +++ b/content/manuals/ai/sandboxes/customize/kits-v2/kit-examples.md @@ -0,0 +1,472 @@ +--- +title: Kit v2 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 +weight: 20 +--- + +{{< summary-bar feature_name="Docker Sandboxes sbx" >}} + +This page covers kits with `schemaVersion: "2"`. Use [Kits v3](../kits.md) +for authoring kits with image builds and runtime capabilities. Docker Sandboxes +also supports v1 and v2 kits. A composition cannot combine v3 kits with v1 or v2 +kits. + +> [!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. + +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). + +## Drop a shared config file + +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. + +```text +ruff-lint/ +├── spec.yaml +└── files/ + └── workspace/ + └── ruff.toml +``` + +```yaml {title="ruff-lint/spec.yaml"} +schemaVersion: "2" +kind: mixin +name: ruff-lint +displayName: Ruff +description: Python linting with shared team config + +setup: + install: + - command: "uv tool install ruff@latest" + user: "1000" +``` + +```toml {title="ruff-lint/files/workspace/ruff.toml"} +line-length = 80 + +[lint] +select = ["E", "F", "I"] +``` + +## 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" +``` + +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" +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 +``` + +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`. + +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. + +## Install an internal CA certificate + +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. + +```text +internal-ca/ +├── spec.yaml +└── files/ + └── home/ + └── internal-ca.crt +``` + +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`. + +```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" + description: Install internal CA certificate +``` + +`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. + +## Run a background service + +`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: + +```yaml +setup: + startup: + - command: ["my-service", "--port", "8080"] + user: "1000" + background: true +``` + +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 +``` + +An empty log file tells you the wrapper ran; a populated one tells you why +the service failed. + +## Write runtime values to a file + +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. + +```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" +``` + +`mode: "0755"` makes the generated file executable so the startup +command can invoke it directly. + +Use `setup.files` instead of a static file whenever the content depends +on a runtime value. Use a static file otherwise. + +> [!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. + +## 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. + +```text +docker-review/ +├── spec.yaml +└── files/ + └── workspace/ + └── .claude/ + └── skills/ + └── docker-review/ + └── SKILL.md +``` + +```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"} +--- +name: docker-review +description: Review a Dockerfile for best practices. Use when the user asks 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 +``` + +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](_index.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: + +```text +claude-sonnet/ +├── spec.yaml +└── files/ + └── home/ + └── .config/ + └── claude/ + └── sonnet.json +``` + +```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 +``` + +```json {title="claude-sonnet/files/home/.config/claude/sonnet.json"} +{ + "model": "sonnet" +} +``` + +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: + +```console +$ sbx run ./claude-sonnet +``` + +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). + +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`: + +```text +opencode-team/ +├── spec.yaml +└── files/ + └── home/ + └── .config/ + └── opencode/ + └── team.json +``` + +```yaml {title="opencode-team/spec.yaml"} +schemaVersion: "2" +kind: mixin +name: opencode-team +requires: + agent: opencode + +environment: + variables: + OPENCODE_CONFIG: /home/agent/.config/opencode/team.json +``` + +```json {title="opencode-team/files/home/.config/opencode/team.json"} +{ + "$schema": "https://opencode.ai/config.json", + "autoupdate": false +} +``` + +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. + +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. + +## 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 +``` + +For a walkthrough of the v3 workload format, see +[Build an agent](../build-an-agent.md). + +## More examples + +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`: + +```console +$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#dir=" +``` + +For a `kind: sandbox` kit, pass the reference in place of the agent name: + +```console +$ sbx run "git+https://github.com/docker/sbx-kits-contrib.git#dir=" +``` diff --git a/content/manuals/ai/sandboxes/customize/kits-v2/kit-reference.md b/content/manuals/ai/sandboxes/customize/kits-v2/kit-reference.md new file mode 100644 index 00000000000..8fbd73ff0cd --- /dev/null +++ b/content/manuals/ai/sandboxes/customize/kits-v2/kit-reference.md @@ -0,0 +1,586 @@ +--- +title: Kit v2 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 +weight: 30 +--- + +{{< summary-bar feature_name="Docker Sandboxes sbx" >}} + +This page covers kits with `schemaVersion: "2"`. Use [Kits v3](../kits.md) +for authoring kits with image builds and runtime capabilities. Docker Sandboxes +also supports v1 and v2 kits. A composition cannot combine v3 kits with v1 or v2 +kits. + +> [!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 page documents every field in a kit's `spec.yaml`. For an overview of +what kits are and how to use them, see [Kits](_index.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/ +``` + +## Schema versions + +Use `schemaVersion: "2"` for the syntax on this page. Version `"1"` also +remains accepted. See [Kits v3](../kits.md) for the preferred authoring format. + +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 + +```yaml +schemaVersion: "2" +kind: +name: +version: +displayName: +description: +sourceURL: +licenses: + - MIT +locked: + - sandbox.image +security: + privileged: false +args: + channel: + default: stable + enum: [stable, beta] +``` + +| 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. + +Supply values with `--kit-arg` or `--kit-args-file` when loading the kit. See +[Pass arguments to kits](_index.md#pass-arguments-to-kits) for scoping, +precedence, and validation behavior. + +## 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 + +```yaml +agentInstructions: + filename: CLAUDE.md + content: | + Ruff is installed. Run `ruff check` before committing. +``` + +| 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. + +## 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. + +```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}}" +``` + +`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. + +```yaml +permissions: + network: + allow: [, ...] + deny: [, ...] +``` + +| 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 + +Use `ports` to expose sandbox services to the host: + +```yaml +ports: + - container: 8080 + name: web +``` + +| 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 + +```yaml +environment: + variables: + : +``` + +| Field | Description | +| ----------- | ---------------------------------------------- | +| `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 + +```yaml +setup: + install: + - command: + user: + description: + startup: + - command: [, ...] + user: + background: + description: + files: + - path: + content: + mode: + onlyIfMissing: + description: +``` + +### 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 +``` + +| 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. + +## Volumes + +```yaml +volumes: + - path: /workspace + size: 10g + mode: "0755" + - path: /tmp/scratch + type: tmpfs + size: 512m + mode: "1777" +``` + +| 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. diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 685f60f5529..71595d41f0f 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -1,558 +1,470 @@ --- 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" >}} -> [!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. - -A kit packages a set of capabilities a sandbox can use, such as: +A kit packages a sandbox workload or an extension to one. It combines image +content, such as an agent or a toolchain, with declarations for the runtime: +network access, credentials, storage, lifecycle hooks, and agent instructions. +Build the kit once and reuse its content across sandboxes, while the runtime +applies its declarations when you create and run each sandbox. -- 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 +This page covers kits v3, the preferred format for authoring kits. Docker +Sandboxes also supports v1 and v2. See [Kits v2](kits-v2/_index.md) for the +`spec.yaml` format used by existing v2 kits. A single composition cannot combine +v3 kits with v1 or v2 kits. -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. - -A kit is either a mixin or a sandbox: +> [!NOTE] +> 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. -- 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. +## Workloads and mixins -## What kits can do +Every v3 sandbox composition contains exactly one workload kit and zero or more +mixin kits: -### Run commands +| Kind | What it supplies | How you use it | +| --- | --- | --- | +| `workload` | A complete root filesystem and the command to run, such as an agent or a shell | Pass it as the first argument to `sbx run` or `sbx create` | +| `mixin` | Additional files, tools, or runtime declarations | Add it with `--kit` | -A kit can run commands inside the sandbox automatically. **Install -commands** run once at creation; **startup commands** run each time -the sandbox starts. +Both kinds use the same descriptor schema. A mixin that only declares network +access or agent instructions needs no Dockerfile. A mixin that ships a tool +includes a build recipe. A workload always includes a build recipe because it +supplies the sandbox's filesystem and launch configuration. -Install commands are the place to put anything an agent needs into the -image, via `apt`, `pip`, `npm`, `curl | bash`, or whatever fits: +For example, with a local v3 workload and two local v3 mixins: -```yaml -setup: - install: - - command: "apt-get update && apt-get install -y jq" +```console +$ sbx run ./my-agent --name my-project --kit ./my-tool --kit ./team-config . ``` -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: +`sbx` builds the source directories, checks that the kits can work together, +and assembles their content into the image the sandbox runs. Unchanged source +builds and compositions reuse cached results. For complete examples, see +[Kit examples](kit-examples.md). To build an agent workload, follow +[Build an agent](build-an-agent.md). -```yaml -setup: - startup: - - command: ["my-daemon"] - background: true -``` +`--kit` applies when creating a sandbox. An existing sandbox with the same +name is reused, so use a different `--name` to try a different kit set or +source revision. Recreate the sandbox when you want to replace its composition. -### Inject files +## Capabilities -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. +Kits declare two kinds of contract. The distinction determines who supplies +what a kit needs. -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. +### Requests to the runtime -```text -my-kit/ -├── spec.yaml -└── files/ - ├── home/ - │ └── .config/my-tool/settings.json - └── workspace/ - └── .editorconfig -``` +The `capabilities` list describes what the kit needs the runtime to do. Each +entry has a namespaced, versioned `type` and a `config` specific to that type. +For example, this mixin requests outbound access to the GitHub API: -`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: +```yaml {title="github-access/github-access.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin -```yaml -setup: - files: - - path: /home/agent/.my-tool/config.json - content: '{"workspace": "${WORKDIR}"}' - onlyIfMissing: true +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: [api.github.com] ``` -See [`setup.files`](kit-reference.md#files) in the spec reference for all -fields. - -#### Sandbox-managed agent configuration +Other capability types request credentials, volumes, ports, lifecycle hooks, +or agent instructions. The type's `@1` identifies its config schema. A +capability can evolve independently of the kit descriptor's `schemaVersion`. -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`. +The capability contract distinguishes required requests from requests marked +`optional: true`. Mark a capability optional only when the kit can work without +it. Optional requests still participate in permission review when granted. -| 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` | +Docker Sandboxes implements a subset of the capability schemas and doesn't +reject every unsupported required request. Check the support notes in the +reference before relying on a capability. -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. +For each type's fields and Docker Sandboxes support, see +[Runtime capabilities](kit-reference.md#runtime-capabilities). -### Set environment variables +### Contracts between kits -Environment variables set by the kit are available to the agent at -runtime: +Use `provides`, `requires`, `integrates`, and `conflicts` to describe how kits +fit together: ```yaml -environment: - variables: - MY_TOOL_WORKSPACE: /home/agent/my-tool +provides: ["my-tool@1.0.0"] +requires: ["node >= 22.0.0"] +integrates: ["docker-engine >= 25.0.0"] +conflicts: ["incompatible-tool"] ``` -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. +This kit supplies `my-tool` version `1.0.0`, needs another selected kit to +provide Node.js version `22.0.0` or later, and can work with Docker Engine if +it is present at a compatible version. It rejects a composition that supplies +`incompatible-tool`. -> [!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). +These names are declarations by kit authors. They don't install packages or +search a registry. You select the complete set of kits, and the runtime +validates it. Installing Node.js in a Dockerfile doesn't automatically declare +`provides: ["node@22.0.0"]`. -### Control network access +See [Composition fields](kit-reference.md#composition-fields) for naming and +version rules. -Network rules define which domains the sandbox can reach or block. Kit -network rules apply only to sandboxes that use the kit: +## Build content and runtime setup -```yaml -permissions: - network: - allow: - - api.example.com - - "*.cdn.example.com" - deny: - - telemetry.example.com -``` +Choose where to put a customization based on when its inputs are available +and whether its result belongs in the reusable image or an individual sandbox. -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. +| Mechanism | When it runs | Use it for | +| --- | --- | --- | +| Dockerfile `RUN` and `COPY` | When the kit is built | Install tools, compile binaries, and package static content | +| Lifecycle `install` hooks | Once per sandbox, during creation | Initialize sandbox state using credentials, mounts, or other runtime inputs | +| Lifecycle `startup` hooks | Each sandbox start | Start a service or refresh state that must be restored after a restart | +| Lifecycle `files` | During sandbox setup | Write configuration with kit argument values or preserve an existing file with `overwrite: false` | -> [!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 a kit is separate from running its lifecycle hooks. A package +installed by a Dockerfile becomes reusable image content. An install hook +runs again for each sandbox you create from the kit. Put tool installations +and compilation in the build when they don't require sandbox-specific inputs. -For authenticated services, see -[Authenticate to external services](#authenticate-to-external-services). +### Build a workload -### Authenticate to external services +The descriptor declares `kind: workload`. Its Dockerfile sets the launch +configuration with `ENTRYPOINT`, `CMD`, `ENV`, `USER`, and `WORKDIR`. For +example, a shell workload can use this companion pair: -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. - -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: +```yaml {title="my-shell/my-shell.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: workload +provides: ["team-shell@1.0.0"] +``` -```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 +```dockerfile {title="my-shell/my-shell.dockerfile"} +FROM docker/sandbox-templates:shell +USER root +RUN apt-get update && apt-get install -y jq \ + && rm -rf /var/lib/apt/lists/* +USER agent +ENTRYPOINT ["bash"] +CMD [] ``` -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 sandbox templates provide the runtime's base requirements, including +Bash, Git, a CA certificate store, and the `agent` user with UID 1000. See +[Base image requirements](kit-reference.md#base-image-requirements) before +choosing another base. -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. +### Build a mixin -### Inject agent memory +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 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 copied binary must be compatible with the workload's architecture and +libraries. Declare relevant compatibility requirements with `requires` and +ship dependencies that the workload doesn't supply. See +[Build a tool overlay](kit-examples.md#build-a-tool-overlay). -```yaml -agentInstructions: - content: | - Ruff is installed. Run `ruff check` before committing. - Shared config lives at `/workspace/ruff.toml`. -``` +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. -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: +### Static files -```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 -``` +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. -See [`agentInstructions`](kit-reference.md#agent-instructions) in the spec -reference for the full field schema. +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. V3 doesn't automatically +inject a source directory named `files/home/` or `files/workspace/`. See +[Copy shared configuration](kit-examples.md#copy-shared-configuration). -### Define an agent +### Lifecycle hooks -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: +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: ```yaml -sandbox: - image: "my-registry/my-agent:latest" - entrypoint: [my-agent, "--yolo"] +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 ``` -See [Sandbox kits](#sandbox-kits) for use cases and an example. - -## Mixin kits - -A mixin kit extends an existing agent with extra capabilities. Common use -cases: +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. -- 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) +## Control network access -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. +Use `com.docker.runtime/network-policy@1` to declare network rules. The schema +separates `install` rules, intended for runtime install hooks, from `runtime` +rules for the workload. Neither block configures the Dockerfile build's network. -## Sandbox kits - -A sandbox kit defines a full agent from scratch — image, entrypoint, and -everything the agent needs. Common use cases: +```yaml +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: [api.example.com] + deny: [telemetry.example.com] +``` -- 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 +Network declarations compose across the selected kits. A deny rule takes +precedence over a kit allow rule. Kit rules also participate in the sandbox's +[policy precedence](../governance/concepts.md#precedence). -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). +See [Network policy](kit-reference.md#network-policy) for phase support and +pattern syntax. Use `sbx policy log` to investigate refused connections. -### Extend a built-in agent +## Authenticate to external services -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. +A credential capability declares the service and how the runtime presents its +credential. The user supplies the value on the host with +[`sbx secret set`](../configuration/credentials.md#stored-secrets). -## Using kits +```yaml +capabilities: + - type: com.docker.runtime/network-policy@1 + config: + runtime: + allow: [api.example.com] + - type: com.docker.runtime/credential@1 + config: + service: my-service + phase: runtime + apiKey: + name: MY_SERVICE_TOKEN + proxyManaged: true + inject: + - domain: api.example.com + header: Authorization + format: "Bearer %s" +``` -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. +With `proxyManaged: true`, the sandbox receives a sentinel value in +`MY_SERVICE_TOKEN`. The host proxy inserts the real secret in outbound requests +matching the injection rule. Every injection domain must also appear in the +same phase's network allow list. -Starting with Docker Sandboxes version 0.42.0, pass the sandbox kit reference -as the first argument: +Store the credential using the kit's service identifier: ```console -$ sbx run [PATH...] -$ sbx create [PATH...] +$ sbx secret set my-service ``` -The previous form, `sbx run --kit `, is -deprecated. +On the first interactive run, `sbx` also asks you to approve the service's +credential mechanism and domains. Storing a secret doesn't grant that approval. +Without a binding, the sandbox starts with the credential withheld. For +unattended runs, prepare the binding in advance. -> [!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. +See [Credentials](kit-reference.md#credentials) for API key and OAuth fields, +and [Credential configuration](../configuration/credentials.md) for host-side +storage and approval. -### Pass arguments to kits +## Compose kits -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`: +Composition validates the set you selected and orders providers before the +kits that require or integrate with them. Independent mixins are ordered by +reference. Reordering `--kit` flags isn't an override mechanism. -```console -$ sbx run ./my-agent/ --kit-arg channel=beta -``` +The workload supplies the root filesystem and launch configuration. Mixins +contribute overlays and runtime declarations. Files contributed by more than +one kit 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. -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. +V3 has no `extends` field. 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 it doesn't merge the +parent kit's descriptor into the child. To extend a workload without replacing +it, use a mixin. -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: +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 ./my-agent/ \ - --kit ./my-mixin/ \ - --kit-arg version=1.2.3 \ - --kit-arg my-mixin.version=2.0.0 -``` +## Pass arguments to kits + +Kit arguments use `${{ kit.args. }}` in the descriptor. They resolve in +one of two phases: -The kit-specific value takes precedence over the shared value for `my-mixin`. +| Declaration | Phase | Supply a value with | +| --- | --- | --- | +| `buildArg: VERSION` | Kit build | `docker buildx build --build-arg version=...` using the kit argument's name | +| `env: TOOL_MODE`, or neither mapping | Sandbox creation | `sbx run --kit-arg mode=...` | -Use `--kit-args-file` for a reusable set of `name=value` entries. Blank lines -and lines that start with `#` are ignored: +For example: -```text {title="kit.args"} -version=1.2.3 -my-mixin.channel=beta +```yaml +args: + mode: + default: check + enum: [check, fix] + env: TOOL_MODE ``` ```console -$ sbx create ./my-agent/ . \ - --kit ./my-mixin/ \ - --kit-args-file ./kit.args \ - --kit-arg my-mixin.channel=stable +$ sbx run ./my-agent --kit ./my-tool --kit-arg mode=fix . ``` -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. - -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. - -### Local +`env` exports the resolved value to the sandbox. Without `env`, a create-time +argument is available only through descriptor substitution. Build arguments +are validated and expanded before the kit is published. Changing a build +argument requires rebuilding the kit. -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: +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 ./my-agent/ -$ sbx create ../my-agent-1.0.zip . +$ sbx run ./my-agent --kit ./my-tool --kit-arg my-tool.mode=fix . ``` -Pass a local 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 ./my-mixin/ -$ sbx run claude --kit ../my-mixin-1.0.zip -``` +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. -While iterating on a supported mixin kit, apply changes to a running sandbox -with `sbx kit add`: +## Directory and build layout -```console -$ sbx kit add my-sandbox ./my-kit/ +A companion pair keeps the YAML descriptor separate from its Dockerfile recipe: + +```text +my-kit/ +├── my-kit.yaml +├── my-kit.dockerfile +├── context.md +└── files/ + └── settings.json ``` -`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. +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. + +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. -### Git repository +## Packaging and distribution -Launch a sandbox kit from a Git repository: +A published v3 kit is an OCI image. Use Docker Buildx to build and push it: ```console -$ sbx run "git+https://github.com/docker/sbx-kits-contrib.git#ref=v0.1.0&dir=amp" +$ docker login +$ docker buildx build ./my-kit -f ./my-kit/my-kit.yaml \ + -t docker.io//my-kit:1.0.0 --push ``` -Pass a Git-hosted mixin with `--kit`: +For a workload, launch the published reference. For a mixin, pass it with +`--kit`: ```console -$ sbx run claude --kit "git+https://github.com/docker/sbx-kits-contrib.git#ref=v0.1.0&dir=code-server" +$ sbx run docker.io//my-agent:1.0.0 \ + --kit docker.io//my-kit:1.0.0 . ``` -- `#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. - -### OCI registry - -Launch a sandbox kit from an OCI registry: +Build for both supported Linux architectures when distributing across machines: ```console -$ sbx run docker.io/sbx/droid-kit:latest +$ 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 ``` -Pass an OCI-hosted mixin with `--kit`: +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 the build-and-run development loop. The +`sbx kit pack`, `push`, and `pull` packaging commands belong to +[Kits v2](kits-v2/_index.md#packaging-and-distribution). + +You can also share source through Git. Select the kit directory with `dir` and +pin the source with `ref`: ```console -$ sbx run claude --kit ghcr.io/myorg/my-kit:1.0 +$ sbx run "git+https://github.com//.git#ref=&dir=my-agent" . ``` -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. +For private registries, configure +[Registry credentials](../configuration/credentials.md#registry-credentials). +Include `docker.io/` explicitly for Docker Hub references. ### Restrict kit sources -`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: - -```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. -``` - -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: +`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 settings set kit.allowedSources '["docker.io/","github.com/docker/"]' ``` -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. - -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: +Prefixes match at path-segment boundaries. Local source directories are +controlled separately by `kit.allowLocalKits`, which defaults to `true`: ```console $ sbx settings set kit.allowLocalKits false ``` -For non-interactive use, both settings have environment-variable equivalents: -`DOCKER_SANDBOXES_KIT_ALLOWED_SOURCES` and `DOCKER_SANDBOXES_KIT_ALLOW_LOCAL`. +For non-interactive configuration, use `DOCKER_SANDBOXES_KIT_ALLOWED_SOURCES` +and `DOCKER_SANDBOXES_KIT_ALLOW_LOCAL`. -## Sign and verify kits +### Sign and verify a published kit -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: +Sign the OCI image after pushing it: ```console -$ sbx kit sign ./my-kit/ -$ sbx kit verify \ - --certificate-identity user@example.com \ - --certificate-oidc-issuer https://accounts.google.com \ - ./my-kit/ +$ 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 ``` -For key-based signing, use an ECDSA P-256 key pair: +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`. -```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: +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 +472,26 @@ $ 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. -```console -$ sbx settings set kit.trustedSigners '[{"key":"/path/to/cosign.pub"}]' -$ sbx settings set kit.requireSignature true -``` +## Published format -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 image layers carry the workload filesystem or mixin overlay. The image +config carries environment and launch settings. The manifest annotation +`vnd.docker.runtime.kit.descriptor` carries the published descriptor as compact +JSON. Build-time argument values are resolved in this descriptor; create-time +values are resolved for each sandbox. -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. +Every kit also includes its published descriptor at +`/usr/share/runtime/kit//kit.yaml` and, when it has a recipe, that recipe +at `kit.dockerfile` in the same directory. This makes the source declarations +available for inspection inside the sandbox. Agent guidance supplied through +`contentFile` is staged in the image and referenced by its published path. -## Packaging and distribution +Ordinary Docker image tools can inspect and distribute the image. Running a +workload with `docker run` uses its image configuration, but doesn't apply the +kit's capability declarations or lifecycle hooks. Use `sbx` for those runtime +behaviors. A mixin is intended to be composed with a workload. -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 ...`. +See the [Kit spec reference](kit-reference.md) for the descriptor schema. diff --git a/content/manuals/ai/sandboxes/customize/templates.md b/content/manuals/ai/sandboxes/customize/templates.md index 48ac92cdd56..9aeb63e4fb2 100644 --- a/content/manuals/ai/sandboxes/customize/templates.md +++ b/content/manuals/ai/sandboxes/customize/templates.md @@ -12,6 +12,11 @@ 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. +A [v3 workload kit](kits.md#build-a-workload) can use a template as its +Dockerfile base and build additional content as part of the kit. Use kits when +you also need to distribute runtime declarations such as credentials, network +access, or lifecycle hooks with the image. + ## Custom templates Custom templates are reusable sandbox images that extend one of the built-in @@ -29,7 +34,7 @@ ask the agent to install what's needed. > 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). +> define a new agent from scratch, see [Build an agent](build-an-agent.md). ### Base images diff --git a/content/manuals/ai/sandboxes/faq.md b/content/manuals/ai/sandboxes/faq.md index a451158c5c6..ca3fef56037 100644 --- a/content/manuals/ai/sandboxes/faq.md +++ b/content/manuals/ai/sandboxes/faq.md @@ -114,23 +114,18 @@ 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, define a workload kit +whose Dockerfile launches the agent without its permission-skipping flag. For +example, use the following entrypoint in a Claude Code workload: + +```dockerfile +ENTRYPOINT ["claude"] ``` -Run it with `sbx run ./claude-safe/`. See -[Sandbox kits](customize/kits.md#sandbox-kits) for the full pattern. +Follow [Build an agent](customize/build-an-agent.md) for the complete v3 kit, +including its build and credential declarations. For a v2 kit that extends a +built-in agent, see +[Fork an existing agent](customize/kits-v2/kit-examples.md#fork-an-existing-agent). ## How do I know if my agent is running in a sandbox? From 19a9bb32665fa3447c08567cee188893511704f6 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 11 Sep 2026 14:31:24 +0000 Subject: [PATCH 2/6] docs: use positional sandbox kit references throughout Replace the outdated Mistral Vibe launch syntax and explain positional local, OCI, and Git workload references. Keep mixin usage distinct and point v2 examples at the matching documentation. --- content/guides/mistral-vibe-sandbox.md | 14 +++++++------- content/manuals/ai/sandboxes/customize/kits.md | 13 ++++++++++++- content/manuals/ai/sandboxes/troubleshooting.md | 7 ++++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/content/guides/mistral-vibe-sandbox.md b/content/guides/mistral-vibe-sandbox.md index 833abaa2492..c74348bb1f6 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). +For the v2 kit format used in this guide, 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/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 71595d41f0f..2004381f29e 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -38,7 +38,18 @@ access or agent instructions needs no Dockerfile. A mixin that ships a tool includes a build recipe. A workload always includes a build recipe because it supplies the sandbox's filesystem and launch configuration. -For example, with a local v3 workload and two local v3 mixins: +To run a workload kit, pass its reference as the first positional argument. +The reference can be a local directory, an OCI image, or a Git URL: + +```console +$ sbx run ./my-agent +$ sbx run docker.io//my-agent:1.0.0 +$ sbx run "git+https://github.com//.git#ref=&dir=my-agent" +``` + +The same positional syntax applies to `sbx create` and to v1 and v2 sandbox +kits. Use `--kit` only for mixins. For example, with a local v3 workload and +two local v3 mixins: ```console $ sbx run ./my-agent --name my-project --kit ./my-tool --kit ./team-config . diff --git a/content/manuals/ai/sandboxes/troubleshooting.md b/content/manuals/ai/sandboxes/troubleshooting.md index 0ecaa572e67..5ff94372790 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/kit-examples.md#install-an-internal-ca-certificate) for an example kit. Use a PEM-encoded certificate with a `.crt` extension. If traffic can be signed From 7f3369b0bcfa41c1296cc6ce829d39d7f5832708 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 11 Sep 2026 14:53:58 +0000 Subject: [PATCH 3/6] docs: introduce kits progressively across the v3 guides The draft introduced build requirements and schema mechanics before showing what a kit contains or how to run one. Lead with source files and a runnable workload, explain capabilities through examples, and defer composition and build rules. Order mixin examples by complexity, develop the agent descriptor step by step, move migration guidance after the reference, and preserve the signing anchor used by the homepage. --- .../manuals/ai/sandboxes/customize/_index.md | 30 +- .../ai/sandboxes/customize/build-an-agent.md | 111 +++-- .../ai/sandboxes/customize/kit-examples.md | 254 +++++----- .../ai/sandboxes/customize/kit-reference.md | 46 +- .../manuals/ai/sandboxes/customize/kits.md | 457 +++++++++--------- 5 files changed, 466 insertions(+), 432 deletions(-) diff --git a/content/manuals/ai/sandboxes/customize/_index.md b/content/manuals/ai/sandboxes/customize/_index.md index f3ef2e833ad..3c734e28a36 100644 --- a/content/manuals/ai/sandboxes/customize/_index.md +++ b/content/manuals/ai/sandboxes/customize/_index.md @@ -28,22 +28,6 @@ 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. -## Builds and runtime configuration - -Kits v3 include building as part of authoring a kit. Use the kit's Dockerfile -to install packages, compile tools, and copy static content into an image. -Use its descriptor to declare what the runtime must provide when the sandbox -runs, such as a credential, network access, or a startup hook. - -A workload kit can build on a sandbox template with Dockerfile `FROM`. The -template supplies the base environment, and the kit adds its content and -runtime declarations. A mixin can also build and ship its own tools as an -overlay, so adding a tool doesn't require rebuilding the workload image. - -Lifecycle install hooks remain available for initialization that needs a -sandbox's runtime inputs. Build steps produce content that sandboxes reuse; -install hooks initialize each sandbox separately. - ## Choose a customization | Goal | Option | @@ -57,3 +41,17 @@ install hooks initialize each sandbox separately. Use v3 for authoring kits. Docker Sandboxes also supports v1 and v2, whose `spec.yaml` workflow is documented in [Kits v2](kits-v2/_index.md). A single composition cannot combine v3 kits with v1 or v2 kits. + +## Builds and runtime configuration + +A kit starts as source files: a YAML descriptor for sandbox behavior and a +Dockerfile for software and files to package. Building them produces an image +you can share. Docker Sandboxes reads the descriptor when it runs the kit. + +For example, an agent kit can install the agent during its build, then request +access to its API and credentials when the sandbox runs. The installed agent +is reused across sandboxes, while each sandbox receives its own configuration. + +A workload kit can use a sandbox template as its base image. Mixins add tools +or behavior to that workload. See [Kits](kits.md) for the file layout, a +complete example, and how the parts work together. diff --git a/content/manuals/ai/sandboxes/customize/build-an-agent.md b/content/manuals/ai/sandboxes/customize/build-an-agent.md index cd42f393085..87d367c0516 100644 --- a/content/manuals/ai/sandboxes/customize/build-an-agent.md +++ b/content/manuals/ai/sandboxes/customize/build-an-agent.md @@ -19,8 +19,10 @@ configurable model, and an Anthropic API key held on the host. The same steps apply to other agents: build the software into an image, declare its runtime requirements, and provide instructions about the environment. -This example uses API-key authentication. For the complete schema, see the -[Kit spec reference](kit-reference.md). +This example uses API-key authentication. If you're starting with kits, read +[Kits](kits.md) for the file layout and the roles of a workload and a mixin. +The walkthrough builds up one kit, adding each part of its descriptor as it is +needed. For field definitions, see the [Kit spec reference](kit-reference.md). ## Prepare the kit directory @@ -47,7 +49,11 @@ the kit frontend find the recipe. ## Build the agent into the image -Create the companion Dockerfile: +First, define what goes into the image. The Dockerfile installs Claude Code +and launches it with a settings file. The descriptor you write next will +supply the agent version and create that settings file in the sandbox. + +Save the following as `claude-team/claude-team.dockerfile`: ```dockerfile {title="claude-team/claude-team.dockerfile"} FROM docker/sandbox-templates:shell @@ -83,9 +89,11 @@ same in every sandbox using this kit. BuildKit can cache that work. Reserve lifecycle install hooks for configuration that depends on an individual sandbox, such as registering a runtime endpoint. -## Declare the runtime requirements +## Describe the workload and its inputs -Create the descriptor: +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 @@ -104,14 +112,42 @@ args: enum: [sonnet, opus, haiku] provides: ["claude@${{ kit.args.version }}"] +``` + +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. + +The `model` input will be used in the settings file. It is chosen when creating +a sandbox, so selecting another model doesn't require rebuilding the agent. +## Allow access to the API + +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: + +```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: @@ -124,7 +160,22 @@ capabilities: - 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: @@ -132,40 +183,16 @@ capabilities: content: | {"model": "${{ kit.args.model }}"} mode: "0644" - - - type: com.docker.runtime/agent-context@1 - config: - filename: CLAUDE.md - contentFile: ./context.md ``` -The descriptor separates what the kit supplies from what it asks the runtime -to do: - -- `kind: workload` makes this kit the environment and launch command for - the sandbox. A composition has one workload. -- `provides` identifies the installed agent and version so other kits can - declare a dependency on Claude Code. -- `capabilities` requests network access, credential injection, settings - file creation, and agent instructions. - -The arguments resolve at different times. `version` has a `buildArg`, so -the frontend validates it, passes it to the Dockerfile as `CLAUDE_VERSION`, -and records the installed version in `provides`. `model` resolves when you -create a sandbox, so changing it doesn't require rebuilding the binary. - -The credential capability exposes a placeholder in `ANTHROPIC_API_KEY`. -The proxy substitutes the host's key in the `x-api-key` header for requests -to `api.anthropic.com`. The matching network allow entry permits those -requests. Declaring a credential doesn't store or grant access to a key. - -The network policy describes sandbox execution. Downloading Claude Code in -the Dockerfile is build-time work. An `install` network policy would apply -to lifecycle install hooks, rather than to Dockerfile `RUN` instructions. +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 -Create the context file: +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 @@ -181,10 +208,18 @@ Bash commands. Keep shell completion scripts out of that file because non-interactive commands also source it. ``` -The frontend includes this file in the kit image. At runtime, `sbx` adds an -entry to `CLAUDE.md` that points Claude Code to the kit's instructions. The -content stays in its own file, so composing more kits doesn't put all their -instructions into the main profile. +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` adds an entry +to the agent's `CLAUDE.md` profile that points to this file. Each kit's +instructions stay in their own file, so the agent can read them when needed. ## Store the key and run diff --git a/content/manuals/ai/sandboxes/customize/kit-examples.md b/content/manuals/ai/sandboxes/customize/kit-examples.md index 49aa29da372..ab8e009bbdb 100644 --- a/content/manuals/ai/sandboxes/customize/kit-examples.md +++ b/content/manuals/ai/sandboxes/customize/kit-examples.md @@ -16,12 +16,16 @@ weight: 40 These schema v3 examples show how to add tools, configuration, and instructions to a workload. Each example includes the files needed to run it locally. +Start with [Kits](kits.md) for the concepts and source layout. The examples +here progress from a mixin with one YAML file to files, hooks, and tool builds. For schema v2 patterns, see [Schema v2 kit examples](kits-v2/kit-examples.md). For field definitions, see the [Kit spec reference](kit-reference.md). ## Create a workload for the examples -Create a `shell-v3` directory and save this descriptor inside it: +The mixins need a workload to run with. Create a `shell-v3` directory with +these two files. The descriptor selects a shell workload and names `AGENTS.md` +as the profile used by the agent-instructions example: ```yaml {title="shell-v3/shell-v3.yaml"} # syntax=docker/runtime-kit:3 @@ -29,12 +33,6 @@ schemaVersion: "3" kind: workload displayName: Example shell -build: | - FROM docker/sandbox-templates:shell - USER agent - ENTRYPOINT ["bash"] - CMD ["-l"] - capabilities: - type: com.docker.runtime/agent-context@1 config: @@ -42,8 +40,16 @@ capabilities: content: This shell is an environment for testing composed kits. ``` -The inline `build` is a Dockerfile that produces a complete workload. Run it -from the parent directory, using your current directory as the workspace: +The Dockerfile supplies the shell environment: + +```dockerfile {title="shell-v3/shell-v3.dockerfile"} +FROM docker/sandbox-templates:shell +USER agent +ENTRYPOINT ["bash"] +CMD ["-l"] +``` + +Run it from the parent directory, using your current directory as the workspace: ```console $ sbx run --name kit-shell ./shell-v3 . @@ -54,6 +60,120 @@ sandbox name because adding kits requires creating a sandbox. Schema v3 mixins need a schema v3 workload. The built-in agent names use earlier kit formats and can't be combined with these mixins. +## 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 +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. +``` + +The example shell workload 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. + +```console +$ sbx run --name kit-team-review ./shell-v3 --kit ./team-review . +``` + +For longer instructions, set `contentFile: ./review.md` instead of `content` +and keep the Markdown file beside the descriptor. The build frontend stages +the file in the image. Only workload kits can set `filename`. + +## Write runtime configuration + +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 +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" +``` + +Create the `workspace-config` directory, save the descriptor, and supply the +project name when creating the sandbox: + +```console +$ sbx run --name kit-project ./shell-v3 --kit ./workspace-config \ + --kit-arg workspace-config.project=payments . +``` + +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. + +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). + +## Run a hook on every start + +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="start-log/start-log.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: mixin +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 +``` + +```console +$ sbx run --name kit-start-log ./shell-v3 --kit ./start-log . +``` + +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. + ## Copy shared configuration Ship static files in the image, then use a lifecycle install hook to copy @@ -222,120 +342,6 @@ true 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 [Kit composition](kits.md). - -## Write runtime configuration - -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 -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" -``` - -Create the `workspace-config` directory, save the descriptor, and supply the -project name when creating the sandbox: - -```console -$ sbx run --name kit-project ./shell-v3 --kit ./workspace-config \ - --kit-arg workspace-config.project=payments . -``` - -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. - -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]`, as in the shared configuration -example. - -## Run a hook on every start - -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="start-log/start-log.yaml"} -# syntax=docker/runtime-kit:3 -schemaVersion: "3" -kind: mixin -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 -``` - -```console -$ sbx run --name kit-start-log ./shell-v3 --kit ./start-log . -``` - -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. - -## 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 -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. -``` - -The example shell workload 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. - -```console -$ sbx run --name kit-team-review ./shell-v3 --kit ./team-review . -``` - -For longer instructions, set `contentFile: ./review.md` instead of `content` -and keep the Markdown file beside the descriptor. The build frontend stages -the file in the image. Only workload kits can set `filename`. +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 ed8516b82e7..ba6bd83fa34 100644 --- a/content/manuals/ai/sandboxes/customize/kit-reference.md +++ b/content/manuals/ai/sandboxes/customize/kit-reference.md @@ -29,29 +29,6 @@ and v2 kits. For `schemaVersion: "2"`, see the Each descriptor uses one grammar. V2 fields aren't accepted in a v3 descriptor. -### 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. - -| 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. - ## Descriptor fields A descriptor is a YAML document with `schemaVersion: "3"` and a `kind`. @@ -632,3 +609,26 @@ no recipe has a layer containing its descriptor. The published descriptor has a 512 KiB limit, with a build warning above 64 KiB. Keep substantial instruction text in `contentFile` and other content in image layers. For image composition and distribution, see [Kits](kits.md). + +## 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. + +| 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.md b/content/manuals/ai/sandboxes/customize/kits.md index 2004381f29e..276fce61dfb 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -25,56 +25,116 @@ v3 kits with v1 or v2 kits. ## Workloads and mixins -Every v3 sandbox composition contains exactly one workload kit and zero or more -mixin kits: +A sandbox runs one workload kit. You can add mixin kits to customize that +workload: | Kind | What it supplies | How you use it | | --- | --- | --- | -| `workload` | A complete root filesystem and the command to run, such as an agent or a shell | Pass it as the first argument to `sbx run` or `sbx create` | -| `mixin` | Additional files, tools, or runtime declarations | Add it with `--kit` | +| `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` | -Both kinds use the same descriptor schema. A mixin that only declares network -access or agent instructions needs no Dockerfile. A mixin that ships a tool -includes a build recipe. A workload always includes a build recipe because it -supplies the sandbox's filesystem and launch configuration. +For example, a team might use a Claude Code 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. -To run a workload kit, pass its reference as the first positional argument. -The reference can be a local directory, an OCI image, or a Git URL: +## Kit files and images + +When you author a kit, you work in a directory of source files. A typical kit +has a YAML file and a Dockerfile: + +```text +my-shell/ +├── my-shell.yaml +└── my-shell.dockerfile +``` + +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. + +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. + +### Build a workload + +For example, these two files define a shell workload with the `jq` tool +installed. The descriptor identifies it as a v3 workload: + +```yaml {title="my-shell/my-shell.yaml"} +# syntax=docker/runtime-kit:3 +schemaVersion: "3" +kind: workload +``` + +The Dockerfile starts from a sandbox template, installs `jq`, and sets Bash +as the command to run: + +```dockerfile {title="my-shell/my-shell.dockerfile"} +FROM docker/sandbox-templates:shell +USER root +RUN apt-get update && apt-get install -y jq \ + && rm -rf /var/lib/apt/lists/* +USER agent +ENTRYPOINT ["bash"] +CMD [] +``` + +The template supplies the sandbox's base environment, including the `agent` +user. The installation runs as root, then the Dockerfile switches back to +`agent` for the shell. For an agent workload, the Dockerfile would install +and launch the agent instead. See [Build an agent](build-an-agent.md) for a +complete walkthrough. + +## Run a kit + +Save the two files in `my-shell` and run this command from its parent +directory: ```console -$ sbx run ./my-agent -$ sbx run docker.io//my-agent:1.0.0 -$ sbx run "git+https://github.com//.git#ref=&dir=my-agent" +$ sbx run ./my-shell ``` -The same positional syntax applies to `sbx create` and to v1 and v2 sandbox -kits. Use `--kit` only for mixins. For example, with a local v3 workload and -two local v3 mixins: +`sbx` builds the kit and opens a sandbox shell with `jq` installed. Your current +directory is mounted as the workspace. Unchanged builds reuse cached results. + +The workload reference is a positional argument. It can also be a published +image or a Git URL: ```console -$ sbx run ./my-agent --name my-project --kit ./my-tool --kit ./team-config . +$ sbx run docker.io//my-shell:1.0.0 +$ sbx run "git+https://github.com//.git#ref=&dir=my-shell" ``` -`sbx` builds the source directories, checks that the kits can work together, -and assembles their content into the image the sandbox runs. Unchanged source -builds and compositions reuse cached results. For complete examples, see -[Kit examples](kit-examples.md). To build an agent workload, follow -[Build an agent](build-an-agent.md). +The same positional syntax applies to `sbx create` and to v1 and v2 sandbox +kits. Use `--kit` to add mixins when creating a sandbox: + +```console +$ sbx run ./my-shell --name shell-with-tools --kit ./my-tool --kit ./team-config . +``` -`--kit` applies when creating a sandbox. An existing sandbox with the same -name is reused, so use a different `--name` to try a different kit set or -source revision. Recreate the sandbox when you want to replace its composition. +`sbx` combines the workload and mixins into the sandbox's environment. This +combination is called a composition. An existing sandbox with the same name +is reused, so choose a different `--name` when trying a different kit set. +For complete mixins to try, see [Kit examples](kit-examples.md). ## Capabilities -Kits declare two kinds of contract. The distinction determines who supplies -what a kit needs. +A kit can include a tool in its image, but using that tool might also require +network access or a credential. The descriptor tells Docker Sandboxes what to +provide through its `capabilities` list. Capabilities also cover behavior such +as running setup commands and supplying instructions to an agent. -### Requests to the runtime +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. -The `capabilities` list describes what the kit needs the runtime to do. Each -entry has a namespaced, versioned `type` and a `config` specific to that type. -For example, this mixin requests outbound access to the GitHub API: +### Control network access + +This mixin permits requests to the GitHub API. Save it as +`github-access/github-access.yaml`: ```yaml {title="github-access/github-access.yaml"} # syntax=docker/runtime-kit:3 @@ -88,119 +148,68 @@ capabilities: allow: [api.github.com] ``` -Other capability types request credentials, volumes, ports, lifecycle hooks, -or agent instructions. The type's `@1` identifies its config schema. A -capability can evolve independently of the kit descriptor's `schemaVersion`. - -The capability contract distinguishes required requests from requests marked -`optional: true`. Mark a capability optional only when the kit can work without -it. Optional requests still participate in permission review when granted. - -Docker Sandboxes implements a subset of the capability schemas and doesn't -reject every unsupported required request. Check the support notes in the -reference before relying on a capability. - -For each type's fields and Docker Sandboxes support, see -[Runtime capabilities](kit-reference.md#runtime-capabilities). - -### Contracts between kits +The entry's `type` names the capability, and `config` contains its settings. +Here, `runtime.allow` lists a domain the running sandbox can reach. -Use `provides`, `requires`, `integrates`, and `conflicts` to describe how kits -fit together: +This kit needs only its YAML file: it configures network access without adding +software or files to the image. Run it with the shell workload: -```yaml -provides: ["my-tool@1.0.0"] -requires: ["node >= 22.0.0"] -integrates: ["docker-engine >= 25.0.0"] -conflicts: ["incompatible-tool"] +```console +$ sbx run ./my-shell --name shell-github --kit ./github-access ``` -This kit supplies `my-tool` version `1.0.0`, needs another selected kit to -provide Node.js version `22.0.0` or later, and can work with Docker Engine if -it is present at a compatible version. It rejects a composition that supplies -`incompatible-tool`. - -These names are declarations by kit authors. They don't install packages or -search a registry. You select the complete set of kits, and the runtime -validates it. Installing Node.js in a Dockerfile doesn't automatically declare -`provides: ["node@22.0.0"]`. - -See [Composition fields](kit-reference.md#composition-fields) for naming and -version rules. - -## Build content and runtime setup - -Choose where to put a customization based on when its inputs are available -and whether its result belongs in the reusable image or an individual sandbox. - -| Mechanism | When it runs | Use it for | -| --- | --- | --- | -| Dockerfile `RUN` and `COPY` | When the kit is built | Install tools, compile binaries, and package static content | -| Lifecycle `install` hooks | Once per sandbox, during creation | Initialize sandbox state using credentials, mounts, or other runtime inputs | -| Lifecycle `startup` hooks | Each sandbox start | Start a service or refresh state that must be restored after a restart | -| Lifecycle `files` | During sandbox setup | Write configuration with kit argument values or preserve an existing file with `overwrite: false` | - -Building a kit is separate from running its lifecycle hooks. A package -installed by a Dockerfile becomes reusable image content. An install hook -runs again for each sandbox you create from the kit. Put tool installations -and compilation in the build when they don't require sandbox-specific inputs. +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. -### Build a workload +The same pattern of `type` and `config` applies to other capabilities. Each +type has its own settings; `@1` identifies the version of those settings. +See [Runtime capabilities](kit-reference.md#runtime-capabilities) for the +available types and their Docker Sandboxes support, including limits on +required and optional requests. -The descriptor declares `kind: workload`. Its Dockerfile sets the launch -configuration with `ENTRYPOINT`, `CMD`, `ENV`, `USER`, and `WORKDIR`. For -example, a shell workload can use this companion pair: +### Authenticate to external services -```yaml {title="my-shell/my-shell.yaml"} -# syntax=docker/runtime-kit:3 -schemaVersion: "3" -kind: workload -provides: ["team-shell@1.0.0"] -``` +A credential capability names the service a kit needs and declares how to +authenticate to it. Users supply the secret on the host. For example, for a +service named `my-service`: -```dockerfile {title="my-shell/my-shell.dockerfile"} -FROM docker/sandbox-templates:shell -USER root -RUN apt-get update && apt-get install -y jq \ - && rm -rf /var/lib/apt/lists/* -USER agent -ENTRYPOINT ["bash"] -CMD [] +```console +$ sbx secret set my-service ``` -The sandbox templates provide the runtime's base requirements, including -Bash, Git, a CA certificate store, and the `agent` user with UID 1000. See -[Base image requirements](kit-reference.md#base-image-requirements) before -choosing another base. - -### Build a mixin +On the first interactive run, `sbx` also asks the user to approve how the kit +uses that credential. A kit can request proxy-managed authentication, where +the host proxy inserts the secret into outbound requests and the real value +stays outside the sandbox. -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. +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 copied binary must be compatible with the workload's architecture and -libraries. Declare relevant compatibility requirements with `requires` and -ship dependencies that the workload doesn't supply. See -[Build a tool overlay](kit-examples.md#build-a-tool-overlay). +## Build content and runtime setup -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. +The shell example installs `jq` while building the kit. Every sandbox using +that image starts with the tool available. Other setup needs information that +exists only when a sandbox runs, such as the mounted workspace path or a +host-provided credential. -### Static files +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. -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. +| 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 | -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. V3 doesn't automatically -inject a source directory named `files/home/` or `files/workspace/`. See -[Copy shared configuration](kit-examples.md#copy-shared-configuration). +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 @@ -224,95 +233,63 @@ capabilities: 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. +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. -## Control network access - -Use `com.docker.runtime/network-policy@1` to declare network rules. The schema -separates `install` rules, intended for runtime install hooks, from `runtime` -rules for the workload. Neither block configures the Dockerfile build's network. +### Static files -```yaml -capabilities: - - type: com.docker.runtime/network-policy@1 - config: - runtime: - allow: [api.example.com] - deny: [telemetry.example.com] -``` +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. -Network declarations compose across the selected kits. A deny rule takes -precedence over a kit allow rule. Kit rules also participate in the sandbox's -[policy precedence](../governance/concepts.md#precedence). +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). -See [Network policy](kit-reference.md#network-policy) for phase support and -pattern syntax. Use `sbx policy log` to investigate refused connections. +## Compose kits -## Authenticate to external services +Selecting a workload and mixins with `sbx run` is enough to combine them. When +a kit depends on another kit's tools, its descriptor can also declare that +relationship. -A credential capability declares the service and how the runtime presents its -credential. The user supplies the value on the host with -[`sbx secret set`](../configuration/credentials.md#stored-secrets). +For example, a tool kit can advertise what it supplies: ```yaml -capabilities: - - type: com.docker.runtime/network-policy@1 - config: - runtime: - allow: [api.example.com] - - type: com.docker.runtime/credential@1 - config: - service: my-service - phase: runtime - apiKey: - name: MY_SERVICE_TOKEN - proxyManaged: true - inject: - - domain: api.example.com - header: Authorization - format: "Bearer %s" +provides: ["my-tool@1.0.0"] ``` -With `proxyManaged: true`, the sandbox receives a sentinel value in -`MY_SERVICE_TOKEN`. The host proxy inserts the real secret in outbound requests -matching the injection rule. Every injection domain must also appear in the -same phase's network allow list. +A mixin that needs that tool can declare a requirement: -Store the credential using the kit's service identifier: - -```console -$ sbx secret set my-service +```yaml +requires: ["my-tool >= 1.0.0"] ``` -On the first interactive run, `sbx` also asks you to approve the service's -credential mechanism and domains. Storing a secret doesn't grant that approval. -Without a binding, the sandbox starts with the credential withheld. For -unattended runs, prepare the binding in advance. +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. -See [Credentials](kit-reference.md#credentials) for API key and OAuth fields, -and [Credential configuration](../configuration/credentials.md) for host-side -storage and approval. - -## Compose kits +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. -Composition validates the set you selected and orders providers before the -kits that require or integrate with them. Independent mixins are ordered by -reference. Reordering `--kit` flags isn't an override mechanism. +### Avoid conflicting customizations -The workload supplies the root filesystem and launch configuration. Mixins -contribute overlays and runtime declarations. Files contributed by more than -one kit 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. +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. -V3 has no `extends` field. 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 it doesn't merge the -parent kit's descriptor into the child. To extend a workload without replacing -it, use a mixin. +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. 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 @@ -320,15 +297,9 @@ changes to an existing sandbox. ## Pass arguments to kits -Kit arguments use `${{ kit.args. }}` in the descriptor. They resolve in -one of two phases: - -| Declaration | Phase | Supply a value with | -| --- | --- | --- | -| `buildArg: VERSION` | Kit build | `docker buildx build --build-arg version=...` using the kit argument's name | -| `env: TOOL_MODE`, or neither mapping | Sandbox creation | `sbx run --kit-arg mode=...` | - -For example: +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: ```yaml args: @@ -342,10 +313,11 @@ args: $ sbx run ./my-agent --kit ./my-tool --kit-arg mode=fix . ``` -`env` exports the resolved value to the sandbox. Without `env`, a create-time -argument is available only through descriptor substitution. Build arguments -are validated and expanded before the kit is published. Changing a build -argument requires rebuilding the kit. +`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. A bare argument name applies to every kit that declares it. To target one kit, prefix the name with its handle and a period: @@ -359,13 +331,21 @@ 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. +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). + 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. ## Directory and build layout -A companion pair keeps the YAML descriptor separate from its Dockerfile recipe: +The shell example uses two files with matching names. Kits can also include +configuration, scripts, and instructions that the Dockerfile copies into the +image. A larger directory might look like this: ```text my-kit/ @@ -388,6 +368,10 @@ 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, like the GitHub network example, 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 @@ -395,6 +379,23 @@ embed a descriptor in a Dockerfile comment block. See 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. + ## Packaging and distribution A published v3 kit is an OCI image. Use Docker Buildx to build and push it: @@ -458,7 +459,7 @@ $ sbx settings set kit.allowLocalKits false For non-interactive configuration, use `DOCKER_SANDBOXES_KIT_ALLOWED_SOURCES` and `DOCKER_SANDBOXES_KIT_ALLOW_LOCAL`. -### Sign and verify a published kit +### Sign and verify kits Sign the OCI image after pushing it: @@ -488,21 +489,15 @@ workflow. Publish and sign an OCI image when signatures are required. ## Published format -The image layers carry the workload filesystem or mixin overlay. The image -config carries environment and launch settings. The manifest annotation -`vnd.docker.runtime.kit.descriptor` carries the published descriptor as compact -JSON. Build-time argument values are resolved in this descriptor; create-time -values are resolved for each sandbox. - -Every kit also includes its published descriptor at -`/usr/share/runtime/kit//kit.yaml` and, when it has a recipe, that recipe -at `kit.dockerfile` in the same directory. This makes the source declarations -available for inspection inside the sandbox. Agent guidance supplied through -`contentFile` is staged in the image and referenced by its published path. +The image you publish 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. -Ordinary Docker image tools can inspect and distribute the image. Running a -workload with `docker run` uses its image configuration, but doesn't apply the -kit's capability declarations or lifecycle hooks. Use `sbx` for those runtime -behaviors. A mixin is intended to be composed with a workload. +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). -See the [Kit spec reference](kit-reference.md) for the descriptor schema. +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. From c7723aac3178bbc3186e1e81f6fcf7ef5ed96fcb Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 11 Sep 2026 15:11:49 +0000 Subject: [PATCH 4/6] docs: teach kits through practical v3 workflows The introductory shell workload gave readers little reason to use kits, while shared concepts remained in the preserved v2 guide. Use OpenCode with Ruff and review instructions for the introductory workload. Bring use cases, agent guidance, settings, environment configuration, debugging, and a skill example into v3, and focus v2 pages on their schema and behavior. --- .../ai/sandboxes/customize/kit-examples.md | 65 +++++ .../ai/sandboxes/customize/kits-v2/_index.md | 162 +++--------- .../customize/kits-v2/kit-examples.md | 6 +- .../manuals/ai/sandboxes/customize/kits.md | 249 ++++++++++++++---- 4 files changed, 303 insertions(+), 179 deletions(-) diff --git a/content/manuals/ai/sandboxes/customize/kit-examples.md b/content/manuals/ai/sandboxes/customize/kit-examples.md index ab8e009bbdb..e97a446638c 100644 --- a/content/manuals/ai/sandboxes/customize/kit-examples.md +++ b/content/manuals/ai/sandboxes/customize/kit-examples.md @@ -248,6 +248,71 @@ 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 + +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/`. + +Create a `docker-review` directory with a descriptor, a Dockerfile, and the +skill's Markdown file: + +```markdown {title="docker-review/SKILL.md"} +--- +name: docker-review +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: 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 +``` + +The Dockerfile packages the skill outside the mounted workspace: + +```dockerfile {title="docker-review/docker-review.dockerfile"} +FROM scratch +COPY SKILL.md /usr/share/docker-review/SKILL.md +``` + +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 + +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] +``` + +Use it with the Claude Code workload from [Build an agent](build-an-agent.md): + +```console +$ sbx run ./claude-team --name claude-review --kit ./docker-review +``` + +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. + ## Install an internal CA certificate If your organization uses a proxy that inspects HTTPS traffic, add its root diff --git a/content/manuals/ai/sandboxes/customize/kits-v2/_index.md b/content/manuals/ai/sandboxes/customize/kits-v2/_index.md index e76e3f9ee95..ff4b79c271f 100644 --- a/content/manuals/ai/sandboxes/customize/kits-v2/_index.md +++ b/content/manuals/ai/sandboxes/customize/kits-v2/_index.md @@ -1,7 +1,7 @@ --- title: Kits v2 linkTitle: Kits v2 -description: Extend a sandbox with tools, credentials, network rules, and configuration using declarative YAML artifacts. +description: Maintain schema v2 sandbox and mixin kits with the spec.yaml format, runtime setup, built-in agent inheritance, and v2 packaging commands. keywords: sandboxes, sbx, kits, mixins, customization, extensions, agents weight: 60 --- @@ -19,39 +19,20 @@ kits. > feature evolves. Share feedback and bug reports 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: +For kit concepts and use cases, see [What kits can do](../kits.md#what-kits-can-do). +The following sections describe the v2 fields and commands for maintaining +existing kits. -- 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 +V2 kits use a `spec.yaml` file. A `kind: sandbox` kit selects the agent's image +and launch configuration. A `kind: mixin` kit adds configuration to an agent. +Load a kit from a directory, ZIP file, OCI artifact, or Git URL. -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. - -A kit is either a mixin or a sandbox: - -- 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. - -## What kits can do +## V2 configuration ### Run commands -A kit can run commands inside the sandbox automatically. **Install -commands** run once at creation; **startup commands** run each time -the sandbox starts. - -Install commands are the place to put anything an agent needs into the -image, via `apt`, `pip`, `npm`, `curl | bash`, or whatever fits: +V2 declares runtime commands under `setup`. `setup.install` runs once at +sandbox creation and can install software: ```yaml setup: @@ -59,9 +40,8 @@ setup: - command: "apt-get update && apt-get install -y jq" ``` -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: +`setup.startup` runs on each start alongside the agent. Commands must tolerate +repeated execution. See [`startup`](kit-reference.md#startup) for its fields: ```yaml setup: @@ -72,13 +52,8 @@ setup: ### Inject files -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. - -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. +V2 places bundled files from `files/home/` in the agent's home directory and +from `files/workspace/` in the workspace: ```text my-kit/ @@ -133,8 +108,7 @@ initialization because startup commands don't gate the agent entrypoint. ### Set environment variables -Environment variables set by the kit are available to the agent at -runtime: +Declare environment variables under `environment.variables`: ```yaml environment: @@ -147,21 +121,13 @@ For credentials, see 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). +Leave proxy variables to the sandbox. See +[Set environment variables](../kits.md#set-environment-variables) for shared +environment and proxy guidance. ### Control network access -Network rules define which domains the sandbox can reach or block. Kit -network rules apply only to sandboxes that use the kit: +V2 declares network rules under `permissions.network`: ```yaml permissions: @@ -173,32 +139,14 @@ permissions: - telemetry.example.com ``` -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. - -> [!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). - -For authenticated services, see -[Authenticate to external services](#authenticate-to-external-services). +Kit deny rules take precedence over kit allow rules. Organization governance +can also override kit allow rules. See [Control network access](../kits.md#control-network-access) +for policy behavior shared by kit versions. ### Authenticate to external services -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. - -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: +V2 declares credentials in a top-level `credentials` list. The injection +domain must also be allowed by `permissions.network`: ```yaml credentials: @@ -217,23 +165,14 @@ permissions: - api.example.com # the domain must also be reachable ``` -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. - -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. +See [Credential configuration](../../configuration/credentials.md) for storing +secrets and approving credential bindings. The proxy behavior is shared with +v3; the descriptor fields differ. ### Inject agent memory -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. +V2 uses `agentInstructions` for guidance such as tool usage and project +conventions: ```yaml agentInstructions: @@ -273,17 +212,12 @@ sandbox: entrypoint: [my-agent, "--yolo"] ``` -See [Sandbox kits](#sandbox-kits) for use cases and an example. +See [Sandbox kits](#sandbox-kits) for inheritance from a built-in agent. ## Mixin kits -A mixin kit extends an existing agent with extra capabilities. Common use -cases: - -- 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) +A v2 `kind: mixin` kit extends a built-in agent or a v2 sandbox kit. Pass it +with `--kit` when creating the sandbox. 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) @@ -291,14 +225,6 @@ for complete mixin examples. ## Sandbox kits -A sandbox kit defines a full agent from scratch — image, entrypoint, and -everything the agent needs. Common use cases: - -- 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 - 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 walkthrough of the v3 workload format, see @@ -614,23 +540,7 @@ agent instructions, and the sandbox block — see [Kit spec reference](kit-refer ## 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 ...`. +See [Debug kits](../kits.md#debug-kits) for network policy logs, inspecting +files and tools inside the sandbox, and capturing background-service output. +Use `sbx kit validate` and `sbx kit inspect` to check a v2 descriptor before +launching it. diff --git a/content/manuals/ai/sandboxes/customize/kits-v2/kit-examples.md b/content/manuals/ai/sandboxes/customize/kits-v2/kit-examples.md index a51371c235f..5430142f960 100644 --- a/content/manuals/ai/sandboxes/customize/kits-v2/kit-examples.md +++ b/content/manuals/ai/sandboxes/customize/kits-v2/kit-examples.md @@ -20,9 +20,9 @@ kits. > [docker/sbx-releases](https://github.com/docker/sbx-releases) repository. 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). +single v2 kit pattern. For v3 examples of tools, shared configuration, hooks, +and agent skills, see [Kit examples](../kit-examples.md). For the v2 field +definitions, see [Kit spec reference](kit-reference.md). ## Drop a shared config file diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 276fce61dfb..8483e57c7a0 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -33,19 +33,35 @@ workload: | `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` | -For example, a team might use a Claude Code workload with a mixin that adds a +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. +## What kits can do + +Use kits to give agents a repeatable working environment and share it with +your team: + +- 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. + ## Kit files and images When you author a kit, you work in a directory of source files. A typical kit has a YAML file and a Dockerfile: ```text -my-shell/ -├── my-shell.yaml -└── my-shell.dockerfile +opencode-python/ +├── opencode-python.yaml +└── opencode-python.dockerfile ``` The YAML file is the kit's descriptor. It identifies the kit as a workload or @@ -60,59 +76,102 @@ sandbox. ### Build a workload -For example, these two files define a shell workload with the `jq` tool -installed. The descriptor identifies it as a v3 workload: +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. + +The Dockerfile starts from Docker's OpenCode template and installs Ruff: + +```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 [] +``` + +The template supplies OpenCode, Python, uv, and the `agent` user. Ruff is +installed during the build, so it is ready when the agent starts. -```yaml {title="my-shell/my-shell.yaml"} +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 -``` -The Dockerfile starts from a sandbox template, installs `jq`, and sets Bash -as the command to run: - -```dockerfile {title="my-shell/my-shell.dockerfile"} -FROM docker/sandbox-templates:shell -USER root -RUN apt-get update && apt-get install -y jq \ - && rm -rf /var/lib/apt/lists/* -USER agent -ENTRYPOINT ["bash"] -CMD [] +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 template supplies the sandbox's base environment, including the `agent` -user. The installation runs as root, then the Dockerfile switches back to -`agent` for the shell. For an agent workload, the Dockerfile would install -and launch the agent instead. See [Build an agent](build-an-agent.md) for a -complete walkthrough. +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. The +following sections explain how these declarations work. + +This example uses an existing agent image. To install an agent yourself and +configure its version and model, see [Build an agent](build-an-agent.md). ## Run a kit -Save the two files in `my-shell` and run this command from its parent -directory: +Save the two files in `opencode-python`. Store an Anthropic API key on the +host, then launch the kit from its parent directory against a Python project: ```console -$ sbx run ./my-shell +$ sbx secret set anthropic +$ sbx run ./opencode-python ``` -`sbx` builds the kit and opens a sandbox shell with `jq` installed. Your current -directory is mounted as the workspace. Unchanged builds reuse cached results. +Approve the kit's credential request when prompted, then select an Anthropic +model in OpenCode. Ask it to review a Python module or make a change. It has +Ruff available and instructions to run it before reporting completion. + +`sbx` builds the kit and mounts your project as the workspace. Unchanged +builds reuse cached results. See +[Credential configuration](../configuration/credentials.md) for storing keys +and approving their use. The workload reference is a positional argument. It can also be a published image or a Git URL: ```console -$ sbx run docker.io//my-shell:1.0.0 -$ sbx run "git+https://github.com//.git#ref=&dir=my-shell" +$ sbx run docker.io//opencode-python:1.0.0 +$ sbx run "git+https://github.com//.git#ref=&dir=opencode-python" ``` The same positional syntax applies to `sbx create` and to v1 and v2 sandbox kits. Use `--kit` to add mixins when creating a sandbox: ```console -$ sbx run ./my-shell --name shell-with-tools --kit ./my-tool --kit ./team-config . +$ sbx run ./opencode-python --name python-with-tools --kit ./my-tool --kit ./team-config . ``` `sbx` combines the workload and mixins into the sandbox's environment. This @@ -122,10 +181,10 @@ For complete mixins to try, see [Kit examples](kit-examples.md). ## Capabilities -A kit can include a tool in its image, but using that tool might also require -network access or a credential. The descriptor tells Docker Sandboxes what to -provide through its `capabilities` list. Capabilities also cover behavior such -as running setup commands and supplying instructions to an agent. +The OpenCode example uses three capabilities: network access, credentials, +and agent instructions. Each entry in `capabilities` tells Docker Sandboxes +what to provide when running the kit. Other capabilities cover behavior such +as running setup commands or publishing a development server's port. 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 @@ -152,10 +211,10 @@ The entry's `type` names the capability, and `config` contains its settings. Here, `runtime.allow` lists a domain the running sandbox can reach. This kit needs only its YAML file: it configures network access without adding -software or files to the image. Run it with the shell workload: +software or files to the image. Run it with the OpenCode workload: ```console -$ sbx run ./my-shell --name shell-github --kit ./github-access +$ sbx run ./opencode-python --name python-github --kit ./github-access ``` Network rules from the selected kits combine. A kit's `deny` entries take @@ -163,6 +222,10 @@ 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. +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. + The same pattern of `type` and `config` applies to other capabilities. Each type has its own settings; `@1` identifies the version of those settings. See [Runtime capabilities](kit-reference.md#runtime-capabilities) for the @@ -172,25 +235,50 @@ required and optional requests. ### Authenticate to external services A credential capability names the service a kit needs and declares how to -authenticate to it. Users supply the secret on the host. For example, for a -service named `my-service`: - -```console -$ sbx secret set my-service -``` +authenticate to it. In the OpenCode example, `service: anthropic` matches the +key you store with `sbx secret set anthropic`. -On the first interactive run, `sbx` also asks the user to approve how the kit -uses that credential. A kit can request proxy-managed authentication, where -the host proxy inserts the secret into outbound requests and the real value -stays outside the sandbox. +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. 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. +### Give the agent instructions + +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. + +A mixin can contribute instructions through the agent-context capability: + +```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. +``` + +The workload chooses the instruction filename, such as `AGENTS.md` or +`CLAUDE.md`. Docker Sandboxes adds an index of kit instructions to that file. +Each kit's instructions stay in a separate file that the agent can read when +needed. Use `contentFile` to keep longer guidance in a Markdown file beside +the descriptor. See [Contribute agent instructions](kit-examples.md#contribute-agent-instructions). + +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 -The shell example installs `jq` while building the kit. Every sandbox using +The OpenCode example installs Ruff while building the kit. Every sandbox using that image starts with the tool available. Other setup needs information that exists only when a sandbox runs, such as the mounted workspace path or a host-provided credential. @@ -248,6 +336,44 @@ 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). +### Customize agent settings + +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`. + +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. + +### Set environment variables + +Use Dockerfile `ENV` for tool settings that apply to every sandbox using the +kit. For example, make Python write output without buffering: + +```dockerfile +ENV PYTHONUNBUFFERED=1 +``` + +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. + +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). + +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. + ## Compose kits Selecting a workload and mixins with `sbx run` is enough to combine them. When @@ -343,7 +469,7 @@ sandbox state. Use credential capabilities for secrets. See ## Directory and build layout -The shell example uses two files with matching names. Kits can also include +The OpenCode example uses two files with matching names. Kits can also include configuration, scripts, and instructions that the Dockerfile copies into the image. A larger directory might look like this: @@ -501,3 +627,26 @@ For the image annotations and file layout, see 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 exec -- which +$ sbx exec -- cat /home/agent/.config//settings.json +$ sbx policy log +``` + +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. + +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). + +To test changes to a v3 kit, create a sandbox with a different name. Reusing +an existing sandbox keeps its recorded kit configuration. From 6d039afea1a8be21e1b8e1bebdedc38b26162331 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 11 Sep 2026 15:22:19 +0000 Subject: [PATCH 5/6] docs: separate kit usage from the workload example The Run a kit section continued the OpenCode example and mixed basic usage with credentials, source formats, composition, and sandbox reuse. Lead with general run syntax and group sources, mixins, and naming into dedicated subsections. Keep credential setup in the authentication section and remove agent-specific tutorial steps from usage guidance. --- .../manuals/ai/sandboxes/customize/kits.md | 78 ++++++++++++------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index 8483e57c7a0..bd030e8f774 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -134,50 +134,72 @@ capabilities: 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. The -following sections explain how these declarations work. +entry names the service; you store the actual API key on your host. This example uses an existing agent image. To install an agent yourself and configure its version and model, see [Build an agent](build-an-agent.md). ## Run a kit -Save the two files in `opencode-python`. Store an Anthropic API key on the -host, then launch the kit from its parent directory against a Python project: +Pass a workload kit reference to `sbx run`, followed by the project directory +to use as the sandbox's workspace: ```console -$ sbx secret set anthropic -$ sbx run ./opencode-python +$ sbx run ``` -Approve the kit's credential request when prompted, then select an Anthropic -model in OpenCode. Ask it to review a Python module or make a change. It has -Ruff available and instructions to run it before reporting completion. +To create a sandbox without launching the workload, use `sbx create` with +the same arguments. + +### Choose a kit source -`sbx` builds the kit and mounts your project as the workspace. Unchanged -builds reuse cached results. See -[Credential configuration](../configuration/credentials.md) for storing keys -and approving their use. +The workload reference can point to a local directory, a published image, +or a kit in a Git repository: -The workload reference is a positional argument. It can also be a published -image or a Git URL: +| Source | Example reference | +| --- | --- | +| Local directory | `./my-agent` | +| Published image | `docker.io//my-agent:1.0.0` | +| 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 docker.io//opencode-python:1.0.0 -$ sbx run "git+https://github.com//.git#ref=&dir=opencode-python" +$ sbx run "git+https://github.com//.git#ref=&dir=my-agent" ``` -The same positional syntax applies to `sbx create` and to v1 and v2 sandbox -kits. Use `--kit` to add mixins when creating a sandbox: +`sbx` pulls published images and builds local or Git sources when creating +the sandbox. Unchanged source builds reuse cached results. See +[Packaging and distribution](#packaging-and-distribution) for publishing kits +and configuring access to remote sources. + +### Add mixins + +A sandbox runs one workload kit. Add mixins with `--kit`, repeating the flag +for each one: ```console -$ sbx run ./opencode-python --name python-with-tools --kit ./my-tool --kit ./team-config . +$ sbx run ./my-agent --kit ./linter --kit ./team-config ``` -`sbx` combines the workload and mixins into the sandbox's environment. This -combination is called a composition. An existing sandbox with the same name -is reused, so choose a different `--name` when trying a different kit set. -For complete mixins to try, see [Kit examples](kit-examples.md). +Mixin references accept the same source types as workload references. 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. + +### Name and reuse a sandbox + +Use `--name` to give the sandbox a name: + +```console +$ sbx run ./my-agent --name my-project +``` + +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. ## Capabilities @@ -235,8 +257,12 @@ required and optional requests. ### Authenticate to external services A credential capability names the service a kit needs and declares how to -authenticate to it. In the OpenCode example, `service: anthropic` matches the -key you store with `sbx secret set anthropic`. +authenticate to it. The OpenCode example declares `service: anthropic`. Store +its API key on the host using that service name: + +```console +$ sbx secret set anthropic +``` The example sets `proxyManaged: true`. OpenCode receives a placeholder in `ANTHROPIC_API_KEY`, and the host proxy inserts the real key into requests to From 3d92c6de4a7962394ae090bebc06ecc1da7565a8 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 11 Sep 2026 15:37:11 +0000 Subject: [PATCH 6/6] docs: make kit topics independently navigable Several guide sections assumed readers had followed the OpenCode example, and individual recipes depended on an unlinked sample workload. Lead topics with their concepts, link supporting examples and recipe prerequisites, and point reference readers to specific guide sections. Group registry publishing and Git sharing by task and keep launch syntax in the usage section. --- .../ai/sandboxes/customize/kit-examples.md | 40 ++++--- .../ai/sandboxes/customize/kit-reference.md | 8 +- .../manuals/ai/sandboxes/customize/kits.md | 103 ++++++++++-------- 3 files changed, 87 insertions(+), 64 deletions(-) diff --git a/content/manuals/ai/sandboxes/customize/kit-examples.md b/content/manuals/ai/sandboxes/customize/kit-examples.md index e97a446638c..ebaf89f66bf 100644 --- a/content/manuals/ai/sandboxes/customize/kit-examples.md +++ b/content/manuals/ai/sandboxes/customize/kit-examples.md @@ -15,17 +15,21 @@ weight: 40 > repository. These schema v3 examples show how to add tools, configuration, and instructions -to a workload. Each example includes the files needed to run it locally. -Start with [Kits](kits.md) for the concepts and source layout. The examples -here progress from a mixin with one YAML file to files, hooks, and tool builds. +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. + +For concepts and source layout, see [Kits](kits.md). For schema v2 patterns, see [Schema v2 kit examples](kits-v2/kit-examples.md). For field definitions, see the [Kit spec reference](kit-reference.md). ## Create a workload for the examples -The mixins need a workload to run with. Create a `shell-v3` directory with -these two files. The descriptor selects a shell workload and names `AGENTS.md` -as the profile used by the agent-instructions example: +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 @@ -55,9 +59,9 @@ Run it from the parent directory, using your current directory as the workspace: $ sbx run --name kit-shell ./shell-v3 . ``` -Use this workload with the mixins that follow. Each example uses a different -sandbox name because adding kits requires creating a sandbox. Schema v3 -mixins need a schema v3 workload. The built-in agent names use earlier kit +Each example uses a different sandbox name because selecting a different kit +set requires creating a sandbox. Schema v3 mixins need a schema v3 workload. +The built-in agent names use earlier kit formats and can't be combined with these mixins. ## Contribute agent instructions @@ -82,8 +86,9 @@ capabilities: project checks before reporting completion. ``` -The example shell workload chooses `AGENTS.md`. When you compose this mixin, -`sbx` adds a kit entry to that profile and puts the instructions in a separate +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. @@ -122,8 +127,9 @@ capabilities: mode: "0644" ``` -Create the `workspace-config` directory, save the descriptor, and supply the -project name when creating the sandbox: +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: ```console $ sbx run --name kit-project ./shell-v3 --kit ./workspace-config \ @@ -162,6 +168,8 @@ capabilities: description: Record each sandbox start ``` +Run the mixin with the [sample shell workload](#create-a-workload-for-the-examples): + ```console $ sbx run --name kit-start-log ./shell-v3 --kit ./start-log . ``` @@ -233,7 +241,7 @@ capabilities: description: Copy team defaults into the workspace and home ``` -Run it with the example workload: +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 . @@ -345,6 +353,8 @@ 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 . ``` @@ -394,6 +404,8 @@ 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. +Run the mixin with the [sample shell workload](#create-a-workload-for-the-examples): + ```console $ sbx run --name kit-gojq ./shell-v3 --kit ./gojq . ``` diff --git a/content/manuals/ai/sandboxes/customize/kit-reference.md b/content/manuals/ai/sandboxes/customize/kit-reference.md index ba6bd83fa34..b0b71cbcd20 100644 --- a/content/manuals/ai/sandboxes/customize/kit-reference.md +++ b/content/manuals/ai/sandboxes/customize/kit-reference.md @@ -223,8 +223,9 @@ COPY review-checklist.md /usr/local/share/team/review-checklist.md A comment descriptor can't also declare `build` or `dockerfile`. Dockerfile semantics apply to every recipe, including multi-stage builds and -build mounts. For directory layouts, build commands, and static files, see -[Kits](kits.md). +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. ### Base image requirements @@ -608,7 +609,8 @@ no recipe has a layer containing its descriptor. The published descriptor has a 512 KiB limit, with a build warning above 64 KiB. Keep substantial instruction text in `contentFile` and other content -in image layers. For image composition and distribution, see [Kits](kits.md). +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 diff --git a/content/manuals/ai/sandboxes/customize/kits.md b/content/manuals/ai/sandboxes/customize/kits.md index bd030e8f774..d5f720765f5 100644 --- a/content/manuals/ai/sandboxes/customize/kits.md +++ b/content/manuals/ai/sandboxes/customize/kits.md @@ -203,19 +203,26 @@ mixin set, choose another name or recreate the sandbox with the desired kits. ## Capabilities -The OpenCode example uses three capabilities: network access, credentials, -and agent instructions. Each entry in `capabilities` tells Docker Sandboxes -what to provide when running the kit. Other capabilities cover behavior such -as running setup commands or publishing a development server's port. +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. 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 -This mixin permits requests to the GitHub API. Save it as -`github-access/github-access.yaml`: +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 @@ -229,11 +236,14 @@ capabilities: allow: [api.github.com] ``` -The entry's `type` names the capability, and `config` contains its settings. -Here, `runtime.allow` lists a domain the running sandbox can reach. +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. This kit needs only its YAML file: it configures network access without adding -software or files to the image. Run it with the OpenCode workload: +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 @@ -248,17 +258,12 @@ 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. -The same pattern of `type` and `config` applies to other capabilities. Each -type has its own settings; `@1` identifies the version of those settings. -See [Runtime capabilities](kit-reference.md#runtime-capabilities) for the -available types and their Docker Sandboxes support, including limits on -required and optional requests. - ### Authenticate to external services A credential capability names the service a kit needs and declares how to -authenticate to it. The OpenCode example declares `service: anthropic`. Store -its API key on the host using that service name: +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 @@ -304,10 +309,9 @@ where that agent looks for them. See ## Build content and runtime setup -The OpenCode example installs Ruff while building the kit. Every sandbox using -that image starts with the tool available. Other setup needs information that -exists only when a sandbox runs, such as the mounted workspace path or a -host-provided credential. +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 @@ -402,9 +406,9 @@ of it because they can fail in non-interactive shells. ## Compose kits -Selecting a workload and mixins with `sbx run` is enough to combine them. When -a kit depends on another kit's tools, its descriptor can also declare that -relationship. +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. For example, a tool kit can advertise what it supplies: @@ -495,9 +499,10 @@ sandbox state. Use credential capabilities for secrets. See ## Directory and build layout -The OpenCode example uses two files with matching names. Kits can also include -configuration, scripts, and instructions that the Dockerfile copies into the -image. A larger directory might look like this: +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: ```text my-kit/ @@ -522,7 +527,8 @@ 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, like the GitHub network example, can omit it. +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 @@ -550,7 +556,13 @@ configuration. ## Packaging and distribution -A published v3 kit is an OCI image. Use Docker Buildx to build and push it: +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. + +### Publish an image + +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 $ docker login @@ -558,13 +570,10 @@ $ docker buildx build ./my-kit -f ./my-kit/my-kit.yaml \ -t docker.io//my-kit:1.0.0 --push ``` -For a workload, launch the published reference. For a mixin, pass it with -`--kit`: - -```console -$ sbx run docker.io//my-agent:1.0.0 \ - --kit docker.io//my-kit:1.0.0 . -``` +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: @@ -576,20 +585,20 @@ $ docker buildx build ./my-kit -f ./my-kit/my-kit.yaml \ 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 the build-and-run development loop. The -`sbx kit pack`, `push`, and `pull` packaging commands belong to -[Kits v2](kits-v2/_index.md#packaging-and-distribution). +source directory to `sbx` for development. The `sbx kit pack`, `push`, and +`pull` packaging commands belong to [Kits v2](kits-v2/_index.md#packaging-and-distribution). -You can also share source through Git. Select the kit directory with `dir` and -pin the source with `ref`: +### Share source through Git -```console -$ sbx run "git+https://github.com//.git#ref=&dir=my-agent" . +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 private registries, configure -[Registry credentials](../configuration/credentials.md#registry-credentials). -Include `docker.io/` explicitly for Docker Hub references. +Docker Sandboxes builds the source when a consumer creates a sandbox from +that reference. ### Restrict kit sources @@ -641,7 +650,7 @@ workflow. Publish and sign an OCI image when signatures are required. ## Published format -The image you publish contains both the kit's content and its descriptor. +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.