Sandboxes#

SkyPilot Sandboxes are fast, isolated compute environments that run on your own Kubernetes clusters. Each sandbox is a lightweight pod you can launch on demand, run commands in, and tear down, without provisioning a full cluster.

Sandboxes are built for AI coding agents, RL training rollouts, and parallel evals: workloads that need many short-lived, isolated environments spun up and down quickly. Pre-warmed pools launch sandboxes in under a second, with volumes and secrets injected automatically. Coding agents like Claude provision a sandbox through a tool call and run their generated code in it, isolated from everything else.

Tip

Sandboxes are part of SkyPilot Platform, in limited early access. Sign up here; takes 20 seconds.

Sandboxes: a coding agent provisions a SkyPilot sandbox through a tool call and runs its code there, on your own cluster.

Why sandboxes with SkyPilot#

  • Sub-second launches: pre-warmed pools keep idle environments ready, so a sandbox is live in under a second instead of waiting on image pulls and scheduling.

  • Isolated per pod: every sandbox is its own Kubernetes pod with a dedicated image, CPU, and memory, a natural boundary for running agent-generated or otherwise untrusted code without it touching your other workloads.

  • Secrets stay out of your code: credentials are injected at launch from the SkyPilot Secrets Manager as environment variables, so tokens are never baked into images or hardcoded into the commands an agent runs.

  • Massively parallel: launch thousands of sandboxes in one call for RL rollouts and parallel evals, then fan out commands concurrently.

  • Runs on your infra: sandboxes live on your own Kubernetes clusters, so your code and data never leave your environment, and capacity is simply your existing cluster.

Use cases#

  • AI coding agents: give each agent (e.g. Claude Code) its own disposable environment to read, write, and run code in, isolated from your other work and from other agents.

  • RL training rollouts: spin up thousands of sandboxes to run rollouts in parallel, collect results, and tear them down, all from a single process.

  • Parallel evals: run a large eval suite across many isolated environments at once instead of serializing on one machine.

  • Ephemeral build and CI tasks: execute short-lived builds, tests, or scripts in a clean environment without provisioning a full cluster.

Quickstart#

A sandbox is an isolated pod you create, run commands in, and terminate. Once the SkyPilot CLI and the bundled Sandbox SDK are installed, that is the whole loop:

sky sandbox create provisions a sandbox and drops you straight into an interactive shell; exit the shell and the sandbox is destroyed.

# Create a sandbox and drop into a shell (destroyed on exit).
$ sky sandbox create -n dev
✓ Sandbox dev is ready. Connecting via bash...

# Or keep it running with --detach, then manage it by name.
$ sky sandbox create --detach -n dev
$ sky sandbox ls
$ sky sandbox terminate dev
import sky.sandbox

# Create a sandbox from the built-in `default` pool.
sb = sky.sandbox.create(name='dev')

# Run a command (argv tokens, no implicit shell); get
# stdout / stderr / exit_code back.
result = sb.exec('python', '-c', 'print(2 ** 10)')
print(result['stdout'])  # 1024

# Tear it down.
sb.terminate()

Commands are argv tokens run directly in the pod (no implicit shell). For shell features like pipes, globs, or env-var expansion, invoke a shell explicitly: sb.exec('sh', '-c', 'echo $HOME | wc -c').

Or use the context manager to terminate automatically:

import sky.sandbox

with sky.sandbox.create(name='dev') as sb:
    sb.exec('python', 'train.py')
# Sandbox is terminated on exit.

Working with the SDK#

Beyond create / exec / terminate, the sky.sandbox SDK covers batch and async fan-out, and secret / volume injection:

Pass num_sandboxes to create a batch in one call; names are auto-generated from the prefix (rollout-0001, rollout-0002, …).

import sky.sandbox

sandboxes = sky.sandbox.create(name='rollout', num_sandboxes=1000)
for i, sb in enumerate(sandboxes):
    sb.exec('python', 'rollout.py', str(i))

Every entrypoint has an async sibling on a .aio attribute. A single event loop can drive hundreds of concurrent exec calls.

import asyncio
import sky.sandbox

async def main():
    sandboxes = await sky.sandbox.create.aio(
        name='rollout', num_sandboxes=100)
    try:
        results = await asyncio.gather(
            *(sb.exec.aio('python', 'rollout.py', str(i))
              for i, sb in enumerate(sandboxes)))
    finally:
        # Always tear down, even if an exec raises.
        await asyncio.gather(*(sb.terminate.aio() for sb in sandboxes))
        await sky.sandbox.aclose()  # release the shared session

