Sandboxes#
A sandbox is a persistent cloud computer for one agent: a full Firecracker microVM with its own kernel, disk, IP address and network namespace. It keeps its state between sessions and costs nothing for compute while paused.
TaskCloudis in private beta. Latency numbers on this page are targets, not guarantees.
What is inside#
| Property | Value |
|---|---|
| OS | Ubuntu 24.04 LTS, x86_64, own Linux kernel |
| Users | user (home /home/user, passwordless sudo); root is allowed |
| Init | real systemd: services you enable come back after pause/resume and fork |
| Containers | Docker inside the VM |
| Disk | persistent, survives pause, resume and host restarts |
| GPU | none (software rendering only); GPU is on the roadmap |
Nested virtualization (/dev/kvm) |
not available; on the roadmap |
| Region | EU only. auto (default) places the sandbox on any EU host; eu-de and eu-fi pin it to a location |
Isolation: each sandbox runs in its own Firecracker jailer, cgroup and network namespace. Sandboxes cannot see each other or the host API. See Security.
Sizes#
size |
vCPU | RAM | Included disk | Price per active hour |
|---|---|---|---|---|
small |
2 | 4 GB | 20 GB | $0.015 |
default |
4 | 8 GB | 50 GB | $0.030 |
large |
8 | 16 GB | 100 GB | $0.060 |
Billing is per second while the VM runs. Paused sandboxes cost $0 for compute. Set disk_gb (20–1000) for more disk; disk above the included amount is billed per GB-month. Details: Billing.
The desktop (desktop template) needs size default or larger.
Create a sandbox#
A sandbox starts from exactly one source:
- a template (
template, defaultbase), see Templates; - a snapshot (
snapshot_id), which restores RAM and disk, see Snapshots and fork; - an OCI image (
image.ref).
With wait: true (the default) the call returns 201 once the sandbox is running (target p95 < 500 ms from a template). With wait: false it returns 202 in state creating.
import { Client } from "@taskcloud/sdk";
const client = new Client(); // reads TASKCLOUD_API_KEY
const sandbox = await client.sandboxes.create({
template: "base",
size: "default",
name: "user-42",
labels: { customer: "acme", user: "42" },
env: { NODE_ENV: "development" },
auto_pause: { enabled: true, idle_timeout_seconds: 300 },
});
console.log(sandbox.id, sandbox.state); // sbx_2fqxnt4bkq7m running
from taskcloud import Client
client = Client() # reads TASKCLOUD_API_KEY
sandbox = client.sandboxes.create(
template="base",
size="default",
name="user-42",
labels={"customer": "acme", "user": "42"},
env={"NODE_ENV": "development"},
auto_pause={"enabled": True, "idle_timeout_seconds": 300},
)
print(sandbox.id, sandbox.state)
curl -sS -X POST https://api.task.cloud/v1/sandboxes \
-H "Authorization: Bearer $TASKCLOUD_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: create-user-42" \
-d '{"template":"base","size":"default","name":"user-42","labels":{"customer":"acme"}}'
taskcloud create --template base --size default --name user-42 --label customer=acme
Create options#
| Field | Default | Notes |
|---|---|---|
template |
base |
name, ID, or name:version |
snapshot_id |
– | start from a named snapshot |
image |
– | { "ref": "ghcr.io/acme/env:1.4", "registry_auth": {...} } |
size |
default |
small, default, large |
name |
– | up to 120 chars |
labels |
– | up to 32 key/value pairs; filter with ?label=key=value |
env |
– | up to 100 variables, 64 KiB; write-only, never returned |
auto_pause |
{enabled: true, idle_timeout_seconds: 300} |
see Lifecycle |
always_on |
false |
never auto-pause; default size only, monthly price |
priority |
standard |
batch = −50%, may wait in queued |
region |
auto |
eu-de, eu-fi |
ttl_seconds |
null |
delete automatically after N seconds |
disk_gb |
size's included disk | 20–1000 |
ports |
– | up to 20 ports to expose at creation |
metadata |
– | free-form JSON up to 16 KiB, returned as-is |
wait |
true |
ignored for priority: "batch" |
Send an Idempotency-Key header to make retries safe: replaying the same key with the same body returns the original sandbox.
Inspect, list, update, delete#
const sbx = await client.sandboxes.get("sbx_2fqxnt4bkq7m"); // does not wake a paused sandbox
for await (const s of client.sandboxes.list({ state: ["running", "paused"], label: ["customer=acme"] })) {
console.log(s.id, s.state, s.last_activity_at);
}
await sbx.update({ name: "user-42-main", labels: { customer: "acme", tier: "pro" } });
await sbx.delete();
sbx = client.sandboxes.get("sbx_2fqxnt4bkq7m")
for s in client.sandboxes.list(state=["running", "paused"], label=["customer=acme"]):
print(s.id, s.state)
sbx.update(name="user-42-main")
sbx.delete()
GET /sandboxesis paginated (limit,cursor) and filters bystate,label,name_prefixandtemplate_id.PATCH /sandboxes/{sandbox_id}changesname,labels,auto_pause,always_on,ttl_secondsandmetadata.DELETE /sandboxes/{sandbox_id}returns202. The sandbox moves todeleting, thendeleted. Its disk and automatic pause snapshot are removed. Named snapshots are kept.
The sandbox object#
Key fields of Sandbox (full schema in the API reference):
| Field | Meaning |
|---|---|
id |
sbx_... |
state |
queued, creating, running, pausing, paused, resuming, deleting, deleted, error |
size, vcpus, memory_mb, disk_gb |
resources |
template_id, snapshot_id, image, forked_from |
where it came from |
auto_pause, always_on, priority, ttl_seconds, expires_at |
lifecycle settings |
url_template |
https://a2b3c4d5e6f7-{port}.taskhost.cloud |
ports |
exposed ports |
desktop |
true if the sandbox has a desktop |
error |
{code, message} when state is error |
created_at, started_at, paused_at, last_activity_at |
timestamps |
One sandbox per end user#
The most common pattern for agent platforms: give each of your users their own sandbox, label it with your user ID, and let auto-pause stop billing while the user is away. Create a restricted API key per user with restrict.labels so that a leaked key only reaches that user's sandboxes. See Security.
Related#
- Lifecycle: states, auto-pause, wake on request, billing per state
- Snapshots and fork
- Exec and files
- Networking
- Limits