docs: split kits spec reference into its own page

Move the spec.yaml field reference out of the Kits overview page into a
new Kit spec reference page, keeping the overview focused on concepts and
usage. Rewire cross-page links accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
David Karlsson
2026-06-12 10:25:23 +02:00
parent caa2754dfe
commit d5799d8e59
4 changed files with 343 additions and 321 deletions
@@ -18,8 +18,8 @@ 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 [Kits](kits.md) page. This tutorial
focuses on the journey.
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) —
@@ -28,7 +28,7 @@ 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](kits.md#base-image-requirements): non-root
[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.
@@ -17,7 +17,7 @@ weight: 25
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 [Kits](kits.md).
spec reference, see [Kit spec reference](kit-reference.md).
## Drop a shared config file
@@ -0,0 +1,330 @@
---
title: Kit spec reference
linkTitle: Spec reference
description: Field-by-field reference for a kit's spec.yaml — credentials, network rules, environment, commands, files, memory, and the agent block.
keywords: sandboxes, sbx, kits, spec.yaml, reference, schema, fields
weight: 22
---
{{< 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.
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).
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/
```
## Top-level fields
```yaml
schemaVersion: "1"
kind: <mixin | agent>
name: <name>
displayName: <name>
description: <text>
```
| Field | Required | Description |
| --------------- | -------- | ------------------------------------------------------------------------ |
| `schemaVersion` | Yes | Spec schema version. Set to `"1"`. |
| `kind` | Yes | `mixin` for kits that extend an agent; `agent` for kits that define one. |
| `name` | Yes | Unique identifier. Lowercase, alphanumeric, hyphens. |
| `displayName` | No | Human-readable name. |
| `description` | No | Short description. |
The sections below apply to both kinds. Agent kits also declare an
[`agent:` block](#agent-block).
## Credentials
```yaml
credentials:
sources:
<service-id>:
env: [<env-var>, ...]
file:
path: <path>
parser: <parser>
priority: <env-first | file-first>
```
| Field | Description |
| -------------------------- | ------------------------------------------------------------- |
| `sources` | Map of service identifier to credential source. |
| `sources.<id>.env` | Environment variables to read on the host, in priority order. |
| `sources.<id>.file.path` | Path on host. `~` expands to home directory. |
| `sources.<id>.file.parser` | How to extract the credential value from the file. |
| `sources.<id>.priority` | `env-first` (default) or `file-first`. |
Service identifiers link credentials to [network rules](#network).
### file.parser
`file.parser` tells the proxy how to extract a credential from the file at `file.path`.
Omit it for plain-text files; set it to `json:<dot.path>` to extract a field from a JSON file.
| Value | Behavior |
| ----------------- | ------------------------------------------------------------------------------------ |
| omitted or empty | Reads the entire file as the credential. Leading and trailing whitespace is trimmed. |
| `json:<dot.path>` | Parses the file as JSON and returns the value at the dot-separated path. |
| any other value | Rejected — `unsupported parser: <value>`. |
For `json:` paths, segments are separated by `.` (for example, `json:credentials.github.token`).
Only object keys can be navigated — arrays are not supported and there is no `[0]`-style indexing.
Keys that contain a literal `.` cannot be referenced. The resolved value must be a string, number,
or boolean; numbers and booleans are converted to strings. Objects, arrays, and null are rejected.
When a source has both `env` and `file` defined, `priority` controls which is tried first. The
preferred source is used when it exists — the environment variable is set, or the file is
present on disk. If it doesn't, the other source is used instead. The choice is made once at
discovery time, so parser errors (missing JSON field, wrong value type, invalid JSON) surface
as errors rather than triggering a fallback.
Plain-text token file:
```yaml
credentials:
sources:
openai:
file:
path: "~/.openai/token"
```
Nested JSON field, with an environment variable as fallback:
```yaml
credentials:
sources:
github:
env:
- GH_TOKEN
file:
path: "~/.config/myapp/creds.json"
parser: "json:credentials.github.token"
priority: file-first
```
Given `~/.config/myapp/creds.json`:
```json
{
"credentials": {
"github": { "token": "ghp_xyz", "expires": "2026-12-31" }
}
}
```
The proxy resolves the credential to `ghp_xyz`, falling back to `GH_TOKEN` if the file is
missing. If the file exists but the JSON path doesn't resolve, the request fails with the
parser error below instead of falling back.
Common errors when using `json:` parsers:
| Error message | Cause |
| --------------------------------------------- | ------------------------------------------------------------------- |
| `field 'X' not found in JSON` | The path doesn't exist in the file. |
| `cannot navigate to field 'X': not an object` | A path segment hit a string, array, or scalar instead of an object. |
| `field 'X' is not a string value` | The resolved value is an object, array, or null. |
| `failed to parse JSON: ...` | The file is not valid JSON. |
## Network
```yaml
network:
allowedDomains: [<domain>, ...]
deniedDomains: [<domain>, ...]
serviceDomains:
<domain>: <service-id>
serviceAuth:
<service-id>:
headerName: <header>
valueFormat: <format>
```
| Field | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `allowedDomains` | Domains the sandbox can reach. Wildcards supported. |
| `deniedDomains` | Domains the sandbox is blocked from reaching. Deny rules take precedence over allow rules, including those from other composed kits. |
| `serviceDomains` | Map of domain to service identifier from `credentials.sources`. |
| `serviceAuth.headerName` | HTTP header the proxy sets (for example, `Authorization`). |
| `serviceAuth.valueFormat` | Format string for the header value (for example, `"Bearer %s"`). |
## Environment
```yaml
environment:
variables:
<NAME>: <value>
proxyManaged: [<NAME>, ...]
```
| Field | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------- |
| `variables` | Key-value pairs set directly in the container. |
| `proxyManaged` | Environment variable names populated by the proxy at request time. Pair with [`credentials.sources`](#credentials). |
Variable names must be valid shell identifiers (`[A-Za-z_][A-Za-z0-9_]*`).
## Commands
```yaml
commands:
install:
- command: <shell-string>
user: <uid>
description: <text>
startup:
- command: [<argv>, ...]
user: <uid>
background: <true | false>
description: <text>
initFiles:
- path: <path>
content: <text>
mode: <octal>
onlyIfMissing: <true | false>
description: <text>
```
### install
Runs once during sandbox creation. Shell strings passed to `sh -c`.
| 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` | Run in background. |
| `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`. Use them
for non-interactive prep — launching daemons, warming caches,
refreshing config — and use `commands.initFiles` 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.
### initFiles
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. |
## 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.
## Memory
```yaml
memory: |
<markdown>
```
Top-level field. Available in both mixin and agent kits. Markdown
appended to the agent's memory file at sandbox creation. The agent reads
this content at startup. Write it as instructions or notes the agent
should follow when working in the sandbox. Applied only when the active
agent kit sets [`agent.aiFilename`](#agent-block).
The file is written to the parent of the workspace path inside the
sandbox, not to the workspace itself. For a workspace mounted at
`/Users/you/myproject`, the memory file lands at
`/Users/you/AGENTS.md` (or whatever `aiFilename` is set to). It exists
only inside the sandbox. Nothing is written to the host.
When several loaded kits declare `memory:` blocks, the content is split
across files instead of being concatenated into the main one:
- Each kit's memory is written to `<kit-name>.md` in a sibling
`kits-memory/` directory next to the main memory file.
- The main memory file gets a `## Kits` section listing every kit with
a pointer to its file. The section is delimited by
`<!-- sbx:kits-section start -->` and `<!-- sbx:kits-section end -->`
markers so it can be regenerated when kits are added or removed.
## Agent block
Required for `kind: agent`.
```yaml
agent:
image: <image-ref>
aiFilename: <filename>
persistence: <persistent | ephemeral>
entrypoint:
run: [<argv>, ...]
args: [<arg>, ...]
```
| Field | Required | Description |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `agent.image` | Yes | Docker image reference. See [Base image requirements](#base-image-requirements). |
| `agent.aiFilename` | No | Memory filename (for example, `AGENTS.md`). Appends top-level [`memory`](#memory) at creation. |
| `agent.persistence` | No | `persistent` (named volume across restarts) or `ephemeral` (default). |
| `agent.entrypoint.run` | No | Command and args as a string array. Replaces the image's entrypoint. |
| `agent.entrypoint.args` | No | Args appended to the image's existing entrypoint. |
### Base image requirements
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 (baked in, or installed via
[`commands.install`](#commands)).
Build on top of `docker/sandbox-templates:shell-docker` to get these for
free.
+9 -317
View File
@@ -55,7 +55,7 @@ commands:
Startup commands cover things like launching background services,
warming caches, or refreshing config on each start. They must be
idempotent — see the [`startup`](#startup) spec reference:
idempotent — see the [`startup`](kit-reference.md#startup) spec reference:
```yaml
commands:
@@ -96,7 +96,7 @@ commands:
onlyIfMissing: true
```
See [`initFiles`](#initfiles) in the spec reference for all fields.
See [`initFiles`](kit-reference.md#initfiles) in the spec reference for all fields.
Sandboxes seed settings files for some built-in agents during setup.
For example, the sandbox writes `/home/agent/.claude/settings.json`
@@ -105,7 +105,7 @@ for the `claude` agent. This happens after the kit's static files and
Workspace files (such as `<workspace>/.claude/settings.local.json`)
aren't affected, and you can ship them under `files/workspace/` as
usual. To override a path the sandbox writes to, use a
[`commands.startup`](#startup) script instead. See
[`commands.startup`](kit-reference.md#startup) script instead. See
[Override agent settings](kit-examples.md#override-agent-settings) for
an example.
@@ -209,7 +209,7 @@ memory: |
```
Both mixin and agent kits can declare `memory:`. The content is written
only when the active agent kit sets [`agent.aiFilename`](#agent-block),
only when the active agent kit sets [`agent.aiFilename`](kit-reference.md#agent-block),
which determines the memory file's name.
When more than one loaded kit declares a `memory:` block, each kit's
@@ -227,7 +227,7 @@ that points to each kit file:
└── git-ssh-sign.md
```
See [`memory`](#memory) in the spec reference for the full field schema.
See [`memory`](kit-reference.md#memory) in the spec reference for the full field schema.
### Define an agent
@@ -318,7 +318,7 @@ everything the agent needs. Common use cases:
- Prototype a new agent integration
Agent kits declare everything a mixin kit can, plus an
[`agent:` block](#agent-block) that tells the sandbox how to launch the
[`agent:` block](kit-reference.md#agent-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).
@@ -459,317 +459,9 @@ Docker credential store, so pushing to a private registry requires a prior
## Spec reference
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/
```
### Top-level fields
```yaml
schemaVersion: "1"
kind: <mixin | agent>
name: <name>
displayName: <name>
description: <text>
```
| Field | Required | Description |
| --------------- | -------- | ------------------------------------------------------------------------ |
| `schemaVersion` | Yes | Spec schema version. Set to `"1"`. |
| `kind` | Yes | `mixin` for kits that extend an agent; `agent` for kits that define one. |
| `name` | Yes | Unique identifier. Lowercase, alphanumeric, hyphens. |
| `displayName` | No | Human-readable name. |
| `description` | No | Short description. |
The sections below apply to both kinds. Agent kits also declare an
[`agent:` block](#agent-block).
### Credentials
```yaml
credentials:
sources:
<service-id>:
env: [<env-var>, ...]
file:
path: <path>
parser: <parser>
priority: <env-first | file-first>
```
| Field | Description |
| -------------------------- | ------------------------------------------------------------- |
| `sources` | Map of service identifier to credential source. |
| `sources.<id>.env` | Environment variables to read on the host, in priority order. |
| `sources.<id>.file.path` | Path on host. `~` expands to home directory. |
| `sources.<id>.file.parser` | How to extract the credential value from the file. |
| `sources.<id>.priority` | `env-first` (default) or `file-first`. |
Service identifiers link credentials to [network rules](#network).
#### file.parser
`file.parser` tells the proxy how to extract a credential from the file at `file.path`.
Omit it for plain-text files; set it to `json:<dot.path>` to extract a field from a JSON file.
| Value | Behavior |
| ----------------- | ------------------------------------------------------------------------------------ |
| omitted or empty | Reads the entire file as the credential. Leading and trailing whitespace is trimmed. |
| `json:<dot.path>` | Parses the file as JSON and returns the value at the dot-separated path. |
| any other value | Rejected — `unsupported parser: <value>`. |
For `json:` paths, segments are separated by `.` (for example, `json:credentials.github.token`).
Only object keys can be navigated — arrays are not supported and there is no `[0]`-style indexing.
Keys that contain a literal `.` cannot be referenced. The resolved value must be a string, number,
or boolean; numbers and booleans are converted to strings. Objects, arrays, and null are rejected.
When a source has both `env` and `file` defined, `priority` controls which is tried first. The
preferred source is used when it exists — the environment variable is set, or the file is
present on disk. If it doesn't, the other source is used instead. The choice is made once at
discovery time, so parser errors (missing JSON field, wrong value type, invalid JSON) surface
as errors rather than triggering a fallback.
Plain-text token file:
```yaml
credentials:
sources:
openai:
file:
path: "~/.openai/token"
```
Nested JSON field, with an environment variable as fallback:
```yaml
credentials:
sources:
github:
env:
- GH_TOKEN
file:
path: "~/.config/myapp/creds.json"
parser: "json:credentials.github.token"
priority: file-first
```
Given `~/.config/myapp/creds.json`:
```json
{
"credentials": {
"github": { "token": "ghp_xyz", "expires": "2026-12-31" }
}
}
```
The proxy resolves the credential to `ghp_xyz`, falling back to `GH_TOKEN` if the file is
missing. If the file exists but the JSON path doesn't resolve, the request fails with the
parser error below instead of falling back.
Common errors when using `json:` parsers:
| Error message | Cause |
| --------------------------------------------- | ------------------------------------------------------------------- |
| `field 'X' not found in JSON` | The path doesn't exist in the file. |
| `cannot navigate to field 'X': not an object` | A path segment hit a string, array, or scalar instead of an object. |
| `field 'X' is not a string value` | The resolved value is an object, array, or null. |
| `failed to parse JSON: ...` | The file is not valid JSON. |
### Network
```yaml
network:
allowedDomains: [<domain>, ...]
deniedDomains: [<domain>, ...]
serviceDomains:
<domain>: <service-id>
serviceAuth:
<service-id>:
headerName: <header>
valueFormat: <format>
```
| Field | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `allowedDomains` | Domains the sandbox can reach. Wildcards supported. |
| `deniedDomains` | Domains the sandbox is blocked from reaching. Deny rules take precedence over allow rules, including those from other composed kits. |
| `serviceDomains` | Map of domain to service identifier from `credentials.sources`. |
| `serviceAuth.headerName` | HTTP header the proxy sets (for example, `Authorization`). |
| `serviceAuth.valueFormat` | Format string for the header value (for example, `"Bearer %s"`). |
### Environment
```yaml
environment:
variables:
<NAME>: <value>
proxyManaged: [<NAME>, ...]
```
| Field | Description |
| -------------- | ------------------------------------------------------------------------------------------------------------------- |
| `variables` | Key-value pairs set directly in the container. |
| `proxyManaged` | Environment variable names populated by the proxy at request time. Pair with [`credentials.sources`](#credentials). |
Variable names must be valid shell identifiers (`[A-Za-z_][A-Za-z0-9_]*`).
### Commands
```yaml
commands:
install:
- command: <shell-string>
user: <uid>
description: <text>
startup:
- command: [<argv>, ...]
user: <uid>
background: <true | false>
description: <text>
initFiles:
- path: <path>
content: <text>
mode: <octal>
onlyIfMissing: <true | false>
description: <text>
```
#### `install`
Runs once during sandbox creation. Shell strings passed to `sh -c`.
| 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` | Run in background. |
| `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`. Use them
for non-interactive prep — launching daemons, warming caches,
refreshing config — and use `commands.initFiles` 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.
#### `initFiles`
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. |
### 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.
### Memory
```yaml
memory: |
<markdown>
```
Top-level field. Available in both mixin and agent kits. Markdown
appended to the agent's memory file at sandbox creation. The agent reads
this content at startup. Write it as instructions or notes the agent
should follow when working in the sandbox. Applied only when the active
agent kit sets [`agent.aiFilename`](#agent-block).
The file is written to the parent of the workspace path inside the
sandbox, not to the workspace itself. For a workspace mounted at
`/Users/you/myproject`, the memory file lands at
`/Users/you/AGENTS.md` (or whatever `aiFilename` is set to). It exists
only inside the sandbox. Nothing is written to the host.
When several loaded kits declare `memory:` blocks, the content is split
across files instead of being concatenated into the main one:
- Each kit's memory is written to `<kit-name>.md` in a sibling
`kits-memory/` directory next to the main memory file.
- The main memory file gets a `## Kits` section listing every kit with
a pointer to its file. The section is delimited by
`<!-- sbx:kits-section start -->` and `<!-- sbx:kits-section end -->`
markers so it can be regenerated when kits are added or removed.
### Agent block
Required for `kind: agent`.
```yaml
agent:
image: <image-ref>
aiFilename: <filename>
persistence: <persistent | ephemeral>
entrypoint:
run: [<argv>, ...]
args: [<arg>, ...]
```
| Field | Required | Description |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| `agent.image` | Yes | Docker image reference. See [Base image requirements](#base-image-requirements). |
| `agent.aiFilename` | No | Memory filename (for example, `AGENTS.md`). Appends top-level [`memory`](#memory) at creation. |
| `agent.persistence` | No | `persistent` (named volume across restarts) or `ephemeral` (default). |
| `agent.entrypoint.run` | No | Command and args as a string array. Replaces the image's entrypoint. |
| `agent.entrypoint.args` | No | Args appended to the image's existing entrypoint. |
#### Base image requirements
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 (baked in, or installed via
[`commands.install`](#commands)).
Build on top of `docker/sandbox-templates:shell-docker` to get these for
free.
For a field-by-field reference of every `spec.yaml` block — top-level
fields, credentials, network, environment, commands, static files,
memory, and the agent block — see [Kit spec reference](kit-reference.md).
## Debugging