DOCSSandboxes
DOCS

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

  1. Open Sandboxes and choose New sandbox. (The button shows for a workspace's owners and operators.)

  2. Under 1. Choose a provider, keep Auto to let isolance pick where the sandbox runs, or pick a provider.

  3. 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=value per line.

  4. 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 (/workspace unless you change it) across stops and restarts, or uncheck it to start fresh every time. Labels, one key=value per line, show only while a connector's Reaches names a label of your own: they decide which of those connectors the sandbox gets.

  5. 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.

  6. Choose Create sandbox.

The sandbox's page opens. Its status moves from Creating to Starting to Running.

With the CLI

shell
caas sandboxes create --name build-agent --size l --egress api.github.com --label team=ci

Every 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:

shell
caas run --from . --name build-agent -- npm test

With the API

shell
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.

FieldDefaultRulesCLI optionA change takes effect
namesbx- and 12 hex digits of the IDSee Names--nameCan't change
image"default""default", or your image pinned to its digest; see Images--imageAt the next start
commandnull: the image's own entrypoint and command, if it has any1 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=VALUEFor new commands at once; for its own command at the next start
workdir"/workspace"An absolute path, not under /proc, /sys or /dev--workdirCan't change
size"m"See Sizes--sizeAt 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-volumesAt 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-egressAt once, but a change between * and a list at the next start
usernullOne 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)--userCan't change
idleStopMinutes155 to 240--idle-stopAt 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=VALUEAt 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--providerAt 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:

FieldWhat it is
idThe sandbox's ID, a UUID
statecreating, starting, running, stopping, stopped, failed or deleting
stateMessageWhy it's stopped or failed, such as stopped by its owner or the reason a start failed; null while it runs
providerIdThe 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
pendingRestartThe fields a running sandbox takes only at its next start, and connectors when the connectors it would get have changed
createdAt, startedAt, stoppedAtMilliseconds since the Unix epoch
usageSince 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 push prints 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:

SizevCPUMemoryDisk
XS1/16256 MiB2 GB
S1/41 GiB4 GB
M (default)1/24 GiB8 GB
L16 GiB12 GB
XL28 GiB16 GB
2XL412 GiB20 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:

shell
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=web

With 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:

shell
caas sandboxes restart build-agent

Start, 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

shell
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.js

exec 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):

shell
caas sandboxes exec build-agent <exec-id>
caas sandboxes exec build-agent <exec-id> --cancel

With the API

shell
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):

json
{ "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.

shell
caas sandboxes logs build-agent
caas sandboxes logs build-agent --since 2026-10-11T09:00:00Z --follow

In 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

shell
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
RequestDoes
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.com for the names under example.com (list example.com as 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 HOST once per host to caas sandboxes create, caas sandboxes update, caas run or caas mcp create; --egress '*' allows every host, and --no-egress none:

    shell
    caas 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 adds registry.npmjs.org to a list that doesn't already cover it.

  • API: "egress": ["api.github.com", "*.npmjs.org"] on POST or PATCH /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.sh is always reachable, so a sandbox can pull images from your registry.

  • A list never reaches an IP address, localhost or 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.

  • urls are 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:

shell
caas sandboxes snapshots create build-agent --name before-upgrade
caas sandboxes snapshots list build-agent

In 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

  1. Stop the sandbox. Take a snapshot first if you want to come back to what you have now.

  2. 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>/restore with { "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.

shell
caas sandboxes list --label team=web --provider cloudflare

GET /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.

LimitValue
Command1 to 32 arguments of up to 512 characters each
Command time limit1 second to 2 hours (24 hours detached)
Command output1 MiB per stream
Standard input64 KiB
Variables64, values up to 4,096 characters
Volumes8
Ports16
Outbound hosts32
Connectors16 egress rules and 32 connector hosts per sandbox, logins' deliveries included; see Connectors
Labels16
File transfer100 MiB per file
Idle stop5 to 240 minutes