Installation¶
Requirements¶
- Python 3.10+
- uv (recommended) or pip
Install with uv (recommended)¶
Install with pip¶
Optional Dependencies¶
Every workspace and the console tools need Pydantic AI 2.52 or newer.
| Extra | Gives you | Installs |
|---|---|---|
console |
ConsoleCapability, create_console_toolset |
pydantic-ai-slim |
workspaces |
StateWorkspace, SandboxdWorkspace (no further extra needed) |
pydantic-ai-slim |
docker |
DockerWorkspace, DockerSandbox |
docker |
kubernetes |
KubernetesWorkspace |
kubernetes |
daytona |
DaytonaWorkspace |
daytona |
remote |
WorkspaceArchive, reading a sandboxd workspace without a sandbox |
httpx |
server |
sandboxd, the service — install it in the service image, not your app |
FastAPI, uvicorn, docker |
images |
Downscaling large images read_file returns |
Pillow |
uv add "pydantic-ai-backend[console,docker]"
# or
pip install "pydantic-ai-backend[console,docker]"
Environment Setup¶
API Key (for console toolset)¶
If using the console toolset with pydantic-ai, set your model provider's API key:
Docker (for DockerWorkspace)¶
- Install Docker: Get Docker
- Ensure Docker daemon is running
- Pull a base image:
Daytona (for DaytonaWorkspace)¶
Set your Daytona API key, or pass a DaytonaConfig to DaytonaWorkspace(config=...):
Verify Installation¶
from pydantic_ai import Agent
from pydantic_ai_backends import ConsoleCapability, DockerWorkspace
workspace = DockerWorkspace(image="python:3.12-slim")
agent = Agent("anthropic:claude-opus-5-5", capabilities=[workspace, ConsoleCapability()])
result = agent.run_sync("Write hello.py that prints a greeting, then run it")
print(result.output)
await workspace.destroy(result.workspace.ref) # the container outlives the run
Troubleshooting¶
Import Errors¶
Ensure you have the correct Python version:
Docker Permission Denied¶
On Linux, add your user to the docker group:
Then log out and back in.
pydantic-ai Not Found¶
If using console toolset, install with the [console] extra:
Choosing a low-level container runtime¶
Docker does not run containers itself. It hands each one to an OCI runtime —
by default runc — which is the process that actually creates the namespaces and
executes your code. That runtime is swappable, per container, and it is the only
knob that changes how strong a sandbox's isolation is rather than how much CPU or
memory it gets.
Register the ones you want with the daemon in /etc/docker/daemon.json, then name
one per sandbox or per runtime alias:
{
"runtimes": {
"crun": { "path": "/usr/bin/crun" },
"runsc": { "path": "/usr/local/bin/runsc" }
}
}
from pydantic_ai_backends import DockerWorkspace
# gVisor: syscalls handled in userspace, not by the host kernel.
workspace = DockerWorkspace(image="python:3.12-slim", oci_runtime="runsc")
Or service-wide and per runtime in sandboxd, where a runtime's own choice wins
over the service default:
from pydantic_ai_backends.remote.server import SandboxRuntime, SandboxdConfig
config = SandboxdConfig(
token="...",
runtimes={
"shell": SandboxRuntime(image="alpine:3"),
# The runtime allowed to install packages off the network is the one
# worth paying gVisor's I/O overhead for.
"scraping": SandboxRuntime(
runtime="python-scraping",
network_mode="bridge",
oci_runtime="runsc",
),
},
default_runtime="shell",
)
A runtime the daemon does not know about makes it refuse to start the
container, which surfaces as a 502 from POST /sessions. That is why the
default here is None — the daemon's own choice — rather than something we pick
on your behalf. GET /policy and the dashboard report which one is in force.
What each one buys you¶
| Runtime | Isolation | Cost | Use when |
|---|---|---|---|
runc (default) |
Namespaces + cgroups, host kernel shared | none | Code you trust |
crun |
Same as runc |
none — it is faster | Always, if available |
runsc (gVisor) |
Syscalls intercepted in userspace | ~10–30% on I/O-heavy work, near zero on compute | Untrusted, model-written code |
kata |
Own kernel per container, in a microVM | ~200ms start instead of milliseconds | Hard multi-tenant boundaries |
crun: a faster runc, for free¶
crun is a drop-in reimplementation of runc in C rather than Go. It is not a
different isolation model — a crun container is exactly as isolated as a runc
one — it is the same thing with less overhead per container operation, because
there is no Go runtime to start and no garbage collector.
Published comparisons put the container lifecycle around 20% faster. Make it the daemon's default and every sandbox gets it with no code change at all:
Install it with apt install crun or dnf install crun, then
systemctl restart docker.
Two caveats worth knowing before you size a host around it. The widely quoted
"~3 GB of RAM per node" figure comes from Kubernetes measurements comparing
CRI-O + crun against containerd + runc, so it includes the CRI layer and does
not transfer to a Docker deployment unchanged. And under Docker the persistent
per-container process is containerd-shim-runc-v2, which crun does not replace
— so expect the win to show up mainly in start and stop latency, and measure your
own host before promising anyone a density number.
Next Steps¶
- Core Concepts - Learn the fundamentals
- Local Workspace Example - Start with a directory on your machine
- API Reference - Complete API documentation