Sandboxes
A sandbox is an isolated environment where code can be executed safely. Code Sandboxes provides a unified API across different execution backends.
Creating a Sandbox
Use Sandbox.create() to create a new sandbox:
Canonical variant names are jupyter, docker, eval, monty, kaggle,
colab, modal, and datalayer. Older local-* names are no longer supported.
from code_sandboxes import Sandbox
# Create with defaults (datalayer variant)
sandbox = Sandbox.create()
# Create with specific variant
sandbox = Sandbox.create(variant="datalayer")
# Create with timeout and environment
sandbox = Sandbox.create(
variant="datalayer",
timeout=300,
environment="python-cpu-env",
)
# Create with GPU support
sandbox = Sandbox.create(
variant="datalayer",
gpu="T4",
cpu=4.0,
memory=8192,
)
# Create with network policy
sandbox = Sandbox.create(
variant="eval",
network_policy="none", # block all outbound connections
)
Sandbox Variants
Concrete implementations are available from top-level modules:
from code_sandboxes.jupyter_sandbox import JupyterSandbox
from code_sandboxes.docker_sandbox import DockerSandbox
from code_sandboxes.eval_sandbox import EvalSandbox
from code_sandboxes.datalayer_sandbox import DatalayerSandbox
Each section below explains how to configure each variant.
| Variant | Summary |
|---|---|
jupyter | Jupyter kernel-backed execution with persistent state |
eval | In-process exec() for fast development-only runs |
monty | Secure in-process Python subset via Monty |
docker | Jupyter execution in a Docker container |
kaggle | Kaggle runtime (interactive or batch) |
colab | Google Colab runtime via runtime proxy |
modal | Modal container execution |
datalayer | Datalayer managed runtime with optional GPU |
jupyter
Runs code against a local or remote Jupyter Server and connects using
jupyter-kernel-client. This variant provides process isolation via the Jupyter
kernel and persistent state across requests.
- Requirements:
jupyter_serverandjupyter-kernel-client(included by default). - Parameters:
server_url,token,host/port(when the sandbox starts its own server),python_executable.
How to obtain the token:
- If you start the server yourself, you choose the token:
jupyter server --port 8888 --IdentityProvider.token MY_TOKEN
- For an already-running server, list servers and read the
token=...value:You can also pass the fulljupyter server list# http://localhost:8888/?token=abcd1234... :: /home/you/notebookshttp://host:port/?token=...URL asserver_url; the token is parsed automatically. - If you omit
server_url,JupyterSandboxstarts and manages its own local Jupyter Server and generates the token for you — no configuration needed.
# Connect to an existing server:
with Sandbox.create(
variant="jupyter",
server_url="http://localhost:8888",
token="MY_TOKEN",
) as sandbox:
sandbox.run_code("x = 40")
result = sandbox.run_code("x + 2")
print(result.text) # 42
# Or let the sandbox manage a local server automatically:
with Sandbox.create(variant="jupyter") as sandbox:
print(sandbox.run_code("1 + 1").text) # 2
eval
Uses Python's exec() for code execution. No isolation, but fast and simple for development.
- Credentials / parameters: none — nothing to configure.
- ⚠️
evalshares memory with the host process and provides no sandboxing. Never run untrusted code with it.
with Sandbox.create(variant="eval") as sandbox:
result = sandbox.run_code("x = 1 + 1")
result = sandbox.run_code("print(x)") # prints 2
monty
Runs code in Monty, a minimal, secure Python
interpreter written in Rust (pydantic-monty). Monty runs a restricted subset of
Python in-process with microsecond startup and no access to the host filesystem,
environment, or network unless explicitly granted. It is ideal for short,
LLM-generated snippets where a full container or kernel would be overkill.
Session state (variables, imports, definitions) persists across run_code calls.
Note that Monty supports only a subset of Python — third-party libraries and rich
display outputs are not available.
- Requirements:
code-sandboxes[monty](installspydantic-monty). - Credentials: none required (fully local, in-process).
- Optional parameters (on
MontySandbox):type_check(type-check before running),type_check_stubs,external_functions({name: callable}host functions the code may call),limits(memory / stack / time limits).
with Sandbox.create(variant="monty") as sandbox:
sandbox.run_code("x = 21")
result = sandbox.run_code("x * 2")
print(result.text) # 42
You can expose host callables to the sandboxed code and enable type checking:
from code_sandboxes.monty_sandbox import MontySandbox
sandbox = MontySandbox(
type_check=True,
external_functions={"now": lambda: "2026-01-01"},
)
sandbox.start()
sandbox.run_code("print(now())")
Docker
Runs a Jupyter Server inside a Docker container for local isolated execution and
connects using jupyter-kernel-client.
- Requirements:
code-sandboxes[docker]plus a running Docker Engine (verify withdocker version). - Credentials: none — the kernel
tokenis generated automatically. - Parameters:
image(defaultcode-sandboxes-jupyter:latest),container_name,host/container_port,auto_remove,workdir.
Build the default image used by DockerSandbox:
docker build -t code-sandboxes-jupyter:latest -f docker/Dockerfile .
with Sandbox.create(variant="docker", image="code-sandboxes-jupyter:latest") as sandbox:
result = sandbox.run_code("import sys; print(sys.version)")
print(result.stdout)
Kaggle
Runs code on Kaggle with two transparent modes:
- Interactive kernel mode using
KaggleKernelClient(runtime proxy URL). - Batch job mode using
KaggleKernelExecutorwhen no runtime connection details are supplied.
- Requirements:
code-sandboxes[kaggle]. - Interactive parameters:
server_url, optionalkernel_id, optionalchannels_url, optionaltoken. - Batch credentials:
~/.kaggle/kaggle.jsonorKAGGLE_API_KEY.
from code_sandboxes import Sandbox
# Interactive mode (existing session / runtime proxy)
with Sandbox.create(
variant="kaggle",
channels_url="wss://kkb-production.jupyter-proxy.kaggle.net/k/.../proxy/api/kernels/.../channels?session_id=...",
) as sandbox:
print(sandbox.run_code("print(1 + 1)").text)
# Transparent batch mode (no runtime URL required)
with Sandbox.create(variant="kaggle") as sandbox:
result = sandbox.run_code("print('hello from kaggle batch')")
print(result.text or result.stdout)
# Stream status updates and outputs while the Kaggle job runs.
with Sandbox.create(variant="kaggle") as sandbox:
for event in sandbox.run_code_streaming("print('hello from kaggle stream')"):
if hasattr(event, "line"):
print(event.line)
Colab
Runs code against a Google Colab runtime. Colab exposes a Jupyter-compatible
kernel behind an authenticating proxy, so this variant connects using
jupyter-kernel-client's ColabKernelClient under the hood.
- Requirements:
code-sandboxes[colab](installsjupyter-kernel-client). - Parameters:
server_url,kernel_id,proxy_token(pass as keyword arguments or through the sandbox configuration). You can also passchannels_urland let the client parse the values.
How to obtain these values — they are the pieces of the WebSocket URL Colab's own frontend uses to reach your runtime:
wss://<host>/api/kernels/<kernel_id>/channels?session_id=<...>&colab-runtime-proxy-token=<proxy_token>&colab-client-agent=web
Read them from your browser's developer tools while a Colab runtime is connected:
- Open your notebook on colab.research.google.com and connect to a runtime (Runtime → Connect, or run any cell).
- Open DevTools (
F12) → Network tab → WS filter, then run a cell to trigger kernel traffic. - Click the
.../api/kernels/<kernel_id>/channels?...request and read off:server_url— scheme + host before/api/kernels(changewss://tohttps://). Colab assigns a per-session host such ashttps://8080-m-s-kkb-...-d.us-east1-0.prod.colab.dev; there is usually no/tun/m/...path segment.kernel_id— the UUID right after/api/kernels/.proxy_token— thecolab-runtime-proxy-tokenquery parameter (same value as theX-Colab-Runtime-Proxy-Tokenrequest header). Ignore thesession_idandcolab-client-agentparameters.
Consumer Colab does not expose an official third-party API to provision runtimes from scratch. Start/connect a runtime in the Colab UI first, then reuse it here. The values are tied to your Colab session and are short-lived — refresh them after the runtime is reassigned or reconnected.
with Sandbox.create(
variant="colab",
server_url="https://8080-m-s-kkb-...-d.us-east1-0.prod.colab.dev",
kernel_id="c9bba548-3995-4f26-8e1a-7b8fbb10c578",
proxy_token="eyJhbGci....",
) as sandbox:
sandbox.run_code("x = 40")
result = sandbox.run_code("x + 2")
print(result.text) # 42
Or pass a channels URL directly:
with Sandbox.create(
variant="colab",
channels_url=(
"wss://<host>/api/kernels/<kernel_id>/channels"
"?session_id=<...>&colab-runtime-proxy-token=<proxy_token>&colab-client-agent=web"
),
) as sandbox:
print(sandbox.run_code("print(1 + 1)").text)
Modal
Runs code in a Modal cloud sandbox, providing fully isolated, on-demand containers with configurable images and secrets.
Each run_code call executes in a fresh python -c process, so state does not
persist across calls (use a single multi-statement snippet if you need shared
state). Configure the image with additional pip packages as needed.
- Requirements:
code-sandboxes[modal](installsmodal). - Parameters:
app_name,image(a prebuiltmodal.Image),pip_packages,python_executable.
How to obtain Modal credentials:
- Create a free account at modal.com.
- Authenticate the CLI (opens a browser, writes
~/.modal.toml):This is enough for local use: the Modal SDK reads credentials frommodal token new~/.modal.tomlautomatically. - Alternatively, create a token in the Modal dashboard (Settings → API Tokens)
and export
MODAL_TOKEN_IDandMODAL_TOKEN_SECRET.
Do you need both MODAL_TOKEN_ID and MODAL_TOKEN_SECRET?
- For environment-based auth (CI/CD, containers, hosted runners): yes, you need both values because Modal authenticates with a token pair (public id + secret).
- For local development with
modal token new: not necessarily. The SDK can authenticate directly from~/.modal.toml.
If you need environment variables, you can export them from your local config:
python - <<'PY'
import pathlib
import tomllib
cfg = tomllib.loads(pathlib.Path("~/.modal.toml").expanduser().read_text())
profile = cfg.get("default", cfg)
token_id = profile.get("token_id")
token_secret = profile.get("token_secret")
if token_id and token_secret:
print(f"export MODAL_TOKEN_ID={token_id}")
print(f"export MODAL_TOKEN_SECRET={token_secret}")
else:
raise SystemExit("Could not find token_id/token_secret in ~/.modal.toml")
PY
with Sandbox.create(
variant="modal",
pip_packages=["numpy"],
) as sandbox:
result = sandbox.run_code("import numpy as np; print(np.arange(3).sum())")
print(result.stdout) # "3"
datalayer
Cloud-based execution with full isolation, GPU support, snapshots, and persistence.
- Requirements:
code-sandboxes[datalayer](installsagent_runtimes). - Parameters:
token(defaults toDATALAYER_API_KEY),run_url,snapshot_name, plus creation options likeenvironment,gpu,cpu,memory.
How to obtain Datalayer credentials:
- Create an account at datalayer.ai.
- Generate an API token from your account settings (IAM → Tokens / API Keys).
- Export
DATALAYER_API_KEY(or pass it as thetokenparameter). SetDATALAYER_RUN_URLonly for a self-hosted / custom deployment.
import os
os.environ["DATALAYER_API_KEY"] = "your-datalayer-token"
with Sandbox.create(
variant="datalayer",
gpu="A100",
environment="python-gpu-env",
) as sandbox:
sandbox.run_code("import torch; print(torch.cuda.is_available())")
Environments
List available environments for a sandbox variant and pick one when creating a sandbox.
from code_sandboxes import Sandbox
environments = Sandbox.list_environments(variant="datalayer")
for env in environments:
print(f"{env.name}: {env.title}")
# Select the first environment
if environments:
sandbox = Sandbox.create(
variant="datalayer",
environment=environments[0].name,
)
sandbox.start()
sandbox.terminate()
Code Execution
Execute Python code with run_code():
with Sandbox.create() as sandbox:
# Simple execution
result = sandbox.run_code("print('hello')")
print(result.stdout) # "hello"
# Check success
if result.success:
print("Code executed successfully")
elif not result.execution_ok:
print(f"Execution error: {result.execution_error}")
elif result.exit_code not in (None, 0):
print(f"Process exited with code: {result.exit_code}")
else:
print(f"Code error: {result.code_error}")
# Multi-statement blocks return the last expression
result = sandbox.run_code("""
x = 10
x * 2
""")
print(result.text)
## Async Execution
Use `await` directly in `run_code()` when the sandbox supports async execution
(eval, jupyter, and datalayer). The last expression is
returned in `results` just like sync code.
```python
with Sandbox.create(variant="eval") as sandbox:
result = sandbox.run_code("""
import asyncio
async def fetch_value():
await asyncio.sleep(0.01)
return 21
value = await fetch_value()
print(f"value: {value}")
value * 2
""")
assert result.success
print(result.stdout) # "value: 21"
print(result.text) # "42"
## State Persistence
All sandbox variants keep state (variables, imports, and definitions)
within the same sandbox instance. For example:
```python
with Sandbox.create() as sandbox:
sandbox.run_code("counter = 1")
sandbox.run_code("counter += 1")
result = sandbox.run_code("counter")
print(result.text) # 2
Streaming Output
from code_sandboxes import OutputMessage
def on_output(msg: OutputMessage):
print(f"[{msg.stream}] {msg.line}")
with Sandbox.create() as sandbox:
result = sandbox.run_code(
"for i in range(5): print(f'Step {i}')",
on_stdout=on_output,
)
Filesystem Operations
Access files within the sandbox:
with Sandbox.create() as sandbox:
# Write files
sandbox.files.write("/data/test.txt", "Hello World")
# Read files
content = sandbox.files.read("/data/test.txt")
# List directory
for f in sandbox.files.list("/data"):
print(f"{f.name} ({f.size} bytes)")
# Create directories
sandbox.files.mkdir("/data/subdir")
# Upload/download
sandbox.files.upload("local.txt", "/remote/file.txt")
sandbox.files.download("/remote/file.txt", "downloaded.txt")
Command Execution
Run shell commands in the sandbox:
with Sandbox.create() as sandbox:
# Run command and wait
result = sandbox.commands.run("ls -la /")
print(result.stdout)
# Execute with streaming output
process = sandbox.commands.exec("python", "-c", "print('hello')")
for line in process.stdout:
print(line, end="")
# Install system packages
sandbox.commands.install_system_packages(["curl", "wget"])
Lifecycle Management
Reconnecting to a Sandbox
# Get sandbox ID for later
sandbox = Sandbox.create(variant="datalayer")
sandbox_id = sandbox.sandbox_id
sandbox.start()
# Later: reconnect
sandbox = Sandbox.from_id(sandbox_id)
result = sandbox.run_code("print('Still running!')")
Listing Sandboxes
# List all sandboxes
sandboxes = Sandbox.list(variant="datalayer")
for info in sandboxes:
print(f"{info.sandbox_id}: {info.status}")
Termination
sandbox = Sandbox.create()
sandbox.start()
# Graceful shutdown
sandbox.terminate()
# Force kill
sandbox.kill()
# Or use context manager for automatic cleanup
with Sandbox.create() as sandbox:
sandbox.run_code("print('auto cleanup')")
Snapshots
Save and restore sandbox state (datalayer only):
with Sandbox.create(variant="datalayer") as sandbox:
# Set up environment
sandbox.run_code("import pandas as pd")
sandbox.run_code("df = pd.DataFrame({'a': [1,2,3]})")
# Create snapshot
snapshot = sandbox.create_snapshot("my-setup")
print(f"Snapshot: {snapshot.id}")
# Later: restore from snapshot
with Sandbox.create(
variant="datalayer",
snapshot_name="my-setup"
) as sandbox:
result = sandbox.run_code("print(df)") # State restored
Timeout Management
# Set timeout at creation
sandbox = Sandbox.create(timeout=60)
# Update timeout
sandbox.set_timeout(120)
Tags and Metadata
# Create with tags
sandbox = Sandbox.create(tags={"project": "demo", "env": "dev"})
# Update tags
sandbox.set_tags({"project": "demo", "env": "prod"})