Docker Sandboxes: Kits (spec.yaml)

SkillCloud & infra

Use this skill when authoring, validating, packaging, signing, or composing a Docker Sandboxes kit `spec.yaml` (`sbx kit add/inspect/pack/pull/push/sign/validate/verify`), even if the user just says they want to "add a tool to a sandbox agent", "build a reusable sandbox extension", "publish a kit to a registry", or "give a mixin its own credentials and network access". Covers the kit-spec v2 grammar (`kind: sandbox` vs `kind: mixin`, the `sandbox:` block, `permissions.network`, `ports`, `credentials` apiKey/oauth, `environment`, `setup` install/startup/files, `volumes`, `args`, `extends`, `mixins`, `requires.agent`), composition via `--kit`/`sbx kit add`, and distribution (pack/push/pull/sign/verify/provenance).

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the Docker Sandboxes: Kits (spec.yaml) skill

What this skill tells your AI

The instructions your AI receives, as published by docker/skills in skills/docker-sandboxes-kits/SKILL.md and read by ahel’s review.

Overview

A kit is a directory (or ZIP/OCI/git artifact) containing a spec.yaml plus an optional files/ tree. sbx composes a kit into a running or about-to-be-created sandbox at sbx create/sbx run --kit/sbx env time or at sbx kit add time. This skill owns kit-spec v2 authoring, validation, and distribution — everything under spec.yaml's own grammar — and defers what a kit's declarations mean at runtime (credential injection, network enforcement) to docker-sandboxes-network-credentials, and the sandboxes a kit is composed into to docker-sandboxes-lifecycle.

When to use this skill

Activate this skill when:

  • The user wants to write, validate, or pack a spec.yaml for a kind: sandbox (complete agent) or kind: mixin (extension) kit.
  • The user wants a mixin to add a tool, credential, network allowance, or files to an existing built-in agent.
  • The user wants to publish a kit to (or pull one from) an OCI registry, sign it, or verify a signature/provenance attestation.
  • The user is debugging a kit-validation error, an argument-substitution error, or sbx kit add's recreate-aware requirement.

Do not use this skill when

Do not use this skill when:

  • The task is creating/running/removing the sandbox a kit is composed into, independent of the kit's own content — use docker-sandboxes-lifecycle.
  • The task is what a credential or network rule a kit declares actually does at runtime (proxy injection, allow/deny precedence, or what the CURRENT network/global policy already permits), or is about secrets/ policy that have nothing to do with a kit — use docker-sandboxes-network-credentials.
  • The task is the sbxenv.yaml file format that references kits via its own kits: block — use docker-sandboxes-env for that file's schema (this skill still owns what goes inside the referenced kit itself).

Core guidance

kind: sandbox vs kind: mixin — pick the right one

  • Exactly one kind: sandbox kit composes into any sandbox (a complete agent: base image + launch config). Any number of kind: mixin kits layer onto it (tools, credentials, network, files). A mixin must not declare a sandbox: block, extends:, or mixins:.
  • Every kit needs schemaVersion: "2" (the current clean grammar — no legacy shims), kind, and name matching ^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$. Decoding is strict: any unrecognized field anywhere is a hard error (e.g. a typo like permissions.netwrok:), so a kit that validates has no silent typos.
    schemaVersion: "2"
    kind: mixin
    name: extra-egress
    
  • Do not redefine a base agent's credential in a mixin: declaring a new apiKey.name or proxyManaged for the same service fails composition. shell, docker-agent, and opencode already own github. An additive routing-only entry (apiKey.inject, no name/proxyManaged/oauth, and required: false) can extend the base credential instead. OAuth belongs on sandbox kits, never mixins. See references/spec-v2-fields.md. Inspect built-in definitions at sandboxlib/agentkits/agents/<agent>/spec.yaml in the pinned source; sbx kit inspect takes artifact references, not built-in names. Standalone mixin validation does not test composition.

The sandbox: block (sandbox kits only)

  • Required for kind: sandbox (unless the kit extends: a parent that already supplies it); forbidden for kind: mixin.
  • image: is the pre-built base image. entrypoint: is the fixed process prefix (entrypoint[0] is the binary); command: is the mode-specific argument tail — either a bare list (sets default, interactive falls back to it) or {default: [...], interactive: [...]}. For a complete minimal kit, use assets/spec-sandbox.yaml, which inherits the embedded shell definition rather than inventing an image or command.
  • sandbox.build: (Dockerfile build) is accepted but not built by the runtime this release — a kit that sets build: must still set image:, or it is rejected at load with an actionable error.
  • extends: (below) is the simplest way to get a real, working image without inventing one. A sandbox kit that extends a built-in agent (e.g. extends: shell) inherits that agent's real sandbox.image and may omit sandbox: entirely — see the minimal example asset, which does exactly this rather than naming a made-up image reference.

Egress: permissions.network — and the all-egress-declared rule

  • permissions.network.allow/deny are the v2 home for what v1 spelled as top-level network:. Enforced shapes include exact host, exact host+port, single-label wildcards (*.example.com), multi-label wildcards (**.example.com), and CIDR prefixes. Port ranges are not supported by the runtime matcher; use separate exact ports. Deny wins within domain rules or within CIDR rules. A decisive domain decision is evaluated before CIDR rules: an allowed hostname is not checked against a CIDR deny for its resolved IP. Do not rely on a CIDR deny alone to block an already-allowed hostname.
    permissions:
      network:
        allow:
          - registry.npmjs.org
        deny:
          - telemetry.example.com
    
  • permissions.network.allow is additive across a composition, and a kit's own allow list is not the only thing granting a sandbox egress. The sandbox already carries the base agent's own allow list, plus whatever the global or per-sandbox network policy (sbx policy, independently of any kit) permits — see docker-sandboxes-network- credentials. Removing a host from one kit's allow list does not by itself prove that host is blocked — the global policy defaults (balanced allows common package registries and AI services; allow-all allows everything) or another composed kit may still permit it. Never claim a host is blocked without checking the actual effective decision with sbx policy check network --sandbox <name> <host> on a real sandbox.
  • Declare the egress a kit requires explicitly for reproducibility. Credential injection does not itself grant network access. Omitting an allow entry leaves reachability dependent on the existing global/per-sandbox policy; it does not necessarily block the host. Check the effective decision.

credentials — what the kit needs, never how the user stores it

  • Each entry declares a service identity and where to inject the resolved value (apiKey and/or oauth); it never declares how the user obtains or stores the credential — that lives in the user's own bindings file, wired through sbx secret set (see docker-sandboxes-network-credentials).
  • apiKey.inject[] needs a domain and either an explicit header+ format (format must contain exactly one %s) or the scheme: sugar: scheme: bearer expands to Authorization: Bearer %s (no username), scheme: basic requires username and is mutually exclusive with format. Pick a service name no composed base agent already declares (see the duplicate-service rule above) — see references/spec-v2-fields.md for a complete fragment.
  • apiKey.proxyManaged: true sets the in-container env var to the literal proxy-managed sentinel rather than leaving it unset; the real value is substituted only by the proxy, on the allow-listed inject domains.
  • oauth needs tokenEndpoint.host/.path and, unless passthrough: true, non-empty sentinels.accessToken/.refreshToken. passthrough: true is a security downgrade — the real token reaches the container instead of a sentinel — use it only when the kit's own design requires it and say so in description.

setup — install (once) vs. startup (every start) vs. files (startup-time writes)

BlockCommand shapeRuns
setup.install[].commandstring, via sh -cOnce, synchronously, before the agent first launches. Runs for every kit, built-in or not.
setup.startup[].commandlist, exec-style (no shell)On every container start (create, stop/start, daemon restart, host reboot) — must be idempotent.
setup.files[]file write via shell execAt container startup; path absolute; only ${WORKDIR} placeholder allowed in content.

Optional fragment for the shell kit in assets/spec-sandbox.yaml:

setup:
  startup:
    - command: ["sh", "-c", "mkdir -p ~/.my-kit"]
  files:
    - path: /home/agent/.my-kit/config.json
      content: '{"workdir": "${WORKDIR}"}'
  • setup.files is not the same mechanism as the files/ directory tree (below). setup.files entries are dynamic, ${WORKDIR}- substituted writes performed at startup time; the files/home/ and files/workspace/ directory tree is a set of static files packed alongside spec.yaml and copied in at container-create time, and it is specifically the files/workspace/ half of that tree — not setup.files — that is written after the workspace is populated (e.g. after an in-container git clone under --clone). Do not conflate the two: setup.files has no "after workspace population" timing guarantee of its own.
  • All three setup: lists concatenate in --kit order across composed kits.
  • Default execution users: install as root (user: "0") unless overridden; startup/entrypoint as the agent user (uid 1000) unless overridden. Root install steps writing under /home/agent must chown it back to agent:agent, or later agent-user writes there fail.

volumes — creation-time only, every volume must set a size

  • Each entry needs an absolute path:, optional type: tmpfs (RAM-backed; omit/"" for the default block-backed volume), optional size: (byte-size string) and mode: (octal).
  • Volumes apply only at sandbox-create time — sbx kit add (runtime injection) skips volume changes entirely; a kit that needs one must be present at creation.
  • Always set size: on a block volume. An unsized volume inherits a 50 GiB default and costs real host disk immediately (ext4 inode-table zeroing); 512 MiB is the practical floor — below it mke2fs switches inode density and the space savings mostly disappear.