asyncio.run(main())

Inject secrets and mount volumes at create time.

import sky.sandbox

sb = sky.sandbox.create(
    name='job',
    # Inject secrets from the secrets manager as env vars of the
    # same name.
    secrets=['HF_TOKEN'],
    # Plain (non-secret) env vars.
    env={'PROJECT': 'demo'},
    # Mount existing volumes, keyed by mount path (create them with
    # `sky volumes apply`).
    volumes={'/data': 'shared-data'},
)

The SDK exposes create, exec, terminate, ls, create_pool, set_pool_size, and delete_pool. Every per-call entrypoint has an async sibling on a .aio attribute (sky.sandbox.create.aio(...), sb.exec.aio(...)), so the same names work in event-loop code. See the dashboard’s Sandboxes page to manage pools and running sandboxes in the UI.

Example: Running AI coding agents#

The common pattern is not to run a coding agent inside a sandbox. Instead, the agent runs wherever your application does and is given a tool to provision a sandbox on demand and run its generated code there, isolated from everything else. Below, Claude drives the sky.sandbox SDK through two tools: one to start a sandbox, one to run a command in it.

import anthropic
import sky.sandbox

# Claude runs outside the sandbox and calls these tools to create one on
# demand and run code in it. A chat that never runs code never makes a pod.
sb = None

tools = [
    {'name': 'start_skypilot_sandbox',
     'description': 'Provision a fresh sandbox. Call the first time you '
                    'need to run code.',
     'input_schema': {'type': 'object', 'properties': {}}},
    {'name': 'run_shell',
     'description': 'Run a shell command in the sandbox. State persists '
                    'across calls.',
     'input_schema': {'type': 'object',
                      'properties': {'command': {'type': 'string'}},
                      'required': ['command']}},
]

def dispatch(name, args):
    global sb
    if name == 'start_skypilot_sandbox':
        sb = sky.sandbox.create(name='chat')  # claims a warm pod from the pool
        return 'Sandbox ready.'
    result = sb.exec('sh', '-c', args['command'])  # run_shell
    return result['stdout'] or result['stderr']

client = anthropic.Anthropic()
messages = [{'role': 'user',
             'content': 'Compute the 100th Fibonacci number with Python.'}]
while True:
    resp = client.messages.create(model='claude-opus-4-8', max_tokens=4096,
                                   tools=tools, messages=messages)
    messages.append({'role': 'assistant', 'content': resp.content})
    tool_uses = [b for b in resp.content if b.type == 'tool_use']
    if not tool_uses:
        break
    messages.append({'role': 'user', 'content': [
        {'type': 'tool_result', 'tool_use_id': tu.id,
         'content': dispatch(tu.name, tu.input)} for tu in tool_uses]})

if sb is not None:
    sb.terminate()

Each agent gets its own pod, so many can work in parallel without sharing a filesystem or stepping on each other. Inject per-agent credentials at create time with secrets=[...] so tokens never appear in the commands the agent runs.

Advanced: Warm pools for fast provisioning#

Without a pool, create provisions a fresh pod on demand, which waits on Kubernetes scheduling and the container image pull. A pool keeps a set of warm, pre-provisioned pods ready, so create instead claims an already-running pod, cutting a single sandbox’s launch time by more than 50%. A pool also fixes the shape of its sandboxes: their container image, CPU, and memory.

SkyPilot ships a built-in default pool (a python image), so the quickstart above needs no setup; create your own when you need a different image or size.

Create a pool, and resize it at any time:

import sky.sandbox

# Create a pool with 10 warm pods kept idle and ready.
sky.sandbox.create_pool(
    name='ml-gpu',
    image='nvcr.io/nvidia/pytorch:24.05-py3',
    cpus=8,
    memory_gb=64,
    replicas=10,
)

# Scale the pool up or down at any time.
sky.sandbox.set_pool_size('ml-gpu', replicas=50)

# Launch a sandbox from the pool.
sb = sky.sandbox.create(name='train', pool='ml-gpu')

See also

  • Volumes: persistent storage you can mount into sandboxes.

  • Job Groups for RL: run many parallel jobs and sandboxes together for RL.

  • SkyPilot for Frontier AI: SkyPilot Platform, including the Secrets Manager that injects credentials into sandboxes.

Tip

Sandboxes are part of SkyPilot Platform, in limited early access. Sign up here; takes 20 seconds.