Share feedback
Answers are generated based on the documentation.

Kit spec reference

Availability: Early Access
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 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.

For the normative v2 grammar used by the parser and tests, see the schemaVersion: "2" specification in the docker/sbx-kits-contrib repository.

A kit directory has a required spec.yaml and an optional files/ tree:

my-kit/
├── spec.yaml       # required
└── files/          # optional — static files to inject
    ├── home/
    └── workspace/

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:

v1v2
credentials.sources.<id>credentials: list entry with service
network.allowedDomains / deniedDomainspermissions.network.allow / deny
network.serviceDomains / serviceAuthcredentials[].apiKey.inject
network.publishedPorts / publishedPortstop-level ports
standalone oauth: blockcredentials[].oauth
oauth.skipIfEnvAccepted but ignored
environment.proxyManagedcredentials[].apiKey.proxyManaged
memory / agentContextagentInstructions.content
kind: agent / agent: blockkind: sandbox / sandbox: block
sandbox.aiFilenameagentInstructions.filename
sandbox.entrypoint.runsandbox.entrypoint
sandbox.entrypoint.argssandbox.command.default
sandbox.entrypoint.ttyArgssandbox.command.interactive
tmpfs:volumes: entries with type: tmpfs
volumes: (mapping form)volumes: sequence (- path: <path>)
commands: / commands.initFilessetup: / setup.files
settings: / kitDir / persistenceRemoved

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.

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

schemaVersion: "2"
kind: <mixin | sandbox>
name: <name>
version: <version>
displayName: <name>
description: <text>
sourceURL: <url>
licenses:
  - MIT
locked:
  - sandbox.image
security:
  privileged: false
FieldRequiredDescription
schemaVersionYesSpec schema version. Use "2" for this grammar.
kindYesmixin for kits that extend an agent; sandbox for kits that define one.
nameYesUnique identifier. Lowercase alphanumeric with hyphens, 1 to 64 characters.
versionNoKit version.
displayNameNoHuman-readable name.
descriptionNoShort description.
sourceURLNoSource repository or documentation URL.
licensesNoSPDX license identifiers.
lockedNoDotted paths child kits may not override.
securityNoContainer security settings. security.privileged: true runs the container in privileged mode.

A kit also declares behavior blocks such as agentInstructions, permissions, ports, credentials, environment, setup, and volumes.

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:

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:

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

sandbox:
  image: <image-ref>
  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"
FieldRequiredDescription
sandbox.imageYesDocker image reference. A sandbox that sets extends: can inherit this from its parent.
sandbox.buildNoBuild configuration. Runtime support is pending, so a kit with build: must also set image:.
sandbox.entrypointNoFixed process prefix as a string array. The first element is the agent binary.
sandbox.commandNoMode-specific argument tail. Use a list shorthand for default, or a mapping with default and interactive.
sandbox.resourcesNoOptional 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.

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.

Build on top of docker/sandbox-templates:shell-docker to get these base requirements.

Agent instructions

agentInstructions:
  filename: CLAUDE.md
  content: |
    Ruff is installed. Run `ruff check` before committing.
FieldDescription
filenameAI profile filename. Meaningful for kind: sandbox; ignored with a warning for kind: mixin.
contentMarkdown instructions. For a sandbox, inlined into the profile. For a mixin, written to kit memory.

For mixins, the engine writes content to <dir-of-AI-file>/kits-memory/<kit-name>.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 authorizes its use. A kit can't read arbitrary host environment variables or files.

credentials:
  - service: <service-id>
    description: <text> # optional
    required: <true | false> # optional, default false
    provider: <provider> # optional, reserved
    apiKey:
      name: <ENV_VAR>
      proxyManaged: true
      inject:
        - domain: <domain>
          header: <header>
          format: <format>
        - domain: <domain>
          scheme: bearer
        - domain: <domain>
          scheme: basic
          username: <user> # required with scheme: basic
    oauth:
      tokenEndpoint:
        host: <host>
        path: <path>
      sentinels:
        accessToken: <sentinel>
        refreshToken: <sentinel>
      credentialFile:
        path: <path>
        template: |
          {
            "<key>": {
              "accessToken": "{{.AccessToken}}",
              "refreshToken": "{{.RefreshToken}}",
              "expiresAt": {{.ExpiresAt}},
              "scopes": {{.ScopesJSON}}
            }
          }

credentials is a list; each entry names a service and configures one or more auth mechanisms.

FieldDescription
serviceCredential identifier, matched against the value stored with sbx secret set. Lowercase kebab-case.
descriptionOptional. Shown to the user when approving a binding.
requiredMarks the credential as essential to the agent. If it has no binding, sbx warns and starts with the credential withheld. Default false.
providerReserved for a provider registry. Accepted with a warning and no runtime effect.
apiKeyAPI-key injection (see apiKey).
oauthOAuth interception (see 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

FieldDescription
nameEnvironment variable name for the credential (for example, ANTHROPIC_API_KEY).
proxyManagedIf true, sbx sets name inside the container to the proxy-managed sentinel. Default false.
inject[].domainDomain to inject the credential into. Must also be allowed in permissions.network.
inject[].headerHTTP header the proxy sets (for example, x-api-key, Authorization).
inject[].formatHeader value format, with one %s placeholder (for example, "%s" or "Bearer %s"). Mutually exclusive with scheme.
inject[].schemeShorthand for common auth schemes. bearer expands to Authorization: Bearer %s; basic requires username. Mutually exclusive with format.
inject[].usernameUsername 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.

FieldDescription
tokenEndpoint.host / pathThe OAuth token endpoint the proxy intercepts.
sentinels.accessToken / refreshTokenSentinel values written into the container in place of the real tokens.
credentialFile.pathWhere to write the credential file inside the container (~ expands).
credentialFile.templateGo template used to render the credential file. Supports {{.AccessToken}}, {{.RefreshToken}}, {{.ExpiresAt}}, {{.Scopes}}, and {{.ScopesJSON}}. Use {{.ScopesJSON}} for a JSON array.
credentialFile.structureDeclarative JSON shape defined by schema v2 but not supported by the sbx engine. A structure-only kit fails validation. Use credentialFile.template.
resourceHostsAPI hosts where the proxy attaches the token on outbound requests, distinct from the token endpoint host.
skipIfEnvAccepted for compatibility, but ignored for schema v2. A v2 binding is authoritative instead of host environment variables.
responseFieldsOverrides the default field names the proxy reads from the token response.
passthroughIf true, the proxy passes the token response through unchanged instead of replacing the tokens with sentinels.

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 lists, and every domain the sandbox reaches must be allowed here.

permissions:
  network:
    allow: [<domain>, ...]
    deny: [<domain>, ...]
FieldDescription
permissions.network.allowDomains the sandbox can reach.
permissions.network.denyDomains the sandbox is blocked from reaching. Deny takes precedence over allow, including across composed kits.

Allow and deny patterns:

PatternExampleStatus
Exact hostapi.example.comEnforced
Exact host and portapi.example.com:8080Enforced
Single-label wildcard*.example.comEnforced
Multi-label wildcard**.example.comParsed; enforcement pending
Port rangeapi.example.com:80-443Parsed; enforcement pending
Port wildcardapi.example.com:*Parsed; enforcement pending
CIDR10.0.0.0/8Parsed; 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:

ports:
  - container: 8080
    protocol: tcp
    name: web
FieldDescription
containerContainer port, 1 to 65535.
protocoltcp or udp. Empty means tcp.
nameOptional label surfaced by tools that list published port bindings.

Host ports are allocated ephemerally on 127.0.0.1. Users can pin host ports with sbx ports --publish <host>:<container>.

Environment

environment:
  variables:
    <NAME>: <value>
FieldDescription
variablesKey-value pairs set directly in the container.

Variable names must be valid shell identifiers ([A-Za-z_][A-Za-z0-9_]*).

Do not set DASH_, SBX_, or DOCKER_ variables, and avoid overriding HOME, USER, SHELL, PATH, LD_PRELOAD, and LD_LIBRARY_PATH. The runtime reserves these names and may override them.

Setup

setup:
  install:
    - command: <shell-string>
      user: <uid>
      description: <text>
  startup:
    - command: [<argv>, ...]
      user: <uid>
      background: <true | false>
      description: <text>
  files:
    - path: <path>
      content: <text>
      mode: <octal>
      onlyIfMissing: <true | false>
      description: <text>

install

Runs once during sandbox creation. Shell strings passed to sh -c.

FieldDefaultDescription
commandShell command string.
user"0"User to run as. "0" = root.
descriptionHuman-readable description.

startup

Runs at every sandbox start. String array, not interpreted by a shell.

FieldDefaultDescription
commandCommand and args as a string array.
user"1000"User to run as. "1000" = agent.
backgroundfalseRun in background.
descriptionHuman-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 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.

FieldDefaultDescription
pathAbsolute container path.
contentFile content. ${WORKDIR} expands to the workspace path.
mode"0644"File permissions in octal.
onlyIfMissingfalseSkip if the file already exists.

Static files

my-kit/files/
├── home/       → /home/agent/
└── workspace/  → primary workspace path
Kit pathContainer 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

volumes:
  - path: /workspace
    size: 10g
    mode: "0755"
  - path: /tmp/scratch
    type: tmpfs
    size: 512m
    mode: "1777"
FieldDescription
pathRequired absolute container path.
typeEmpty for a block-backed volume, or tmpfs for RAM-backed storage.
sizeOptional byte-size string.
modeOptional octal permissions.

Volumes are applied only when a sandbox is created. sbx kit add cannot attach volumes to a running container.