Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

111 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

s0 - Sandbox0 CLI

Command-line interface for Sandbox0.

Installation

One-step install

macOS and Linux:

curl -fsSL https://raw.githubusercontent.com/sandbox0-ai/s0/main/scripts/install.sh | bash

Windows PowerShell:

irm https://raw.githubusercontent.com/sandbox0-ai/s0/main/scripts/install.ps1 | iex

Using Go:

go install github.com/sandbox0-ai/s0/cmd/s0@latest

Release archives

Release 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

Manual install from release archives

Linux

# 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/

macOS

# 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/

Windows

Download s0-windows-amd64.zip or s0-windows-arm64.zip from Releases, extract it, and add s0.exe to your PATH.

Build from Source

git clone https://github.com/sandbox0-ai/s0.git
cd s0
make build
cp bin/s0 /usr/local/bin/

Configuration

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: table

gateway-mode supports:

  • direct: api-url is the working control-plane entrypoint.
  • global: api-url is a Global Gateway entrypoint and workload commands are routed through the locally selected current team's home region.

Mode resolution order:

  1. Explicit gateway-mode in the profile
  2. GET /metadata returned by the API entrypoint
  3. Fallback to direct

Environment Variables

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

GitHub Actions

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.

Setup Action

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 list

For self-hosted or private Sandbox0 deployments, also pass api-url: ${{ vars.SANDBOX0_BASE_URL }}.

Reusable Workflow

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.

Usage

# 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 token

In 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>

Commands

Usage

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.

Team

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.

Admin Region

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.

Sandbox

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.yaml

Sandbox Files

s0 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>

Sandbox Context

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 7

Sandbox Network

s0 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>

Sandbox Services

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>

Template

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.

Volume

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]

Volume Files

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> --recursive

Volume Snapshot

s0 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>

Template Image

s0 template image build [CONTEXT] -t <tag> [-f Dockerfile] [--platform linux/amd64] [--no-cache] [--pull]
s0 template image push <local-image> -t <target-tag>

Examples

# 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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages