Sandboxes
Every run of a runbook gets its own sandbox: an isolated Linux machine that Jetty creates for that run, runs the agent in, and deletes when the run ends. This page covers what is in a sandbox, what Jetty does to it from start to finish, its limits, and how to run on a Docker image of your own.
The short version:
- One sandbox per run. Nothing carries over between runs, and concurrent runs never share a machine.
- The default is the
python312-uvsnapshot: Python 3.12, Node 22, Chromium and the common agent CLIs, ready in seconds. - The agent works in
/app, reads uploaded files from/app/assetsand must write its deliverables to/app/results. - Default size and time: 4 CPUs, 8 GB of memory, 10 GB of disk, and up to 30 minutes for the agent.
- You can bring your own environment: another named snapshot, a public Docker image, or a base image plus packages.
What a sandbox is
A sandbox is an isolated machine with its own filesystem, processes and network access, created from an image. The agent has a real shell in it: it can install packages, run a browser, start servers and write files, and none of that touches another run or Jetty itself. In most sandboxes the agent runs as root.
The sandbox is where the agent runs. The parts of a run that surround the agent run elsewhere: Jetty's worker resolves the run's parameters and secrets, creates the sandbox, streams the agent's log, collects the outputs and records the run.
Choosing the environment
A sandbox is created from one of three sources. Jetty takes the first that applies:
| Source | Set with | Start-up | Use it when |
|---|---|---|---|
| 1. Snapshot | snapshot, in the runbook's frontmatter or a run's init_params | Seconds: the image is already built | Almost always. This is the default |
| 2. Docker image | image, with snapshot left empty | The image is pulled and prepared before the sandbox starts | You need a toolchain, a runtime or system libraries the snapshots don't have |
| 3. Generated Dockerfile | base_image, apt_packages, pip_packages, with no snapshot and no image | Minutes: the image is built for the run | You need a few extra packages and have no image of your own |
A named snapshot wins whenever it exists. If the name doesn't match an available snapshot, Jetty falls through to the image, then to the Dockerfile. apt_packages and pip_packages apply only on the Dockerfile path: a snapshot or an image already contains its packages.
The default snapshot
A runbook that names no snapshot runs on python312-uv. It contains:
- Python 3.12 on Debian (bookworm), with
pip,uvand a C and Fortran build toolchain, so most Python packages install without extra system work - Node 22 and
npm gitandcurl- Chromium, the Playwright Python library and the Playwright MCP server, for browser automation
- The
claude-code,codex,opencodeandhermesagent CLIs, pre-installed
prism-playwright is the other named snapshot, built around Playwright and Chromium for browser-heavy work. A runbook picks its snapshot in frontmatter, and a run can override it:
---
agent: claude-code
snapshot: prism-playwright # python312-uv (default) | prism-playwright
---An agent whose CLI isn't in the image is installed when the run starts, which adds a few seconds. The agent can also install what it needs itself, with pip, uv, npm or apt-get: a runbook step such as “install pdfplumber” works, and costs that install time on every run.
The life of a sandbox
- Prepare. Jetty resolves the run's parameters, fills the runbook's
{{variables}}, and downloads the run's attached files. - Create. A new sandbox is created from the snapshot, image or generated Dockerfile. No secret or user variable is part of the image.
- Set up. Jetty creates the working directories, places the attached files in
/app/assets, installs or pins the agent CLI, and writes the MCP server configuration if the run has one. - Run the agent. The agent starts in
/appwith the runbook as its instruction and its environment applied. Its log is streamed to the run while it works. - Clean up credentials. When the agent exits, the credential files Jetty wrote are deleted and, if Jetty has checks to run, the processes the agent left behind are stopped.
- Collect. Jetty downloads the agent's log,
/app/resultsand/app/assets. - Check. Jetty runs the runbook's Jetty checks in the same sandbox, in a clean shell.
- Save and delete. The outputs are saved to the collection's storage and the sandbox is deleted. A run that fails or times out deletes its sandbox too.
As a backstop, a periodic sweep deletes any sandbox that is still running but has gone idle, so a sandbox whose run was interrupted does not linger.
Where things are
| Path | What it holds | Kept after the run |
|---|---|---|
/app | The agent's working directory | No, apart from the two directories below |
/app/results | The run's deliverables, written by the agent | Yes. The frontmatter's primary_outputs are surfaced first |
/app/assets | Files attached to the run; zips are extracted | Yes |
/app/checks/<name> | The git repositories a runbook's code checks declare as sources | No |
/logs/agent | The agent CLI's log, session and transcript | Yes, as the run's agent log |
A runbook can write these paths as {{results_dir}}, {{assets_dir}} and {{checks_dir}}. Jetty fills them in; a run cannot move them.
Important
Results are a contract
A run whose agent finishes with nothing in /app/results fails. Anything the agent leaves elsewhere in the sandbox is gone when the sandbox is deleted.
Getting files in. Attach files to a run directly, or pre-stage them through the upload API and pass the returned storage paths. The exact calls are in the machine instructions.
Resources and limits
| Limit | Default | Change it with |
|---|---|---|
| CPUs | 4 | cpus on the task's step, for an image or Dockerfile sandbox. A snapshot has its own fixed size |
| Memory | 8 GB | memory on the task's step (for example "16G"), under the same rule |
| Disk | 10 GB | Fixed |
| Agent time | Up to 30 minutes | timeout_sec on the task's step, in seconds, to set a shorter budget. For longer jobs, contact [email protected] |
| Agent install | 10 minutes | Fixed. Pre-install the CLI in the image to avoid it |
| Sandbox creation | 20 minutes, retried once | Fixed. Covers pulling or building an image |
When the agent reaches its time limit it is stopped. A long runbook should write its outputs to /app/results as it goes. If a job needs more than 30 minutes, contact [email protected].
Network access
Sandboxes have outbound network access. The agent can fetch URLs, call APIs, clone repositories and install packages, and it reaches its model provider the same way. Jetty adds no allow-list, so what a run can reach is decided by the credentials you give it; see Secrets and Environment Variables. A run can drive Chromium inside the sandbox for sites that need a real browser.
Running on your own Docker image
When the snapshots don't have what the job needs (a JVM, a GPU-free ML stack, a specific compiler, your company's CLI), build an image and point the run at it.
What the image needs
- Pullable without credentials. Jetty takes an image reference and no registry login, so the image must be public (for example on GHCR or Docker Hub). Don't bake secrets into it; they reach the run through the collection.
- A normal Linux userland:
bashand the core utilities. Jetty runs every command throughbash. - A way to install the agent. The agent installers expect a Debian or Ubuntu base (
apt-get) withcurl, and install Node if it is missing. Putting Node and the agent CLI in the image skips that work on every run. If the CLI in the image is not the version Jetty pins, Jetty installs the pinned one. - What your checks use. If the runbook has Jetty-run code checks, the image needs
python3for the built-in checks andgitfor check sources. Jetty verifies this before the agent starts and fails the run early if something is missing.
The image's ENTRYPOINT and CMD don't run your job: Jetty starts the sandbox and executes the agent in it.
Step 1: Build and push the image
FROM python:3.12-slim-bookworm
# What Jetty and the agent installer rely on: bash, coreutils, curl, git.
RUN apt-get update && apt-get install -y --no-install-recommends \
curl git ca-certificates gnupg && \
rm -rf /var/lib/apt/lists/*
# Node, so the agent CLI doesn't have to install it on every run.
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | sh - && \
apt-get install -y nodejs && rm -rf /var/lib/apt/lists/*
# The agent CLI itself (optional; Jetty installs or re-pins it if needed).
RUN npm install -g @anthropic-ai/claude-code
# Your own toolchain.
RUN pip install --no-cache-dir pandas duckdb
WORKDIR /appdocker build -t ghcr.io/acme/report-env:1.4.2 .
docker push ghcr.io/acme/report-env:1.4.2Use a version tag rather than latest, so a run records exactly which environment it used and a new push can't change a runbook's behaviour unnoticed.
Step 2: Point a run at it
An image is a run parameter, not a frontmatter key. Pass image and clear snapshot, because a named snapshot takes priority and every runbook task has python312-uv as its default:
curl -X POST "https://flows-api.jetty.io/api/v1/run/<your-collection>/<your-task>" \
-H "Authorization: Bearer $JETTY_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"init_params": {
"snapshot": "",
"image": "ghcr.io/acme/report-env:1.4.2"
}
}'init_params merge per top-level key, so the task's runbook, agent, model and variables are unchanged. The run log says which source was used: Creating sandbox from registry image: ….
Step 3: Make it the task's default
To use the image on every run, set the same two keys in the task's own init_params. Raise cpus and memory in the step configuration if the job needs more:
{
"init_params": {
"instruction": "<contents of RUNBOOK.md>",
"agent": "claude-code",
"model": "anthropic/claude-sonnet-5",
"model_provider": "openrouter",
"snapshot": "",
"image": "ghcr.io/acme/report-env:1.4.2",
"vars": {},
"file_paths": []
},
"steps": ["run"],
"step_configs": {
"run": {
"activity": "runbook",
"agent_path": "init_params.agent",
"model_path": "init_params.model",
"model_provider_path": "init_params.model_provider",
"snapshot_path": "init_params.snapshot",
"instruction_path": "init_params.instruction",
"template_variables_path": "init_params.vars",
"files_path": "init_params.file_paths",
"cpus": 4,
"memory": "8G"
}
}
}Note
A task's parameters are generated from the runbook's frontmatter when the task is built from the runbook text, and the frontmatter has no image key. If a task is rebuilt from its runbook, set image and the empty snapshot again, or pass them with each run.
Extra packages without an image
If all you need is a few packages on top of a stock base, skip the registry and let Jetty generate the Dockerfile. With no snapshot and no image, it builds from base_image (default python:3.13-slim-bookworm), adds git, curl and uv, then installs your apt_packages and pip_packages:
"init_params": {
"snapshot": "",
"base_image": "python:3.12-slim-bookworm",
"apt_packages": ["ffmpeg", "poppler-utils"],
"pip_packages": ["pandas", "pdfplumber"]
}This builds an image for the run, which takes minutes. For a runbook you run often, a pushed image starts faster and is the same on every run.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
The run log says Creating sandbox from snapshot although you passed image | The task's default snapshot still applies | Send "snapshot": "" with the image |
| Sandbox creation fails or times out | The image is private, the reference is wrong, or the image is very large | Check that docker pull works without logging in; slim the image |
Agent install failed | The image has no Node and no apt-get or curl to install it with | Add Node and the agent CLI to the image |
| The run fails before the agent starts, naming a missing tool | The runbook's Jetty checks need python3 or git | Add them to the image |
apt_packages or pip_packages had no effect | The run used a snapshot or an image | Clear snapshot and image, or install the packages in your image |
| The run fails with no results files | The agent wrote outside /app/results, or ran out of time | Name /app/results in the runbook and write outputs incrementally |