Sandboxes
A sandbox is an isolated machine for an agent or any other program. It runs your image, or isolance's Default one, with the command, variables and size you give it. It keeps the directories you choose between stops and restarts, stops by itself when idle and starts again when work arrives. It serves the ports you list at its provider's own URLs, reaches only the hosts you allow, gets your connectors' keys added to its requests without ever holding them, and takes snapshots you can restore.
This guide covers a sandbox's whole life in the console, the CLI and the API. The CLI examples assume you've installed and configured caas (see The CLI); the API examples send an API key as $CAAS_API_KEY (see The API).
Create a sandbox
In the console
Open Sandboxes and choose New sandbox. (The button shows for a workspace's owners and operators.)
Under 1. Choose a provider, keep Auto to let isolance pick where the sandbox runs, or pick a provider.
Under 2. Choose an image, pick Default or Your image (see Images). Optionally enter a Command for the sandbox to run, and Environment variables, one
NAME=valueper line.Under 3. Configure, optionally enter a Name and pick a Size from those offered (see Sizes). Keep Keep state between restarts checked to keep the State directory (
/workspaceunless you change it) across stops and restarts, or uncheck it to start fresh every time. Labels, onekey=valueper line, show only while a connector's Reaches names a label of your own: they decide which of those connectors the sandbox gets.Under 4. Network, list the Allowed outbound hosts, one per line, or leave the field empty to allow every host (see Outbound access). The hosts of the connectors the sandbox gets show as Included with its connectors, and a line such as Gets GitHub, Linear names those connectors. Optionally enter a User: one of your own users, whose own accounts the sandbox then uses (see Connectors). Where the page says This sandbox gets no connectors., keys can't be added to its requests where it would run.
Choose Create sandbox.
The sandbox's page opens. Its status moves from Creating to Starting to Running.
With the CLI
caas sandboxes create --name build-agent --size l --egress api.github.com --label team=ciEvery field of the sandbox has its own option; see Fields. Add --from . to put your Git repository's committed files in its working directory once it runs. caas run is the shortcut that creates a sandbox, waits until it runs, and runs one command in it:
caas run --from . --name build-agent -- npm testWith the API
curl https://api.caas.sh/v1/sandboxes \
-H "Authorization: Bearer $CAAS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "image": "default" }'{"image": "default"} is a whole request: every other field takes its default. The answer, 201 Created, is the sandbox with "state": "creating"; its first start runs on its own. A sandbox started for one of your users who hasn't signed in to a connector they sign in to with a code, such as Codex (ChatGPT), also answers their code in signIn (see Sign users in with a code). Send the same Idempotency-Key again to retry safely: you get the same sandbox, never a second one.
Fields
A sandbox is one flat object: the fields you set and the fields isolance reports side by side.
| Field | Default | Rules | CLI option | A change takes effect |
|---|---|---|---|---|
name | sbx- and 12 hex digits of the ID | See Names | --name | Can't change |
image | "default" | "default", or your image pinned to its digest; see Images | --image | At the next start |
command | null: the image's own entrypoint and command, if it has any | 1 to 32 arguments of up to 512 characters | --command "CMD" | At the next start |
env | {} | Up to 64 variables; names of letters, digits and _, not starting with a digit or CAAS_; values up to 4,096 characters | --env NAME=VALUE | For new commands at once; for its own command at the next start |
workdir | "/workspace" | An absolute path, not under /proc, /sys or /dev | --workdir | Can't change |
size | "m" | See Sizes | --size | At the next start |
volumes | [{ "path": "/workspace" }] | Up to 8 directories, none inside another, not / or under /proc, /sys, /dev or /etc; [] keeps nothing | --volume PATH, --no-volumes | At the next start |
ports | [] | Up to 16 { "port", "public" }, each port from 1024 to 65535; see URLs | --port N[:public] | At once |
egress | ["*"]: every host | ["*"], or up to 32 hosts; [] reaches none | --egress HOST, --no-egress | At once, but a change between * and a list at the next start |
user | null | One of your own users, by your ID for them: 1 to 256 characters, without control characters; the sandbox then uses that user's own accounts (see Connectors) | --user | Can't change |
idleStopMinutes | 15 | 5 to 240 | --idle-stop | At once |
labels | {} | Up to 16 key: value; keys of lowercase letters, digits, ., _, / and -; values up to 256 characters; no key starts with caas.sh/. They also pick its connectors | --label KEY=VALUE | At once; the connectors they pick at the next start |
provider | "auto" | Where the sandbox runs: auto (isolance picks one at each start), or one that can run it: cloudflare (Native, isolance's own account), or maritime, daytona, freestyle or e2b, which run only on your own account, with your own key | --provider | At the next start |
A sandbox runs its command at every start, in its working directory and in place of the image's entrypoint and command; its output is the sandbox's log. With command null, the image's own entrypoint and command run the same way, and an image without any, such as the Default image, runs nothing until you send it commands. Each command you run in the sandbox, such as the one caas run takes after --, runs once beside it and is never its command.
And what isolance reports:
| Field | What it is |
|---|---|
id | The sandbox's ID, a UUID |
state | creating, starting, running, stopping, stopped, failed or deleting |
stateMessage | Why it's stopped or failed, such as stopped by its owner or the reason a start failed; null while it runs |
providerId | The provider's own ID for the sandbox, null until it exists |
connectors | [{ "id", "name", "hosts", "account" }]: the connectors it has, and whose account each uses (user, workspace, or null for a tool with no sign-in). You can't set it: each connector's Reaches picks its sandboxes |
urls | [{ "port", "url" }]: the provider's own URL of each port it gives one for, while the sandbox runs; see URLs |
pendingRestart | The fields a running sandbox takes only at its next start, and connectors when the connectors it would get have changed |
createdAt, startedAt, stoppedAt | Milliseconds since the Unix epoch |
usage | Since when it runs, its cost per hour now, and today's cost and egress, in micro-USD and bytes |
A label whose key starts with caas.sh/ is isolance's own: a sandbox with a user carries caas.sh/user with the user's ID, and a sandbox that hosts an MCP server carries caas.sh/mcp-server with the server's ID. You can filter by them, and a connector's Reaches can name them, but you can't set them.
Images
The Default image
The Default image is Alpine Linux with bash, coreutils, Node and Docker. It runs on any size and has no command of its own: without a command, the sandbox runs and waits for the commands you send it. It doesn't include your project's dependencies or a coding agent: install what you need with a command, or build your own image. To install Alpine packages with apk, allow dl-cdn.alpinelinux.org.
Your own image
You can run any linux/amd64 image you've pushed to your workspace's namespace of the registry, registry.caas.sh/<tenant-id>/NAME; Images and the registry shows how. A sandbox on your own image needs size M or larger, or a custom size with at least 4 GiB of memory and 8 GB of disk. Without a command, the image's own entrypoint and command run.
In the console, pick Your image and enter the image by digest,
registry.caas.sh/<tenant-id>/NAME@sha256:…(docker pushprints the digest).The CLI also takes a tag, and pins it to its digest for you:
caas sandboxes create --image agent:1.2.0.The API takes the digest reference:
"image": "registry.caas.sh/<tenant-id>/agent@sha256:…". isolance checks that it's in your registry when you create the sandbox.
Sizes
A size sets the vCPU, memory and disk the sandbox gets:
| Size | vCPU | Memory | Disk |
|---|---|---|---|
| XS | 1/16 | 256 MiB | 2 GB |
| S | 1/4 | 1 GiB | 4 GB |
| M (default) | 1/2 | 4 GiB | 8 GB |
| L | 1 | 6 GiB | 12 GB |
| XL | 2 | 8 GiB | 16 GB |
| 2XL | 4 | 12 GiB | 20 GB |
A custom size takes whole numbers: 1 to 4 vCPU, at least 3 GiB of memory per vCPU and at most 12 GiB, and 2 to 20 GB of disk. In the console, pick Custom and fill in vCPU, Memory (GiB) and Disk (GB). With the CLI, --size custom --vcpu 2 --memory 8 --disk 16; in the API, "size": { "cpu": 2, "memoryGiB": 8, "diskGB": 16 }.
While it runs on Native, a sandbox bills for its vCPU, memory and disk, whether it's busy or waiting. On your own Daytona, Freestyle, Maritime or E2B account, that provider bills its compute and egress instead. A stopped one bills only for its snapshots. See pricing for the rates.
Names
A sandbox's name is unique in your workspace. It's a lowercase letter, then up to 49 lowercase letters, digits and hyphens, not ending with a hyphen. Without a name, isolance gives the sandbox sbx- and 12 hex digits of its ID. A name another of your sandboxes has is refused with 409 name_taken; other workspaces can use the same names.
Every CLI command and API path takes a sandbox's ID or its name.
Change a sandbox
In the console, the sandbox's Network card has an Edit link for its ports and outbound hosts (its connectors are listed there, read-only), and the Public checkbox in Details sets its ports' public; see URLs. With the CLI, caas sandboxes update replaces each field you give, and a --no-… flag empties one:
caas sandboxes update build-agent --size xl --egress api.github.com --egress pypi.org --idle-stop 60
caas sandboxes update build-agent --no-ports --label team=webWith the API, PATCH /v1/sandboxes/<sandbox> takes the fields to replace: env, ports, egress, idleStopMinutes, labels, image, command, size and volumes. name, workdir, user and provider can't change.
A running sandbox takes most changes at once (see Fields). The others wait for its next start, and the answer's pendingRestart lists them; the console shows a notice and a Restart button. Restart it to apply them now:
caas sandboxes restart build-agentStart, stop and restart
Stop a sandbox with Stop on its page or in its row's menu in Sandboxes, caas sandboxes stop <sandbox>, or POST /v1/sandboxes/<sandbox>/stop. Start it with Start, caas sandboxes start <sandbox>, or POST …/start. restart stops it and starts it again, and takes every change that was waiting. Each answers within about 25 seconds: 200 with the sandbox once it's done, or 202 while it's still changing.
A sandbox keeps its volumes as it stops and gets them back when it starts. A stopped sandbox bills for no vCPU, memory or disk, only for storing its snapshots.
Idle stop
A running sandbox stops by itself after idleStopMinutes (15 by default) without activity. Commands, a command still running, file transfers, and requests to its MCP endpoint count as activity. Requests to its URLs go to its provider, not through isolance, so they don't.
Wake on request
A sandbox that stopped while idle starts again by itself when a command, a file transfer or a request to its MCP endpoint arrives. A sandbox you stopped yourself stays stopped until you start it; a command or file transfer sent to it meanwhile gets 409 not_running.
A sandbox also starts only while the workspace has a card on file and a balance above $0. At $0, its sandboxes stop until funds arrive; see Billing and usage.
Keep state, or start fresh
A sandbox keeps its volumes between stops and restarts; by default that's /workspace. A sandbox with volumes: [] (the console's Keep state between restarts unchecked, or --no-volumes) starts with fresh directories every time. You can still take snapshots of it yourself and restore one.
Run commands
A command runs one executable with its arguments, without a shell, in the sandbox's working directory, with its variables and the ones you add. To use shell features, run a shell: sh -c 'npm ci && npm test'. A command sent to a sandbox that stopped while idle starts it first.
In the console
On the sandbox's page, Run a command takes a Command and a Time limit (1 minute to 2 hours; 10 minutes by default). Quote arguments that contain spaces. The result shows the command's state, exit code and output, with Refresh and, while it runs, Cancel.
With the CLI
caas sandboxes exec build-agent -- node --version
caas sandboxes exec build-agent --env NODE_ENV=test --workdir /workspace/app --timeout 1800 -- npm test
echo '{"q": 1}' | caas sandboxes exec build-agent --stdin -- node read-input.jsexec waits for the command, prints its output, and exits with the command's exit code when it fails. --timeout takes 1 to 7,200 seconds (600 by default). With --detach, it returns at once with the exec's ID (and a detached command may run up to 86,400 seconds):
caas sandboxes exec build-agent <exec-id>
caas sandboxes exec build-agent <exec-id> --cancelWith the API
curl https://api.caas.sh/v1/sandboxes/build-agent/exec \
-H "Authorization: Bearer $CAAS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: run-tests-0001" \
-d '{ "command": ["npm", "test"], "env": { "CI": "1" }, "timeoutSeconds": 1800 }'The body takes command, and optionally stdin (text up to 64 KiB), env, workdir, timeoutSeconds (1 to 7,200; 600 by default) and detach. The Idempotency-Key becomes the exec's ID, and resending it never runs the command twice. The answer is 200 once the command ended, within about five minutes or its time limit, or 202 with its ID while it runs (at once with "detach": true):
{ "execId": "run-tests-0001", "state": "succeeded", "detach": false, "createdAt": 1791797400000, "endedAt": 1791797412000,
"exitCode": 0, "signal": null, "stdout": "…", "stderr": "", "stdoutTruncated": false }state is submitting, running, succeeded, failed, timed_out, canceled or lost. stdout and stderr are null until it ends, then up to 1 MiB each (stdoutTruncated or stderrTruncated when cut). Read it again with GET …/exec/<exec-id>, and cancel it with DELETE …/exec/<exec-id>.
Read the log
A sandbox's log holds what isolance recorded about it (its starts, stops and failures, as system lines) and each line its command wrote (stdout and stderr). The console shows it as Runtime output on the sandbox's page.
caas sandboxes logs build-agent
caas sandboxes logs build-agent --since 2026-10-11T09:00:00Z --followIn the API, GET /v1/sandboxes/<sandbox>/logs answers the newest 1,000 entries at or after ?since= (milliseconds or an ISO 8601 time), oldest first: { "entries": [{ "at", "stream", "message", "kind" }] }. With ?follow=true, the answer is newline-delimited JSON: those entries, then each new one as it comes, for up to 10 minutes; follow on with since. A command's output is also in its exec (see Run commands).
Move files
The files API reads, writes and deletes paths in a sandbox, up to 100 MiB a file. A path is absolute, not under /proc, /sys, /dev or /workspace/.caas (which belongs to isolance). A transfer starts a sandbox that stopped while idle, and counts as activity.
In the console
On the sandbox's page, the Files tab lists /workspace, folders first, with each entry's type and size. Open a folder to go into it, and go back up with the path above the list, as far as /. Open a text file to read it (up to 256 KB); Download saves any file. Upload puts files into the folder shown, New folder makes one, and each row's ⋯ menu has Download and Delete (a folder with everything in it). On a stopped sandbox, Browse files starts it. A transfer can take a minute or more per MB; the tab shows what it is doing and for how long, and waits until it's done. ?tab=files in the page's address opens the tab.
With the CLI and the API
caas sandboxes files put build-agent /workspace/config.json config.json
caas sandboxes files put build-agent /workspace/site site.tar.gz --extract
caas sandboxes files put build-agent /workspace --from .
caas sandboxes files get build-agent /workspace/config.json > config.json
caas sandboxes files get build-agent /workspace
caas sandboxes files delete build-agent /workspace/tmp --recursive| Request | Does |
|---|---|
GET /v1/sandboxes/<sandbox>/files/<path> | A file's bytes (?tail=N for its last N bytes), or a directory's listing: { "path", "entries": [{ "name", "type", "size" }] } |
PUT …/files/<path> | Writes the body as the file, creating its directories. With ?extract=true, the body is a gzipped tar unpacked into the directory <path>. |
DELETE …/files/<path> | Deletes the file; a directory needs ?recursive=true |
Percent-encode each segment of the path, as in …/files/workspace/my%20file.txt. The bytes you read bill as egress. Only the sandbox's volumes are kept between restarts.
Outbound access
A sandbox's egress lists the hosts it may reach:
*allows every host. It's the default for a sandbox created without a list.A list of up to 32 hosts allows those hosts and blocks everything else. Write each as an exact host, such as
pypi.org, or as*.example.comfor the names under example.com (listexample.comas well if the sandbox calls it). A host is never an IP address, a port, a path or*inside a list.An empty list reaches no host but its connectors' hosts.
The hosts of the connectors a sandbox gets are always allowed beside its list, and isolance adds their keys to its HTTPS requests there.
Where you set it:
Console: in New sandbox, list the hosts under Allowed outbound hosts, one per line, or leave it empty for every host. On an existing sandbox, choose Edit on its Network card and change the list there. Deploying an MCP server has the same field, where an empty list reaches no host.
CLI: pass
--egress HOSTonce per host tocaas sandboxes create,caas sandboxes update,caas runorcaas mcp create;--egress '*'allows every host, and--no-egressnone:shellcaas run --egress api.github.com -- node -e "fetch('https://api.github.com/zen').then(r => r.text()).then(console.log)"For a command that starts with
npx, the CLI addsregistry.npmjs.orgto a list that doesn't already cover it.API:
"egress": ["api.github.com", "*.npmjs.org"]onPOSTorPATCH /v1/sandboxes/<sandbox>.Files: list hosts under
egress:in an MCP server file.
A change between two lists applies to a running sandbox at once. A change between * and a list applies at its next start, and the sandbox's pendingRestart says so until then.
What a list allows
A sandbox with a list reaches its hosts over HTTP and HTTPS. With
*, it reaches any host on any port.registry.caas.shis always reachable, so a sandbox can pull images from your registry.A list never reaches an IP address,
localhostor an internal name, and reaches only hosts under these top-level domains: .ai, .app, .au, .biz, .br, .ca, .cc, .ch, .cloud, .cn, .co, .com, .de, .dev, .edu, .es, .eu, .fr, .gg, .gov, .in, .info, .io, .it, .jp, .kr, .ly, .me, .net, .nl, .org, .page, .pl, .run, .se, .sh, .so, .tech, .tv, .uk, .us and .xyz.
Owners and operators can change a sandbox's outbound hosts; everyone in the workspace can read them. The bytes a sandbox on Native sends to the hosts it's allowed to reach bill as egress; see pricing.
URLs
A sandbox's ports are served by its provider. While the sandbox runs, its urls list the provider's own URL for each port it lists, where the provider gives one. isolance doesn't sit in between: who can open a URL is up to the provider, which may answer only requests that carry its own credentials for the sandbox, and isolance never shows those. E2B and Daytona give each port a URL; on Native, Freestyle and Maritime a sandbox's urls are empty.
The sandbox's processes get the variable PORT: its first listed port, or 8080 when it lists none.
Add, remove and change ports in the console's network page (Edit on the Network card), with --port in the CLI (caas sandboxes update build-agent --port 3000 --port 9229), or with "ports": [{ "port": 3000 }, { "port": 9229 }] on create or PATCH. A port's public (--port 3000:public, or Public in the console's Details) is kept with it, but isolance serves no URL of its own, so it opens nothing.
What to know
A request to a URL goes to the provider: it doesn't count as activity, doesn't start a stopped sandbox, and isolance doesn't bill it.
urlsare empty while the sandbox doesn't run.
Snapshots
A snapshot is a saved copy of a sandbox's volumes, taken when you take one, with a name or called manual, and kept until you delete it.
The snapshot a sandbox that keeps state would start from if it lost its volumes is its recovery point, marked current. Snapshot storage bills per GB-month (see pricing); taking and restoring snapshots is included.
Take and list snapshots
Snapshots are taken of a running sandbox. Choose Take snapshot on the sandbox's page and enter a name or leave it empty, or:
caas sandboxes snapshots create build-agent --name before-upgrade
caas sandboxes snapshots list build-agentIn the API, POST /v1/sandboxes/<sandbox>/snapshots with an optional { "name": "before-upgrade" }, and GET …/snapshots, which answers { "snapshots", "storedBytes" }. A name follows the rules for sandbox names.
Restore a snapshot
Stop the sandbox. Take a snapshot first if you want to come back to what you have now.
Choose Restore beside the snapshot on the sandbox's page, or run
caas sandboxes snapshots restore build-agent before-upgrade(a snapshot's ID, or a manual snapshot's name). In the API,POST /v1/sandboxes/<sandbox>/restorewith{ "snapshot": "<id or name>" }.
The sandbox starts with that snapshot as its volumes. Newer snapshots stay. If the snapshot can't start, the sandbox stays stopped with its previous recovery point.
Delete a snapshot
Choose Delete beside the snapshot, run caas sandboxes snapshots delete build-agent <snapshot-id>, or send DELETE …/snapshots/<snapshot-id>. You can't delete the current recovery point of a sandbox that keeps state until a newer snapshot exists.
Delete a sandbox
Choose Delete in the sandbox's row menu in Sandboxes, run caas sandboxes delete <sandbox>, or send DELETE /v1/sandboxes/<sandbox>. Deleting a sandbox removes its volumes, snapshots and logs for good, and frees its name. Deleting a sandbox that hosts an MCP server deletes the server too. Deleting one a workflow keeps between runs fails a step running in it; the workflow's next step on it creates it again.
List sandboxes
The console's Sandboxes page lists every sandbox of the workspace, those hosting MCP servers too, with filters by state and a search by name, ID, image or label.
caas sandboxes list --label team=web --provider cloudflareGET /v1/sandboxes answers { "sandboxes", "nextCursor" }, newest first, 50 at a time (?limit= up to 100); pass nextCursor back as ?cursor= for the next page. Filter with ?label=key=value (repeat it; all must match; caas.sh/user=<user> finds a user's sandboxes) and ?provider=.
A web browser
With the Browser connector, the AI tools in a sandbox it reaches get a web browser, as an MCP server named browser in their settings at every start. A sandbox started for a user signs in to websites with that user's logins, and one without a user with your workspace's; the AI never sees their passwords, codes or passkeys.
Its browsers run outside the sandbox, so the sites they open needn't be among its outbound hosts, and they take nothing of its size.
A file a browser tool reads or writes, such as an upload or a screenshot, is a file of this sandbox, relative to its working directory.
Its browsers close when it stops or is deleted, and the next tool after a start opens a new one.
Browser time bills apart from the sandbox's own (see Browser time).
Limits
A workspace has no vCPU or memory quota: its balance is what bounds the sandboxes it runs. These are the limits per sandbox and per command.
| Limit | Value |
|---|---|
| Command | 1 to 32 arguments of up to 512 characters each |
| Command time limit | 1 second to 2 hours (24 hours detached) |
| Command output | 1 MiB per stream |
| Standard input | 64 KiB |
| Variables | 64, values up to 4,096 characters |
| Volumes | 8 |
| Ports | 16 |
| Outbound hosts | 32 |
| Connectors | 16 egress rules and 32 connector hosts per sandbox, logins' deliveries included; see Connectors |
| Labels | 16 |
| File transfer | 100 MiB per file |
| Idle stop | 5 to 240 minutes |