Command-line interface for Sandbox0.
macOS and Linux:
curl -fsSL https://raw.githubusercontent.com/sandbox0-ai/s0/main/scripts/install.sh | bashWindows PowerShell:
irm https://raw.githubusercontent.com/sandbox0-ai/s0/main/scripts/install.ps1 | iexUsing Go:
go install github.com/sandbox0-ai/s0/cmd/s0@latestRelease archives are published at:
https://github.com/sandbox0-ai/s0/releases/latest
Archive names:
s0-linux-amd64.tar.gz
s0-linux-arm64.tar.gz
s0-darwin-amd64.tar.gz
s0-darwin-arm64.tar.gz
s0-windows-amd64.zip
s0-windows-arm64.zip
# AMD64
curl -sLO https://github.com/sandbox0-ai/s0/releases/latest/download/s0-linux-amd64.tar.gz
tar -xzf s0-linux-amd64.tar.gz
chmod +x s0
sudo mv s0 /usr/local/bin/
# ARM64
curl -sLO https://github.com/sandbox0-ai/s0/releases/latest/download/s0-linux-arm64.tar.gz
tar -xzf s0-linux-arm64.tar.gz
chmod +x s0
sudo mv s0 /usr/local/bin/# Intel (AMD64)
curl -sLO https://github.com/sandbox0-ai/s0/releases/latest/download/s0-darwin-amd64.tar.gz
tar -xzf s0-darwin-amd64.tar.gz
chmod +x s0
sudo mv s0 /usr/local/bin/
# Apple Silicon (ARM64)
curl -sLO https://github.com/sandbox0-ai/s0/releases/latest/download/s0-darwin-arm64.tar.gz
tar -xzf s0-darwin-arm64.tar.gz
chmod +x s0
sudo mv s0 /usr/local/bin/Download s0-windows-amd64.zip or s0-windows-arm64.zip from Releases, extract it, and add s0.exe to your PATH.
git clone https://github.com/sandbox0-ai/s0.git
cd s0
make build
cp bin/s0 /usr/local/bin/Configuration file: ~/.s0/config.yaml
current-profile: default
profiles:
default:
api-url: https://api.sandbox0.ai
gateway-mode: direct
current-team-id: team_123
token: ${SANDBOX0_TOKEN}
output:
format: tablegateway-mode supports:
direct:api-urlis the working control-plane entrypoint.global:api-urlis a Global Gateway entrypoint and workload commands are routed through the locally selected current team's home region.
Mode resolution order:
- Explicit
gateway-modein the profile GET /metadatareturned by the API entrypoint- Fallback to
direct
| Variable | Description | Default |
|---|---|---|
SANDBOX0_TOKEN |
API authentication token | - |
SANDBOX0_BASE_URL |
Optional API base URL override for self-hosted or private deployments | https://api.sandbox0.ai |
The GitHub Actions entrypoints in this repository are part of the public integration surface. Keep these paths stable for consumers:
.github/actions/setup-s0/action.yml.github/workflows/template-image.yml
If the implementation changes internally, keep a compatibility stub or wrapper at the same path.
s0 template image build and s0 template image push shell out to Docker.
Use a GitHub-hosted runner with Docker available, or a self-hosted runner with a working Docker daemon.
For CI, prefer a Sandbox0 API key scoped to automation. For image pushes, the recommended team role is builder.
Install s0, add it to PATH, and optionally export SANDBOX0_TOKEN and SANDBOX0_BASE_URL:
jobs:
verify-s0:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: sandbox0-ai/s0/.github/actions/setup-s0@main
with:
token: ${{ secrets.SANDBOX0_TOKEN }}
- run: s0 template listFor self-hosted or private Sandbox0 deployments, also pass api-url: ${{ vars.SANDBOX0_BASE_URL }}.
Build and push a template image with the official reusable workflow:
jobs:
template-image:
uses: sandbox0-ai/s0/.github/workflows/template-image.yml@main
with:
image-tag: my-app:${{ github.sha }}
context: .
dockerfile: Dockerfile
secrets:
sandbox0_token: ${{ secrets.SANDBOX0_TOKEN }}For self-hosted or private Sandbox0 deployments, also pass api-url: ${{ vars.SANDBOX0_BASE_URL }}.
Consume the pushed template image reference from workflow outputs:
jobs:
publish:
uses: sandbox0-ai/s0/.github/workflows/template-image.yml@main
with:
image-tag: my-app:${{ github.sha }}
secrets:
sandbox0_token: ${{ secrets.SANDBOX0_TOKEN }}
deploy:
needs: publish
runs-on: ubuntu-latest
steps:
- run: echo "${{ needs.publish.outputs.template-image }}"For production workflows, pin @main to a published s0 release tag such as @v0.2.4 or @v0.
For Sandbox0 SaaS, you usually only need SANDBOX0_TOKEN. Set SANDBOX0_BASE_URL when your workflow talks to a self-hosted or private Sandbox0 deployment.
# Global flags
s0 [flags] [command]
Flags:
--api-url string Override API URL
-c, --config string Config file (default ~/.s0/config.yaml)
-o, --output string Output format: table|json|yaml (default "table")
-p, --profile string Profile name (default "default")
--token string Override API tokenIn global mode, auth, user, team, and admin commands stay on the configured entrypoint. Workload-facing commands such as sandbox, template, volume, credential, apikey, and registry credential flows use the locally selected current team and switch to the home-region gateway automatically.
If a global-gateway profile has no current team selected yet, create one if needed and then select it locally:
s0 team create --name <name> --home-region <region-id>
s0 team use <team-id>List immutable usage windows for the currently selected team:
s0 usage list
s0 usage list --limit 250 --window-type sandbox.runtime_mib_milliseconds
s0 usage list --cursor <next-cursor>The table output prints the next cursor after each non-final page. Use
--output json or --output yaml to retain the complete typed page, including
region and cluster metadata.
s0 team list
s0 team create --name <name> [--slug <slug>] [--home-region <region-id>]
s0 team use <team-id>s0 team list marks the locally selected current team in the CURRENT column. JSON and YAML output include a current boolean for each team.
s0 admin region list
s0 admin region get <region-id>
s0 admin region create --id <id> --regional-gateway-url <url> [--display-name <name>] [--metering-export-url <url>] [--enabled=true|false]
s0 admin region update <region-id> [--display-name <name>] [--regional-gateway-url <url>] [--metering-export-url <url>] [--enabled=true|false]
s0 admin region delete <region-id>s0 admin region ... targets the Global Gateway region directory and requires a system-admin token. --edge-gateway-url is also accepted as an alias for --regional-gateway-url.
s0 sandbox run <sandbox-id> <input> [--alias <alias>] [--context-id <ctx-id>]
s0 sandbox create -t <template-id> [-f sandbox-config.yaml] [--ttl 3600] [--hard-ttl 7200] [--snapshot-id <rootfs-snapshot-id>] [--mount <volume-id>:/absolute/path] [--wait-for-mounts] [--mount-wait-timeout-ms 45000]
s0 sandbox get <sandbox-id>
s0 sandbox update <sandbox-id> [-f sandbox-update.yaml] [--ttl 3600] [--hard-ttl 7200] [--auto-resume true|false]
s0 sandbox delete <sandbox-id>
s0 sandbox pause <sandbox-id>
s0 sandbox resume <sandbox-id>
s0 sandbox refresh <sandbox-id>
s0 sandbox status <sandbox-id>
s0 sandbox logs <sandbox-id> [--limit 100] [--context-id <ctx-id>] [--stream stdout|stderr|pty] [--watch]
s0 sandbox events <sandbox-id> [--source <source>] [--event-type <type>] [--outcome <outcome>] [--actor-kind <kind>] [--actor-id <id>] [--action <action>] [--resource-type <type>] [--operation-id <id>] [--event-id <uuid>] [--watch]
s0 sandbox metrics <sandbox-id> [--name <metric-name>] [--context-id <ctx-id>] [--watch]
s0 sandbox list [--status <status>] [--template-id <id>] [--paused true|false] [--limit 50] [--offset 0]
s0 sandbox fork <sandbox-id> [--ttl 3600] [--hard-ttl 7200]s0 sandbox get <sandbox-id> prints the SSH connection fields returned by sandbox detail when they are available, including SSH Host, SSH Port, and SSH Username.
s0 sandbox logs/events/metrics query the per-sandbox observability backend. s0 sandbox events returns canonical signed audit facts, including API access, lifecycle, network, process, and file events. Filter by actor, action, resource, operation, outcome, source, or event type; use --event-id alone for exact lookup of one event and any conflicting payload variant. Use --watch for realtime records, --cursor to resume, --start-time / --end-time for absolute windows, or --since 10m for a relative window. Table output shows event identity, actor, action, resource, operation, signature status, and conflict state; use -o json or -o yaml for the full canonical record. s0 sandbox logs prints log messages by default.
Manage the current user's SSH public keys with:
s0 user ssh-key list
s0 user ssh-key add --public-key-file ~/.ssh/id_ed25519.pub
s0 user ssh-key delete <ssh-key-id>Bootstrap mounts can be requested as part of sandbox creation:
s0 volume create
s0 sandbox create -t default \
--snapshot-id <rootfs-snapshot-id> \
--mount <volume-id>:/workspace/data
# Or provide a full claim request file.
cat <<'EOF' > sandbox-claim.yaml
template: default
snapshot_id: <rootfs-snapshot-id>
mounts:
- sandboxvolume_id: <volume-id>
mount_point: /workspace/data
config:
ttl: 3600
EOF
s0 sandbox create -f sandbox-claim.yamls0 sandbox files ls [path] -s <sandbox-id>
s0 sandbox files cat <path> -s <sandbox-id>
s0 sandbox files stat <path> -s <sandbox-id>
s0 sandbox files mkdir <path> --parents -s <sandbox-id>
s0 sandbox files rm <path> -s <sandbox-id>
s0 sandbox files mv <src> <dst> -s <sandbox-id>
s0 sandbox files upload <local> <remote> -s <sandbox-id>
s0 sandbox files download <remote> <local> -s <sandbox-id>
s0 sandbox files write <path> --stdin|--data <content> -s <sandbox-id>
s0 sandbox files watch <path> --recursive -s <sandbox-id>s0 sandbox context list -s <sandbox-id>
s0 sandbox context get <ctx-id> -s <sandbox-id>
s0 sandbox context create --type repl|cmd [--alias <name>] [--command <cmd>] [--cwd <dir>] [--env KEY=VALUE] [--wait] -s <sandbox-id>
s0 sandbox context delete <ctx-id> -s <sandbox-id>
s0 sandbox context restart <ctx-id> -s <sandbox-id>
s0 sandbox context exec <ctx-id> <input> -s <sandbox-id>
s0 sandbox context signal <ctx-id> <signal> -s <sandbox-id>s0 sandbox run is the REPL-oriented convenience command. It preserves state by
reusing a matching running REPL context when there is exactly one match for the
requested alias. s0 sandbox exec remains the one-shot command path.
Use --stream when you want non-TTY commands to stream stdout/stderr live. The command exits when the remote process emits its terminal done event, and the CLI returns the remote exit code.
s0 sandbox exec <sandbox-id> --stream -- sh -c 'echo hello && sleep 1 && exit 7'
# exits locally with status 7s0 sandbox create -t <template-id> -f sandbox-config.yaml
s0 sandbox network get -s <sandbox-id>
s0 sandbox network update --mode allow-all|block-all [--allow-cidr <cidr>] [--allow-domain <domain>] [--allow-port <port[/proto]|start-end[/proto]>] [--deny-cidr <cidr>] [--deny-domain <domain>] [--deny-port <port[/proto]|start-end[/proto]>] [--traffic-rule '<json>'] [--protocol-rule '<json>'] [--credential-rule '<json>'] [--credential-binding '<json>'] -s <sandbox-id>
s0 sandbox network update --policy-file network.yaml -s <sandbox-id>
# Claim-time network policy via sandbox config file
cat <<'EOF' > sandbox-config.yaml
network:
mode: block-all
egress:
trafficRules:
- name: allow-github-api
action: allow
domains: [api.github.com]
ports:
- port: 443
protocol: tcp
credentialBindings:
- ref: gh-token
sourceRef: github-source
projection:
type: http_headers
httpHeaders:
headers:
- name: Authorization
valueTemplate: "Bearer {{ .token }}"
EOF
s0 sandbox create -t default -f sandbox-config.yaml
# Simple compatibility path: block all except HTTPS to GitHub
s0 sandbox network update --mode block-all \
--allow-domain github.com \
--allow-port 443/tcp \
-s <sandbox-id>
# Recommended for complex policies: edit a YAML file and apply it directly
cat <<'EOF' > network.yaml
mode: block-all
egress:
trafficRules:
- name: allow-ssh
action: allow
appProtocols: [ssh]
ports:
- port: 22
protocol: tcp
protocolRules:
- name: api-http-readonly
protocol: http
domains: [api.example.com]
ports:
- port: 8080
protocol: tcp
http:
methods:
allowed: [GET, HEAD]
denied: [POST]
paths:
allowedPrefixes: [/api/]
deniedPrefixes: [/admin/]
credentialBindings:
- ref: gh-token
sourceRef: github-source
projection:
type: http_headers
httpHeaders:
headers:
- name: Authorization
valueTemplate: "Bearer {{ .token }}"
EOF
s0 sandbox network update --policy-file network.yaml -s <sandbox-id>
# Placeholder substitution: sandbox code sends an opaque placeholder, and egress auth replaces it at the boundary
cat <<'EOF' > placeholder-network.yaml
mode: block-all
egress:
trafficRules:
- name: allow-example-api
action: allow
domains: [api.example.com]
ports:
- port: 8080
protocol: tcp
credentialRules:
- name: api-token
credentialRef: api-token
protocol: http
domains: [api.example.com]
ports:
- port: 8080
protocol: tcp
failurePolicy: fail-closed
credentialBindings:
- ref: api-token
sourceRef: api-token-source
projection:
type: placeholder_substitution
placeholderSubstitution:
replacements:
- placeholder: s0env_api_token
valueTemplate: "{{ .token }}"
locations: [query, header, body]
EOF
s0 sandbox network update --policy-file placeholder-network.yaml -s <sandbox-id>
# Script-oriented structured flags: allow SSH traffic, control HTTP operations, then inject outbound auth for GitHub API
s0 sandbox network update --mode block-all \
--traffic-rule '{"name":"allow-ssh","action":"allow","appProtocols":["ssh"],"ports":[{"port":22,"protocol":"tcp"}]}' \
--protocol-rule '{"name":"api-http-readonly","protocol":"http","domains":["api.example.com"],"ports":[{"port":8080,"protocol":"tcp"}],"http":{"methods":{"allowed":["GET","HEAD"],"denied":["POST"]},"paths":{"allowedPrefixes":["/api/"],"deniedPrefixes":["/admin/"]}}}' \
--credential-binding '{"ref":"gh-token","sourceRef":"github-source","projection":{"type":"http_headers","httpHeaders":{"headers":[{"name":"Authorization","valueTemplate":"Bearer {{ .token }}"}]}}}' \
--credential-rule '{"name":"github-auth","credentialRef":"gh-token","protocol":"https","domains":["api.github.com"],"ports":[{"port":443,"protocol":"tcp"}]}' \
-s <sandbox-id>s0 sandbox service get -s <sandbox-id>
s0 sandbox service update --services-file services.yaml -s <sandbox-id>
s0 sandbox service delete <service-id> -s <sandbox-id>
s0 sandbox service clear -s <sandbox-id>
# Claim-time sandbox services via sandbox config file
cat <<'EOF' > sandbox-config.yaml
services:
- id: app
port: 8080
ingress:
public: true
routes:
- id: app
path_prefix: /
resume: true
EOF
s0 sandbox create -t default -f sandbox-config.yaml
# Update existing sandbox services
cat <<'EOF' > services.yaml
services:
- id: app
port: 8080
ingress:
public: true
routes:
- id: app
path_prefix: /
resume: true
EOF
s0 sandbox service update --services-file services.yaml -s <sandbox-id>
# Function service: public ingress is still configured as a sandbox service.
# The function source is carried in the service config and executed by procd.
# The same service config can serve HTTP, SSE, and WebSocket routes.
cat <<'EOF' > function-services.yaml
services:
- id: handler
runtime:
type: function
function:
runtime: python
handler: handler
source:
type: inline
code: |
def handler(request):
return {
"status": 200,
"headers": {"content-type": "text/plain"},
"body": "ok",
}
ingress:
public: true
routes:
- id: handler
path_prefix: /
resume: true
EOF
s0 sandbox service update --services-file function-services.yaml -s <sandbox-id>s0 template list
s0 template get <template-id>
s0 template create --id <id> --spec-file template.yaml
s0 template create --id <id> --from-sandbox <sandbox-id> [--overrides-file overrides.yaml] [--idempotency-key <key>] [--wait] [--wait-timeout <duration>] [--poll-interval <duration>]
s0 template update <template-id> --spec-file template.yaml
s0 template delete <template-id>With --from-sandbox, the optional overrides file is the override object itself
and may contain displayName, description, tags, or pool:
displayName: Python Ready
tags:
- python
pool:
minIdle: 0
maxIdle: 0--wait waits for the captured root filesystem to be published and the new
template to become claimable. Request acceptance is not the rootfs capture
point, so keep the source sandbox available and avoid rootfs writes while the
template is in the capturing stage.
Timing out or interrupting the wait only stops the CLI; it does not cancel the
server-side template build.
s0 volume list
s0 volume get <volume-id>
s0 volume create [--access-mode RWO|ROX|RWX] [--snapshot-id <snapshot-id>]
s0 volume create --backend s3 --s3-bucket <bucket> [--s3-prefix <prefix>] [--s3-region <region>] [--s3-provider aws|ali|r2] [--s3-endpoint-url <url>] [--s3-access-key <key> --s3-secret-key <secret>] [--s3-session-token <token>]
s0 volume delete <volume-id> [--force]s0 volume files ls <volume-id> [path]
s0 volume files cat <volume-id> <path>
s0 volume files stat <volume-id> <path>
s0 volume files mkdir <volume-id> <path> [--parents]
s0 volume files rm <volume-id> <path>
s0 volume files mv <volume-id> <src> <dst>
s0 volume files upload <volume-id> <local> <remote>
s0 volume files download <volume-id> <remote> <local>
s0 volume files write <volume-id> <path> --stdin|--data <content>
s0 volume files watch <volume-id> <path> --recursives0 volume snapshot list <volume-id>
s0 volume snapshot get <volume-id> <snapshot-id>
s0 volume snapshot create <volume-id> -n <name> [-d <description>]
s0 volume snapshot delete <volume-id> <snapshot-id>
s0 volume snapshot restore <volume-id> <snapshot-id>s0 template image build [CONTEXT] -t <tag> [-f Dockerfile] [--platform linux/amd64] [--no-cache] [--pull]
s0 template image push <local-image> -t <target-tag># Set token via environment variable
export SANDBOX0_TOKEN=your-token
# List templates
s0 template list
# Create a sandbox
s0 sandbox create -t my-template --ttl 3600
# Fork a paused sandbox with a new lifecycle
s0 sandbox fork <sandbox-id> --ttl 3600 --hard-ttl 7200
# List files in sandbox
s0 sandbox files ls /home/user -s <sandbox-id>
# Operate on volume files directly without mounting into a sandbox first
s0 volume files write <volume-id> /docs/readme.txt --data "Hello from s0"
s0 volume files cat <volume-id> /docs/readme.txt
s0 volume files watch <volume-id> /docs --recursive
# Execute code in sandbox
s0 sandbox context create --type repl --alias python -s <sandbox-id>
s0 sandbox context exec <ctx-id> "print('hello')" -s <sandbox-id>
# Publish HTTP traffic through sandbox services
s0 sandbox service update --services-file services.yaml -s <sandbox-id>
# Build and push a template image
s0 template image build . -t my-image:v1
s0 template image push my-image:v1 -t my-image:v1
# Create a volume and snapshot
s0 volume create
s0 volume snapshot create <volume-id> -n my-snapshot