> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vpod.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox

> Create sandboxes, run commands, and execute Python code.

## Overview

| Method                         | Description                                                        |
| :----------------------------- | :----------------------------------------------------------------- |
| `Sandbox.create()`             | Create a new sandbox                                               |
| `sandbox.commands.run(cmd)`    | Run a command                                                      |
| `sandbox.commands.interrupt()` | Stop the command that is running                                   |
| `sandbox.code.run(code)`       | Run Python code                                                    |
| `sandbox.close()`              | Shut down the running sandbox (suspended instances are unaffected) |
| `sandbox.suspend()`            | Suspend to disk, returns an instance ID                            |
| `Sandbox.resume(id)`           | Resume a suspended instance                                        |
| `Sandbox.list_instances()`     | List all instances                                                 |
| `Sandbox.destroy(id)`          | Delete a suspended instance from disk                              |

Shell commands and Python code share the same filesystem:

```python theme={null}
from vpod import Sandbox

with Sandbox.create() as sandbox:
    sandbox.commands.run("echo 'from shell' > /tmp/shared.txt")
    sandbox.code.run("print(open('/tmp/shared.txt').read().strip())")
```

<Warning>
  Environment variables don't cross between shell and Python: `sandbox.commands.run("export FOO=bar")` is not visible in `sandbox.code.run(...)`. Use the filesystem to share data between the two.
</Warning>

## Return values

### `sandbox.commands.run(cmd)`

Returns a command result:

| Field       | Type  | Description                    |
| :---------- | :---- | :----------------------------- |
| `stdout`    | `str` | Standard output of the command |
| `stderr`    | `str` | Standard error of the command  |
| `exit_code` | `int` | Exit code of the command       |

```python theme={null}
result = sandbox.commands.run("whoami")
print(result.stdout)    # root
print(result.exit_code)  # 0
```

### `sandbox.code.run(code)`

Returns an execution result:

| Field   | Type            | Description                                             |
| :------ | :-------------- | :------------------------------------------------------ |
| `text`  | `str`           | Output of the executed code                             |
| `error` | `Optional[str]` | Error message if the execution failed, `None` otherwise |
| `logs`  | `list[str]`     | Log lines produced during execution                     |

```python theme={null}
result = sandbox.code.run("print(sum([1, 2, 3]))")
print(result.text)   # 6
print(result.error)  # None
```

## Timeouts

Both `commands.run()` and `code.run()` accept a `timeout` parameter, in seconds. When the timeout is reached, the guest work is interrupted and the call returns instead of waiting for it to finish.

```python theme={null}
with Sandbox.create() as sandbox:
    result = sandbox.code.run("import time; time.sleep(30)", timeout=3)
```

A shell command that hits its timeout returns with `exit_code` `124`:

```python theme={null}
result = sandbox.commands.run("sleep 30", timeout=3)
print(result.exit_code)  # 124
```

## Interrupting a command

A command no longer has to run to its timeout. `interrupt()` stops whatever is in the foreground, and the call that was waiting returns with exit code `130`:

```python theme={null}
import threading

with Sandbox.create() as sandbox:
    threading.Timer(1.0, sandbox.commands.interrupt).start()

    result = sandbox.commands.run("sleep 300")
    print(result.exit_code)  # 130
```

The command really stops. This is not the caller walking away while the guest keeps working, so the sandbox is yours again immediately.

Ctrl-C works too. It used to do nothing while a command was running, because `KeyboardInterrupt` could not land on a thread blocked inside the emulator. It now reaches the command, which makes an interactive REPL behave the way you would expect.

<Note>
  `code.run()` cannot be interrupted yet, so use `timeout` there.

  There is no `signal` parameter, and none is needed: `run()` blocks, so Ctrl-C reaches it directly and `timeout` already covers deadlines. The TypeScript SDK needs an `AbortSignal` because its call returns a promise and the caller keeps going.

  A command that ignores the interrupt runs to its own deadline and is recovered the same way a timeout is.
</Note>

## Streaming output

`commands.run()` hands back everything at once, when the command ends. For a build or a test run that is a long wait followed by a wall of text, so it also takes `on_stdout` and `on_stderr`, called with each piece of output as it arrives:

```python theme={null}
import sys

with Sandbox.create() as sandbox:
    result = sandbox.commands.run(
        "for i in 1 2 3; do echo step $i; sleep 1; done",
        timeout=60,
        on_stdout=sys.stdout.write,
    )

    print(result.exit_code)  # 0
```

The return value does not change. What the callbacks receive concatenates to exactly the `stdout` you get without them, so you can add one to existing code and nothing else moves.

Chunks arrive as the guest produces output, not on a fixed schedule. A command printing two hundred lines with no pauses delivers them in one chunk; one printing a line a second delivers a chunk per line. A command that finishes quickly delivers a single chunk, which is indistinguishable from not streaming, and that is correct: there was nothing to stream.

Streaming composes with interrupts. The chunks that arrived before the stop are kept, and the result still carries exit code `130`.

<Note>
  `code.run()` does not stream, and does not accept `on_stdout`. Shell commands are what tend to run long enough to be worth watching.
</Note>

## `Sandbox.create()` parameters

### `snapshot`

The snapshot to boot from. Defaults to `alpine`. See [Snapshots](/sdk/python/snapshots) for the full catalog.

```python theme={null}
sandbox = Sandbox.create(snapshot="alpine")
```

### `mounts`

Mount host directories into the sandbox. Paths are read-only by default; append `:rw` for read-write access.

```python theme={null}
sandbox = Sandbox.create(mounts={"workspace": "/workspace:rw", "docs": "/docs"})
```
