Iron Proxy Gateway for NanoClaw
nanocoai/nanoclaw
Installs or refreshes Iron Proxy and its Iron Control web console for NanoClaw, with a local Docker setup, database, credentials and a human approval bridge.
Authors a new Docker sandbox kit against the v3 descriptor — choosing workload or mixin, writing the descriptor and its content recipe, declaring the capabilities it needs, pinning the tool version…
$ npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install docker/sandbox-kit-spec create-kit-v3 --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/docker/sandbox-kit-spec.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/create-kit-v3 .claude/skills/create-kit-v3 && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "create-kit-v3" agent skill from https://github.com/docker/sandbox-kit-spec/tree/main/skills/create-kit-v3 into .claude/skills/create-kit-v3/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "create-kit-v3", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/docker/sandbox-kit-spec/tree/main/skills/create-kit-v3Type this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install docker/sandbox-kit-spec create-kit-v3 --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/docker/sandbox-kit-spec.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/create-kit-v3 .agents/skills/create-kit-v3 && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "create-kit-v3" agent skill from https://github.com/docker/sandbox-kit-spec/tree/main/skills/create-kit-v3 into .agents/skills/create-kit-v3/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "create-kit-v3", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install docker/sandbox-kit-spec create-kit-v3 --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/docker/sandbox-kit-spec.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/create-kit-v3 .cursor/skills/create-kit-v3 && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "create-kit-v3" agent skill from https://github.com/docker/sandbox-kit-spec/tree/main/skills/create-kit-v3 into .cursor/skills/create-kit-v3/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "create-kit-v3", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/docker/sandbox-kit-spec.git --path skills/create-kit-v3--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install docker/sandbox-kit-spec create-kit-v3 --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/docker/sandbox-kit-spec.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/create-kit-v3 .gemini/skills/create-kit-v3 && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "create-kit-v3" agent skill from https://github.com/docker/sandbox-kit-spec/tree/main/skills/create-kit-v3 into .gemini/skills/create-kit-v3/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "create-kit-v3", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install docker/sandbox-kit-spec create-kit-v3Installs for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/docker/sandbox-kit-spec.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/create-kit-v3 .github/skills/create-kit-v3 && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "create-kit-v3" agent skill from https://github.com/docker/sandbox-kit-spec/tree/main/skills/create-kit-v3 into .github/skills/create-kit-v3/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "create-kit-v3", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install docker/sandbox-kit-spec create-kit-v3 --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/docker/sandbox-kit-spec.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/create-kit-v3 .opencode/skills/create-kit-v3 && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "create-kit-v3" agent skill from https://github.com/docker/sandbox-kit-spec/tree/main/skills/create-kit-v3 into .opencode/skills/create-kit-v3/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "create-kit-v3", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
create-kit-v3Authors a new Docker sandbox kit against the v3 descriptor — choosing workload or mixin, writing the descriptor and its content recipe, declaring the capabilities it needs, pinning the tool version…
Create Kit V3 is an agent skill from docker/sandbox-kit-spec, published by the product's own GitHub organization. Authors a new Docker sandbox kit against the v3 descriptor — choosing workload or mixin, writing the descriptor and its content recipe, declaring the capabilities it needs, pinning the tool version, and verifying the result with docker buildx, sbx and kit-tck. Use when creating a new kit from scratch, adding a -mixin variant, packaging a CLI or agent as a sandbox kit, or asking what a kit descriptor should contain.
Its SKILL.md is about 6.5k tokens, which your agent loads only when the skill is triggered. The skill folder holds 1 other file (for example `RECIPES.md`).
It sits in DevOps & Cloud, covering Containers. It works with Docker. The repository describes itself as: Docker Sandbox Kit Specification v3 - the kit descriptor grammar, the OCI artifact, the build frontend, and the conformance suites. The licence is Apache-2.0.
Read from SKILL.md and the folder at commit 4be7f4d. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
dockerjqgoFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
github.comtoken.actions.githubusercontent.comAlso links to:
docs.docker.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Create Kit V3 loads about 6.5k tokens when it runs. Until then it costs about 109 tokens; SKILL.md has 3,349 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from docker/sandbox-kit-spec at commit 4be7f4d, republished under its Apache-2.0 licence (© docker). 3,349 words, ~6,514 tokens.
.claude/skills/create-kit-v3/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.A kit is one OCI image. Its layers are the content and its manifest annotation carries the descriptor: what the kit offers, what it needs from the host as typed capability requests, and what it needs from other kits. An engine that does not read the annotation runs it as an ordinary image.
To migrate an existing v2 spec.yaml kit rather than write a new one, use the
migrate-kit-to-v3 skill instead; its FIELD-MAPPING.md is also the best
field-by-field reference if you are unsure what a given declaration means.
Everything else follows from this.
kind: workload | kind: mixin | |
|---|---|---|
| Layers are | a root filesystem | an overlay landing on a workload |
| Per composition | exactly one | zero or more |
| Owns | entrypoint, env, user, workdir, a legacy context profile | env, labels, ports and volumes, which merge; an agent mixin can select an explicit context profile |
| Must have content | yes | no — may be declaration-only |
Write a workload when you own the environment the agent runs in. Write a
mixin when you add a tool, a credential or a policy to somebody else's.
Most new kits should be mixins, and a tool worth shipping as a workload is
usually worth shipping as both — that is what the claude/claude-mixin pair
in the examples is.
A mixin cannot set ENTRYPOINT, CMD, USER or WORKDIR and have it take
effect: the workload anchors the composition and owns those contract fields.
The additive fields do merge — env, labels, ports and volumes — so a
mixin's ENV reaches the composed image, and PATH is appended rather than
replaced.
Prefer ENV for static environment. It is what the agent process sees, and
sbx@1 launches the agent under bash via BASH_ENV precisely because
profile and rc files do not run for it — so an /etc/profile.d/<kit>-env.sh
drop reaches a terminal the user opens and misses the agent itself. Reach for
profile.d only when a value is likely to collide, because two mixins setting
one variable to different values is a hard composition failure, or when the
value genuinely only makes sense in an interactive shell.
The default authoring form is a companion pair, found by filename stem:
<kit>/
<kit>.yaml # the descriptor; first line `# syntax=docker/sandbox-kit:3`
<kit>.dockerfile # the content recipe (omit for a declaration-only mixin)
<kit>-context.md # agent-context body, referenced as contentFile:
README.mdNo dockerfile: field is needed — the stem convention finds it. Three other
forms exist (an inline build: block, a # kit: comment descriptor inside a
Dockerfile, and a kind: set list of other kits); see SPEC-v3 §3.
# syntax=docker/sandbox-kit:3
schemaVersion: "3"
kind: mixin
displayName: GitHub CLI
description: gh, installed from the official release tarball
sourceUrl: https://github.com/cli/cli
licenses: [MIT]
# One value drives the install, the provide, the published version and the tag.
version: "${{ kit.args.version }}"
args:
version:
default: "2.98.0"
pattern: '^[0-9]+\.[0-9]+\.[0-9]+$'
description: GitHub CLI release to install
buildArg: GH_VERSION
provides: ["gh@${{ kit.args.version }}"]
# No requires: this overlay ships a release tarball and needs nothing from the
# workload. Add entries only for what the composed runtime must already have —
# see below, and note the comment under `requires` about refusing to compose.
capabilities:
- type: com.docker.sandbox/network-policy@1
config:
runtime:
allow: [github.com, api.github.com]
- type: com.docker.sandbox/agent-context@1
config:
contentFile: ./gh-context.mdEvery key is lowerCamelCase with acronyms title-cased (sourceUrl,
apiKey). Decoding is strict: an unrecognized key anywhere is an error,
which is deliberate — a misspelled key silently ignored would be a policy
silently absent.
Use an optional entry-level name for a short display label and
description for its explanation. Names may contain spaces and need
not be unique; they do not change identity, permissions, or merge keys.
When entries merge, the first nonempty label in contribution order wins.
This label is separate from config fields such as apiKey.name.
A capability is a typed request the host answers. optional: true means the
kit degrades without it; the default is required, which fails resolution
closed. Read the page for each type you emit — each is normative for its config
and for what a runtime must do. The ones you will reach for, and the rule most
often got wrong:
| Type | Use it for | Easy to get wrong |
|---|---|---|
network-policy@1 | egress by host | It is phase-scoped: an absent phase grants nothing. Hosts your install hooks reach go in install, hosts the running agent (or a startup hook) reaches go in runtime, hosts both reach go in both. |
network-policy@2 | egress bounded by HTTP method and path | Same phases, plus entries that grant only matching requests. Pick @2 when a host should carry one API and not the rest; stay on @1 when host alone is the grant. Exclusive with @1 — declaring both is an error, and a bounded allow entry must name its hosts literally. gh and hello use @2. Verify the bound you rely on: declare the tightest correct policy, but confirm against your target runtime which parts it enforces before treating one as a security boundary — a refusal is a 403 from the boundary, which an origin can also return, so test a case the origin would allow. |
credential@1 | one service's auth | Entries are required by default — add optional: true unless the kit genuinely cannot run unauthenticated. phase accepts a single phase or [install, runtime] with shared configuration. Every inject[].domain must appear in every listed phase's allow list, matched exactly: a *.example.com wildcard does not satisfy api.example.com. |
ssh-agent@1 | git over SSH, SSH commit signing | Set unrestricted: false to bound it. sign: [git] is all commit signing needs; authenticate: [git@github.com] is a login to one server. An entry with unrestricted: true (the default) signs anything with every key the user's agent holds — declare that only when a client cannot bind sessions (OpenSSH can), and prefer credential@1 with HTTPS when a token will do. optional: true unless the kit cannot work without it: many users have no agent running. A hook that uses it declares SSH_AUTH_SOCK in env:, and only hooks of the granted phase get it. authenticate grants a signature, not a connection: the server must also be reachable under the network policy. phase accepts a single phase or [install, runtime] with shared rules. Commit signing needs git config too (gpg.format=ssh, gpg.ssh.defaultKeyCommand="ssh-add -L"). |
lifecycle@1 | install/startup hooks, staged files | Hook environments are deny-by-default. Declare every variable in env:, including ones only a child process reads — curl, pip and npm need HTTP_PROXY/HTTPS_PROXY, and docker needs DOCKER_HOST. |
volume@1 | persistent paths | Always set size. An unsized kit volume is formatted at 512 MiB, which is a cache or a package store running out of room mid-run rather than anything visible at create. |
host-mount@1 | host-shared caches, datasets, or artifacts | Declare only the absolute, canonical in-container path and optional octal mode. The runtime owns the host location. Data is shared across sandboxes of the declaring Kit, survives sandbox removal, and is visible to the host user. Use an optional group to couple cache setup with the mount; see examples/shared-cache. |
agent-context@1 | instructions the agent reads | A workload can supply the legacy workspace-sibling filename. An agent workload or mixin supplies filename plus an absolute directory at its discovery location; this overrides the legacy fallback. Different explicit destinations conflict. Tool mixins contribute bodies alone. Use contentFile: for a static body, but inline content: when the body interpolates an arg — a staged body is never arg-expanded. |
long-running@1 | workloads or service mixins that outlive client sessions | Config-less; a request from any Kit applies to the whole sandbox. A background hook or published port does not prevent session auto-stop. Required by default; use optional: true only if auto-stop is tolerable. This does not request restart after failure. |
git-identity@1 | commits attributed to the runtime-provided user | Config-less; requests only user.name and user.email, not a configuration-source mount. Requires permission; optional when missing identity is tolerable. Signing/authentication are separate grants. |
sbx@1 | "launch this as an agent" | Workload-only, config-less — and enforced: a mixin declaring it fails validation. |
agent-skill@1 | one bundled skill | path names the image directory containing SKILL.md and supporting files. The basename is the discovery name unless config.name overrides it; the entry-level name remains only a display label. |
agent-skills@1 | where an agent discovers skills | Declare on the agent workload or agent mixin. The runtime links or otherwise exposes every selected agent-skill@1 bundle here, and includes host-shared skills when available and enabled. Missing host skills never block startup; mode bounds host-store access only. |
agent-sessions@1 | the headless verbs a harness drives (run one prompt, continue, resume, list) | Argv tails appended to the launch argv; prompt must reference {{.Prompt}} and resume {{.SessionID}}. Most agent Kits declare both this and agent-interactive-sessions@1; an agent with no interactive mode declares only this. Verify every flag against the tool's real CLI — never invent one. |
agent-interactive-sessions@1 | the TUI verbs a human-facing host launches (seeded prompt, continue, resume, session picker, list) | The sibling of agent-sessions@1 for an agent whose CLI has an interactive mode; an agent that also has a headless mode declares both, and one with only a TUI declares just this. Presence is meaning: a present key is supported, an absent key is not, and for continue, newSession and sessionPicker [] is the launch argv alone (prompt and resume carry their placeholders, so they are never empty). newSession is the exception: omit it when a bare launch opens the TUI, because it then defaults to lifecycle@1's interactive launch; if you state both newSession and interactive they must be the same argv, an explicit [] included. list is the same command as in agent-sessions@1. Declare only verbs you verified. |
port@1, resources@1, privileged@1 | inbound ports, limits, elevation | Do not declare on speculation; privileged@1 is the largest widening available. |
Host sharing is a separate grant from volume@1, including when the
in-container path stays the same. Do not supply a host path or assume a
Linux bind mount: runtimes may use VM filesystem sharing. Host-shared
directories may have weaker filesystem semantics than private volumes.
Concurrent writers coordinate access themselves. The initial mode
does not reset existing directory permissions on later creates.
Two host mounts at one path, or a host mount and a volume there, conflict.
Use agent-skill@1 to package a skill as a mixin; see
examples/review-skill. Ship the whole directory under a Kit-specific
prefix such as /usr/share/example-skills/review, and declare that
absolute source path. A source path must be literal after publishing;
use a build-phase argument if it varies. Supporting scripts and references retain their
relative paths. An optional config.name overrides the source basename
at discovery destinations; it does not rename or rewrite the source.
Agent Kits declare agent-skills@1 at their discovery paths. The runtime
links or otherwise exposes every selected agent-skill@1 bundle at each
path, even when the host store is missing, empty, or disabled. The same
declaration permits host sharing when available, bounded by mode and
host policy; no second directory declaration is needed. Runtime assembly
of these sources is implementation-specific. Different source paths
claiming one effective name conflict at composition, and an existing
destination entry makes a skill request unsatisfiable. Without
any selected destination, a required skill request fails; optional
requests are skipped and recorded. Registration does not execute scripts.
Pin the tool, and say so once. Declare a build-phase version arg, wire it
through to the installer, and reference it from both provides and the
top-level version: — publishing expands all of it, so one value drives the
install, the matchable capability, org.opencontainers.image.version and the
published tag.
A pinned provide is a claim about content, so make the build enforce it. Add a step that re-reads the installed version and fails on mismatch. Pinning the provide without pinning the install is worse than floating: it asserts a version the content may not have.
An unversioned provide is not a resting place. It resolves by falling
through: an explicit @version wins, else a version-shaped consumption
reference, else the descriptor's version: — and :latest is not
version-shaped. So an unversioned provide under version: "1.0.0" publishes
<tool>@1.0.0, the kit's release number wearing the tool's name, which a
lower-bound constraint will not match. With no version: at all it does not
publish: RequireVersionedProvides fails the build. Pin the version, or drop
the provide — a kit with no provides publishes fine, and offering nothing
matchable is honest when the kit cannot know what it installed.
requires is a closed-set check: a name nothing in the composition
provides makes your kit refuse to compose anywhere, which is worse than
saying nothing. It constrains the composed runtime set, not your builder —
a mixin that only touches apt inside a build stage needs no deb/apt, and
adding one there rejects every Alpine or distroless workload that could
otherwise have run the shipped binary perfectly well.
Two things follow, and both cut against the instinct to list whatever a lifecycle hook shells out to:
Never require the platform floor. §12 lets kit content assume bash and
sh, curl, git, a populated CA store, and the agent user at uid 1000.
A hook running curl or git has declared nothing by doing so. Worse,
requires: ["deb/curl"] refuses every workload without a dpkg database —
an Alpine or Wolfi base publishes apk/ names — so the entry rules out bases
that were always going to satisfy it.
A conditional dependency cannot be expressed here, so do not try. There is
no either/or in requires: an entry is a hard precondition on every
composition. A hook that reaches for apt-get only when the tool it installs
is missing works fine on a base that already ships the tool, and
requires: ["deb/apt"] converts "degrades on some bases" into "refuses on
them". Let the hook probe and fail with an actionable message, and say in a
comment that the silence is deliberate — otherwise the next reader adds the
entry back.
The test is not "what do my hooks run" but "what must already be present, on every base, for this kit to work at all". Usually that is nothing.
Where a requirement is real, deb/ names are the right vocabulary and
invented ones are wrong: publishing derives a deb/<pkg> provide from a
workload's dpkg database, so requires: ["deb/apt"], ["deb/jq"] or
["deb/docker-ce"] resolve against any Debian-based workload. On a
multi-platform workload the derived set is the intersection — §9.6 emits a
package only where every published platform agrees on its normalized name and
version — so check each arch, not just your own
(docker run --rm --platform linux/arm64 <base> dpkg-query -W -f='${Version} ${Status}\n' <pkg>),
and never author a deb/ provide — the frontend refuses it.
Recipe patterns for both kinds, including the ownership rules an overlay must satisfy, are in RECIPES.md. Read it before writing a mixin: overlay ownership is the single most common way a working-looking kit is broken.
# 1. validate — the descriptor is checked before any content is built, so this
# fails in a second on a bad field. cacheonly drops the EXPORT, not the
# build: once the descriptor is valid the whole recipe still solves.
cd <kit> && docker buildx build . -f <kit>.yaml --output type=cacheonly
# 2. build, exporting a layout so kit-tck can judge it without a registry
docker buildx build . -f <kit>.yaml -t <kit>:<version> \
--output type=oci,dest=/tmp/<kit>-layout,tar=false
# 3. conformance — NB the tag alone, not <kit>:<version>
kit-tck validate --layout /tmp/<kit>-layout <version>
# 4. run it, no registry needed
sbx run ./<kit> . # a workload
sbx run ./<workload> --kit ./<kit> . # a mixin, composed
sbx kit inspect ./<kit> # resolved declarations, no sandboxInside a sandbox the kit is self-describing: /usr/share/sandbox/kit/<kit>/kit.yaml
is the published descriptor, kit.dockerfile the recipe, and
/var/log/sbx-kit-startup.log the startup hook output.
A build is not proof the kit works. For a mixin especially, compose the
built overlay onto a bare base and run the tool. An overlay shipping a dangling
symlink — which happens whenever an installer relocates a launcher but not its
payload and the build-stage test -x passes because the payload is still
there — draws a kit-tck warning in step 3, but only the composition proves
whether the base supplies the target. Details and the ownership audit are in
RECIPES.md.
Publishing is the build. A kit is an OCI artifact and the frontend has
already written its annotations, staged sources and config, so the thing in
the registry is the kit — there is no pack step, no sidecar artifact and no
kit push subcommand to look for. Add --push to the build that produced the
kit you verified:
docker buildx build . -f <kit>.yaml --platform linux/amd64,linux/arm64 --push \
-t <registry>/<kit>:<version> -t <registry>/<kit>:latest \
--metadata-file /tmp/<kit>-push.jsonPush both platforms in one invocation. One build writes the index consumers resolve through; two single-platform builds pushed to the same tag replace each other, leaving a tag that serves whichever ran last and silently fails for everyone on the other architecture.
Tag the version and latest together. <version> is the descriptor's
expanded version:, so where a version arg drives the install it also names
the tag, and the tag says exactly what the image contains.
Where several kits share one repository, the version belongs in the tag.
A repository per kit is the simple case; a repository holding a family of them
distinguishes kits by tag, which leaves <kit>:<version> nowhere to put the
version. Join them and keep the bare name as the moving tag:
docker buildx build . -f <kit>.yaml --platform linux/amd64,linux/arm64 --push \
-t <registry>/<kits-repo>:<kit>-<version> \
-t <registry>/<kits-repo>:<kit>Publishing only the bare name leaves consumers no way to ask for a particular
build, or to notice they were moved onto a different one. Reference the
immutable tag from anything that has to keep working — and note that a
version-shaped tag is also one of the inputs that answers an unversioned
provides entry, which <kit>-<version> is not. That is a reason to state
version: in the descriptor rather than leaning on how you tagged.
Signing is optional and orthogonal. A signature is stored as its own object in
the repository rather than as part of the image, so it changes neither the
kit's digest nor its annotations, and a signed kit's kit-tck verdict is the
one it already had:
digest=$(jq -r '."containerimage.digest"' /tmp/<kit>-push.json)
cosign sign --yes <registry>/<kit>@"$digest"Sign the digest, never the tag. --metadata-file reports the digest the
push actually produced; a tag is mutable, so a signature naming one attests to
whatever it happened to point at.
For a multi-platform build that digest is the index's, and signing it
signs the index alone — the per-platform manifests beneath it carry no
signature of their own, so a consumer verifying a platform digest directly
finds nothing. Add --recursive to sign each discrete image as well, or say
plainly that only the index is signed and expect verification to name the
index.
In CI, keyless signing avoids managing a key at all: grant the job
id-token: write and cosign takes its identity from the OIDC token. The
matching verification names the identity rather than a public key, and for
GitHub Actions that identity is the workflow, not the repository:
cosign verify <registry>/<kit>@"$digest" \
--certificate-identity 'https://github.com/<org>/<repo>/.github/workflows/publish.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comName the whole identity, not a prefix of it. An identity regexp like
^https://github\.com/<org>/ accepts a certificate from any workflow in
any repository in the organization, so any job anywhere in the org with
id-token: write can mint something that passes — and verification then
proves only that the signature came from somebody in your org, which is not
the question being asked. The signature is only evidence of provenance when
the identity pins the repository, the workflow file and the ref. Where a tag
varies, keep the rest exact and vary only that part:
--certificate-identity-regexp '^https://github\.com/<org>/<repo>/\.github/workflows/publish\.yml@refs/tags/'docker buildx — nothing to install for the frontend; BuildKit pulls
docker/sandbox-kit:3 from the # syntax= line.sbx — install the stable CLI from
Docker Docs /
sbx-releases; current releases
support Kits v3 in local and cloud mode. Note sbx kit validate does
not accept a v3 source kit; sbx kit inspect does.kit-tck — go install github.com/docker/sandbox-kit-spec/v3/cmd/kit-tck@latest.cosign — only if you sign. Nothing in the kit grammar requires it and
no consumer needs it to run a kit.gh for a self-contained tool mixin, hello for the smallest workload,
claude and claude-mixin for one agent in both shapes, motd for the
single-file inline form, team for a set.spec/ disagree, the code wins.Use a group when skipping a capability also needs to skip its hooks,
files, or guidance. Put optional: true on the group, never on its
members (even optional: false is invalid). Groups are nonempty and
cannot nest; required and one-member groups are valid. See
examples/optional-cache/optional-cache.yaml.
The selection API includes every member or none, before composition. A runtime supplies decisions on expanded entries; the API owns atomic selection and ordering. Validate all member configs even in skipped groups. Selected entries must still satisfy cross-entry rules, and a composition conflict is an error rather than a reason to skip another group. Singleton arity is per declaration block; lifecycle lists from selected blocks concatenate, and duplicate file paths are errors.
Selection lasts for one sandbox installation, including restarts. Recreation selects afresh. A failing selected hook is an execution failure, not optional unavailability. Skipping a group does not remove old files from reused volumes, omit image layers, or suppress argument environment exports. Group names are display labels, not merge keys.
Groups extend the unfinished schema-3 grammar in place. Descriptors using them require a reader implementing this extension; older strict readers reject them.
Use ${{ kit.env.HOME }} in capability configuration strings when a path
depends on the final container environment. Image defaults compose first,
argument env exports replace them, and runtime environment overrides win
last. Expansion happens before validation and selection, including inside
groups. Missing names fail; empty values remain empty. This is structural
string substitution, not shell evaluation: $HOME, ${HOME}, and ~/
are unchanged. Do not use environment references in mapping keys or Kit
metadata, and do not pass Kit placeholders inside argument or environment
values. Persist the expanded descriptor for restart.
© docker, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file in skills/create-kit-v3 of docker/sandbox-kit-spec.
Open the folder on GitHubat commit 4be7f4d
Create Kit V3 next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Create Kit V3 this skilldocker/sandbox-kit-spec | 136 | — | ~6.5k | Automated safety check: Pass | Apache-2.0 | |
| Iron Proxy Gateway for NanoClawnanocoai/nanoclaw | 31k | — | ~4.6k | Automated safety check: Notes | MIT | |
| GreptimeDB Dev Docker ImageGreptimeTeam/greptimedb | 6.7k | — | ~4k | Automated safety check: Notes | Apache-2.0 | |
| Senior DevOps Toolkitmaslennikov-ig/claude-code-orchestrator-kit | 260 | 6 repos | ~1.1k | Automated safety check: Notes | Custom licence | |
| LangBot Deployment Guidelangbot-app/LangBot | 18k | — | ~1.2k | Automated safety check: Notes | Apache-2.0 | |
| Build Openshell Mxc WindowsNVIDIA/OpenShell | 15k | — | ~4.9k | Automated safety check: Pass | Apache-2.0 |
nanocoai/nanoclaw
Installs or refreshes Iron Proxy and its Iron Control web console for NanoClaw, with a local Docker setup, database, credentials and a human approval bridge.
GreptimeTeam/greptimedb
Packages a locally built GreptimeDB debug binary into a development-only Docker image for local-cluster testing, with an optional push to a dev registry.
maslennikov-ig/claude-code-orchestrator-kit
Comprehensive DevOps skill for CI/CD, infrastructure automation, containerization, and cloud platforms (AWS, GCP, Azure). Includes pipeline setup…
langbot-app/LangBot
Deploys and configures a LangBot instance with Docker Compose or Kubernetes, covering config.yaml, the Box sandbox runtime, the plugin runtime and the global API key.
NVIDIA/OpenShell
Maintain and validate OpenShell's build-only Windows MSVC lane for x64 and ARM64.
omnigent-ai/omnigent
Brings up the Omnigent server and Postgres as a Docker compose stack on any Docker host, and covers the Dockerfile's runtime and host build targets for extending it to a new platform.
docker/sandbox-kit-spec
Migrates a Docker sandbox kit from the v2 spec.yaml grammar to the v3 kit descriptor, then builds it with docker buildx, runs it with sbx, and verifies it with the kit-tck conformance suite.
Works with
Categories
Authors a new Docker sandbox kit against the v3 descriptor — choosing workload or mixin, writing the descriptor and its content recipe, declaring the capabilities it needs, pinning the tool version…. Create Kit V3 is an agent skill from docker/sandbox-kit-spec, published by the product's own GitHub organization. Authors a new Docker sandbox kit against the v3 descriptor — choosing workload or mixin, writing the descriptor and its content recipe, declaring the capabilities it needs, pinning the tool version, and verifying the result with docker buildx, sbx and kit-tck.
Create Kit V3 fits situations like: creating a new kit from scratch; adding a -mixin variant; packaging a CLI; agent as a sandbox kit.
Run `npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a claude-code`. Or copy the skill folder (skills/create-kit-v3 in docker/sandbox-kit-spec) into .claude/skills/create-kit-v3 in your project. Claude Code loads it when a task matches its description.
Run `npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a codex`. Or copy the skill folder (skills/create-kit-v3 in docker/sandbox-kit-spec) into .agents/skills/create-kit-v3 in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add docker/sandbox-kit-spec --skill create-kit-v3 -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/create-kit-v3, .gemini/skills/create-kit-v3, .github/skills/create-kit-v3 and .opencode/skills/create-kit-v3 in your project.
Going by SKILL.md and its folder, Create Kit V3 needs the command-line tools its instructions call (docker, jq and go). Our summary lists: Docker.
SKILL.md names 3 domains. In commands or code: github.com and token.actions.githubusercontent.com; the agent is likely to contact these when it follows the instructions. As links in the text: docs.docker.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Create Kit V3 is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.5k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Create Kit V3: Iron Proxy Gateway for NanoClaw (nanocoai/nanoclaw, 31k stars), GreptimeDB Dev Docker Image (GreptimeTeam/greptimedb, 6.7k stars), Senior DevOps Toolkit (maslennikov-ig/claude-code-orchestrator-kit, 260 stars) and LangBot Deployment Guide (langbot-app/LangBot, 18k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
docker (a GitHub organization, an official publisher) maintains it in docker/sandbox-kit-spec, which has 136 GitHub stars. The repository holds 2 skills in this directory. The repository was last updated on October 7, 2026.
Source: docker/sandbox-kit-spec on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.