Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 52 additions & 41 deletions content/manuals/ai/sandboxes/customize/kit-examples.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Kit examples
linkTitle: Examples
description: Copy-and-adapt spec.yaml snippets for common mixin and sandbox kit patterns — static files, install commands, shell customization, background services, initFiles, Claude Code skills, and agent forks.
description: Copy-and-adapt spec.yaml snippets for common mixin and sandbox kit patterns — static files, install commands, shell customization, background services, setup files, Claude Code skills, and agent forks.
keywords: sandboxes, sbx, kits, mixins, examples, patterns, skills
weight: 25
---
Expand Down Expand Up @@ -35,13 +35,13 @@ ruff-lint/
```

```yaml {title="ruff-lint/spec.yaml"}
schemaVersion: "1"
schemaVersion: "2"
kind: mixin
name: ruff-lint
displayName: Ruff
description: Python linting with shared team config

commands:
setup:
install:
- command: "uv tool install ruff@latest"
user: "1000"
Expand All @@ -56,12 +56,20 @@ select = ["E", "F", "I"]

## Install a tool at sandbox creation

`commands.install` runs once per sandbox, at creation time. It's where
`setup.install` runs once per sandbox, at creation time. It's where
Comment thread
dvdksn marked this conversation as resolved.
anything that needs to land in the image goes — package managers
(`apt-get`, `pip`, `npm`), binary downloads, or vendor install scripts.

> [!TIP]
> Each new sandbox runs all `setup.install` commands. The results aren't
> cached between sandboxes. Creating a kit avoids building and distributing an
> image, so kits work well for smaller, composable changes. For substantial
> build or installation steps, consider a
> [custom template](templates.md#build-a-custom-template). Sandboxes reuse
> template images from the local cache.

```yaml
commands:
setup:
install:
- command: "apt-get update && apt-get install -y jq"
- command: "curl -fsSL https://example.com/install.sh | sh"
Expand Down Expand Up @@ -93,7 +101,7 @@ instance, needs `zip` and `unzip`, so add an
> failed fetch fails the step:
>
> ```yaml
> commands:
> setup:
> install:
> - command: "curl -fsSL https://example.com/install.sh -o /tmp/install.sh && bash /tmp/install.sh"
> user: "1000"
Expand All @@ -117,13 +125,13 @@ you'd set a custom environment variable; see the
[FAQ](../faq.md#how-do-i-set-custom-environment-variables-inside-a-sandbox).

```yaml {title="nvm/spec.yaml"}
schemaVersion: "1"
schemaVersion: "2"
kind: mixin
name: nvm
displayName: nvm
description: Node version manager available in every shell

commands:
setup:
install:
- command: "curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash"
user: "1000"
Expand Down Expand Up @@ -179,11 +187,11 @@ the kit and install each certificate before running
`update-ca-certificates`.

```yaml {title="internal-ca/spec.yaml"}
schemaVersion: "1"
schemaVersion: "2"
kind: mixin
name: internal-ca

commands:
setup:
install:
- command: "install -m 0644 /home/agent/internal-ca.crt /usr/local/share/ca-certificates/internal-ca.crt && update-ca-certificates"
user: "0"
Expand All @@ -196,13 +204,13 @@ certificates without further configuration.

## Run a background service

`commands.startup` runs on every sandbox start. To keep a long-running
`setup.startup` runs on every sandbox start. To keep a long-running
service such as a dev server or daemon alive, set `background: true`. The
sandbox runs the command in the background and replays startup commands on
each start, so the service comes back after a stop/start cycle:

```yaml
commands:
setup:
startup:
- command: ["my-service", "--port", "8080"]
user: "1000"
Expand All @@ -215,7 +223,7 @@ for debugging, wrap the command in a shell and redirect to a log file. Let
trailing `&` yourself:

```yaml
commands:
setup:
startup:
- command:
- sh
Expand All @@ -228,16 +236,16 @@ commands:
An empty log file tells you the wrapper ran; a populated one tells you why
the service failed.

## Bake runtime values into a file with initFiles
## Write runtime values to a file

When a config file needs a value that isn't known until sandbox start
— most often the absolute workspace path — use `commands.initFiles`.
— most often the absolute workspace path — use `setup.files`.
The `${WORKDIR}` placeholder expands to the primary workspace path
when the file is written.

```yaml
commands:
initFiles:
setup:
files:
- path: /home/agent/.local/bin/start-code-server.sh
content: |
exec code-server --bind-addr 0.0.0.0:8080 --auth none "${WORKDIR}"
Expand All @@ -253,7 +261,7 @@ commands:
`mode: "0755"` makes the generated file executable so the startup
command can invoke it directly.

Use `initFiles` instead of a static file whenever the content depends
Use `setup.files` instead of a static file whenever the content depends
on a runtime value. Use a static file otherwise.

> [!TIP]
Expand All @@ -280,7 +288,7 @@ docker-review/
```

```yaml {title="docker-review/spec.yaml"}
schemaVersion: "1"
schemaVersion: "2"
kind: mixin
name: docker-review
displayName: Dockerfile review skill
Expand Down Expand Up @@ -313,12 +321,12 @@ for details.
Sandboxes seed settings files for some built-in agents during setup.
For example, the sandbox writes `/home/agent/.claude/settings.json`
for the `claude` agent. This happens after the kit's static files and
`initFiles`, so a kit can't override those paths with either mechanism.
Use `commands.startup` instead, which runs after the sandbox seeds its
`setup.files`, so a kit can't override those paths with either mechanism.
Use `setup.startup` instead, which runs after the sandbox seeds its
files:

```yaml
commands:
setup:
startup:
- command:
- sh
Expand All @@ -344,34 +352,37 @@ built-in `claude` agent but drops `--dangerously-skip-permissions` so
every tool call prompts for approval:

```yaml {title="claude-safe/spec.yaml"}
schemaVersion: "1"
schemaVersion: "2"
kind: sandbox
name: claude-safe
displayName: Claude Code (with approval prompts)
description: Claude Code without --dangerously-skip-permissions

sandbox:
image: "docker/sandbox-templates:claude-code-docker"
aiFilename: CLAUDE.md
entrypoint:
run: [claude]

network:
serviceDomains:
api.anthropic.com: anthropic
console.anthropic.com: anthropic
serviceAuth:
anthropic:
headerName: x-api-key
valueFormat: "%s"
allowedDomains:
- "claude.com:443"
entrypoint: [claude]

agentInstructions:
filename: CLAUDE.md

permissions:
network:
allow:
- "claude.com:443"
- "api.anthropic.com:443"
- "console.anthropic.com:443"

credentials:
sources:
anthropic:
env:
- ANTHROPIC_API_KEY
- service: anthropic
apiKey:
name: ANTHROPIC_API_KEY
inject:
- domain: api.anthropic.com
header: x-api-key
format: "%s"
- domain: console.anthropic.com
header: x-api-key
format: "%s"
```

Launch with the kit's `name:` as the agent argument to `sbx run`:
Expand Down
Loading