Run Claude Code in a sandbox
Claude Code is a coding agent that edits files, runs shell commands, and executes whatever it decides your task needs. That is exactly the kind of program you want inside a hardware-isolated VM rather than on your own machine or CI runner: give it a disposable sandbox, let it work with full autonomy, and read back the result. The sandbox is the blast radius.
api.anthropic.com are already on it.1. Create a sandbox
Use the node-22 base and restricted networking. Pass the Anthropic key as a sandbox env var - it lives in the VM's memory only and is never persisted.
from orkestr import Sandbox
# node-22 ships Node and npm. restricted egress already covers
# registry.npmjs.org, github.com and api.anthropic.com.
sbx = Sandbox.create(
template="node-22",
network="restricted",
timeout_seconds=3600,
env={"ANTHROPIC_API_KEY": "sk-ant-..."},
)2. Install Claude Code
A plain npm install lands in the writable workspace and takes a few seconds. Skip this step entirely by baking the install into a custom template (step 6).
# A local install lands in the writable workspace - about 4 seconds.
sbx.exec("npm install @anthropic-ai/claude-code", timeout_seconds=120)
print(sbx.exec("npx claude --version").stdout) # 2.x.x (Claude Code)3. Get a repo in
# github.com is on the default restricted allowlist.
sbx.exec("git clone --depth 1 https://github.com/you/your-repo.git repo")
# Private repo? Put a fine-grained token in the URL - it stays inside
# the sandbox and dies with it:
# sbx.exec(f"git clone https://x-access-token:{token}@github.com/you/private.git repo")4. Run it headless
-p runs Claude Code non-interactively: one prompt in, the agent works (edits files, runs commands, iterates), one JSON result out. Inside a disposable sandbox, --dangerously-skip-permissions is the point rather than a risk - there is nothing of yours here to protect, so the agent never blocks on approval prompts.
import json
result = sbx.exec(
'npx claude -p "Run the test suite and fix the first failing test" '
"--output-format json --dangerously-skip-permissions",
cwd="/workspace/repo",
timeout_seconds=1800,
)
run = json.loads(result.stdout)
print(run["result"]) # Claude Code's final answer
print(run["total_cost_usd"]) # what the run cost
session_id = run["session_id"] # keep it - resumes the conversation later5. Multi-turn sessions
Two layers of state compose here. Claude Code's own conversation resumes with --resume <session_id>, and the sandbox around it can be paused between turns so you stop paying for compute while the user thinks - resume brings back the repo, the installed packages, and the agent's session files exactly as they were.
# Same Claude Code session, next instruction:
sbx.exec(
f'npx claude -p "Now update the changelog" --resume {session_id} '
"--output-format json --dangerously-skip-permissions",
cwd="/workspace/repo",
timeout_seconds=1800,
)
# Park the whole environment between turns - repo, node_modules,
# Claude Code session state, everything:
sandbox_id = sbx.pause()
# ... later, possibly from another process:
sbx = Sandbox.resume(sandbox_id)6. Make boots instant with a template
If you launch Claude Code sandboxes often, bake the install into a custom template once. Template recipes run as root in a build VM with a writable root filesystem, so a global install works there and every sandbox booted from the template has claude ready in ~300 ms.
curl -X POST https://api.orkestr.eu/v1/templates \
-H "Authorization: Bearer $ORKESTR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "claude-code",
"base_template": "node-22",
"recipe": ["npm install -g @anthropic-ai/claude-code"],
"network": "restricted"
}'
# Once ready, boot from it - claude is preinstalled, ~300ms to a working agent:
# Sandbox.create(template="tmpl_...", network="restricted", env={...})Production notes
- Read files back, not just stdout. The JSON result carries the agent's answer; the actual work is in the filesystem. Use
sbx.files.readorgit diffto collect it, or push a branch from inside the sandbox. - Budget with timeouts. Give the sandbox a lifetime that matches the task and each exec its own ceiling - agent runs hang more often than scripts do.
- Scope both credentials. A sandbox-scoped orkestr token can't touch the rest of your account, and the Anthropic key you inject should be one you can revoke without pain.
- Fan out for parallel work. Each sandbox is fully isolated - run one Claude Code per candidate fix and keep the branch that passes the tests. See the cookbook.