args — parameterizing a kit

  • Declare under top-level args: (v2 only — the frozen v1 grammar has no args block), each with exactly one of default/required: true, plus optional description/enum/pattern. Reference with ${{ kit.args.NAME }} anywhere in spec.yaml or files/; substitution happens before the spec is decoded. Every reference must be declared, or loading fails — that is what makes the block a trustworthy list of a kit's inputs. Quote a placeholder used in a string field (VERSION: "${{ kit.args.version }}"), or an unquoted numeric-looking value decodes as a number and fails to decode into a string field.
  • Supply values with --kit-arg name=value (every kit) or --kit-arg kitname.name=value (one kit only), or --kit-args-file. Never pass a secret this way — --kit-arg values are not masked; see docker-sandboxes-network-credentials.

extends and mixins — composition, not runtime injection

  • extends: resolves only built-in agent names at this pinned release (shell, claude, etc.). Remote git/OCI parents fail to resolve, even if pinned; the broader format specification is not an implementation guarantee. The minimal asset uses the supported extends: shell.
  • mixins: is accepted with a warning but is not applied by this runtime. Use --kit or sbx kit add for composition. The format's immutable-ref requirements do not make unimplemented remote inheritance work.
  • Prefer digest/commit-pinned CLI kit references for reproducibility. --kit and sbx kit add still accept mutable tags/branches; the CLI parser does not enforce this recommendation.
  • requires.agent (mixin-only; rejected on kind: sandbox) pins the single base agent a mixin is designed for (e.g. Claude-specific env vars). It is well-formedness-checked by the spec library; the actual agent-affinity mismatch is enforced by the composition consumer, not by sbx kit validate alone.

Validating, packaging, and distributing

CommandPurpose
sbx kit validate REFERENCE [--kit-arg ...]Local directory, ZIP, or git reference; OCI is rejected. Schema-only well-formedness check. Never composes against a base agent — cannot catch a duplicate-service credential collision or confirm any domain is reachable at runtime.
sbx kit inspect REFERENCE [--kit-arg ...] [--json]Loads and prints the decoded artifact before composing it, including --kit-arg substitution preview.
sbx kit pack DIRECTORY [-o OUTPUT.zip]Packages a validated directory as a ZIP.
sbx kit pull REFERENCE [-o OUTPUT]Pulls a kit's raw layer payload from an OCI registry without composing it.
sbx kit push DIRECTORY REGISTRY/REPO:TAG [--sign]Packages and pushes; every push attaches an unsigned-by-default SLSA provenance attestation.
sbx kit provenance REFERENCE [--certificate-identity ...]Prints the attestation push attached; marked UNSIGNED unless verified against a matching key/identity.
sbx kit sign REFERENCE / sbx kit verify REFERENCESigstore sign/verify (keyless by default); prefer --identity-token-file over --identity-token.
sbx kit add SANDBOX REFERENCE [--kit-arg ...]Injects a mixin only into an existing sandbox at runtime (recreate-aware label required); container-immutable settings (security.privileged, volumes:) cannot take effect this way.

See references/kit-distribution-commands.md for full flag lists and worked examples of each command above.

Related skills

  • For the sandboxes a kit is composed into (sbx create/run --kit, sbx kit add SANDBOX), use docker-sandboxes-lifecycle.
  • For what a kit's credentials:/permissions.network: declarations mean at runtime — proxy injection, allow/deny precedence, the effective policy a sandbox actually has once global/per-sandbox policy is included, where the user stores the actual secret value — use docker-sandboxes-network-credentials.
  • For the sbxenv.yaml file whose kits:/agent: fields reference a kit by this schema, use docker-sandboxes-env.

References

  • references/sources.md — provenance for every rule above (spec package, SPEC-v2.md, help captures, docs URLs).
  • references/spec-v2-fields.md — the complete v2 field table (common fields, sandbox-only fields, mixin-only fields, shared blocks) for lookup without re-reading the full spec.
  • references/kit-distribution-commands.md — full flags and worked examples for sbx kit validate/inspect/pack/pull/push/provenance/sign/verify/add.

Assets

  • assets/spec-sandbox.yaml — a genuine minimal kind: sandbox kit that extends: shell to inherit a real, working image rather than inventing one.
  • assets/spec-mixin.yaml — a genuine minimal kind: mixin kit with no credentials at all (an egress-only extension), which composes cleanly with every built-in agent.

Checks

  • checks/verification.md — Schema, composition, egress, and kit-add checks (unexecuted integration runbook; isolated --app-name, no registry publishing or signing).

Signals

GitHub stars
410
Forks
21
Last commit
Sep 2026
Advanced
Item type
skill
Key
docker-sandboxes-kits
Source
github.com/docker/skills