Migrate from E2B#
TaskCloud covers the same core workflow as E2B: create a sandbox from a template, run commands, read and write files, expose ports, pause and resume. Most migrations are a mechanical rename. This page maps the concepts and lists the differences in behavior.
E2B compatibility adapter: Coming soon. We plan an adapter that lets existing E2B SDK code run against
TaskCloudafter you change the import and the API key. Until it ships, use the mapping below.E2B's SDKs change over time. The E2B names below describe its public SDK in general terms; check E2B's current documentation for exact signatures.
What changes conceptually#
| E2B | TaskCloud | |
|---|---|---|
| Isolation | Firecracker microVM | Firecracker microVM with its own kernel, Ubuntu 24.04, root, systemd and Docker inside |
| Lifetime | Session with a timeout. The sandbox is killed or paused when the timeout runs out. | Persistent computer. No time limit by default. Idle sandboxes auto-pause (default after 5 min) and wake on the next request. |
| Disk | Ephemeral unless paused | Persistent disk (50 GB included with default) |
| Billing while idle | Depends on plan and pause usage | $0 compute while paused, billed per second while running |
| Fork | — | Fork a running VM with memory (target < 2 s) |
| Region | Check E2B | EU only |
API and SDK mapping#
| E2B (general) | TaskCloud TypeScript | TaskCloud REST |
|---|---|---|
Sandbox.create(template) |
client.sandboxes.create({ template }) |
POST /sandboxes |
| Connect to an existing sandbox by ID | client.sandboxes.get(id) |
GET /sandboxes/{sandbox_id} |
sandbox.commands.run(cmd) |
sandbox.exec({ command }) |
POST /sandboxes/{sandbox_id}/exec |
| Run in the background | sandbox.exec({ command, background: true }) |
same endpoint, background: true |
| Stream output callbacks | sandbox.exec({ command, stream: true }) |
same endpoint, stream: true (SSE) |
| Interactive terminal (PTY) | PTY session over WebSocket | POST /sandboxes/{sandbox_id}/pty |
files.read(path) / files.write(path, data) |
sandbox.files.read(path) / sandbox.files.write(path, data) |
GET / PUT /sandboxes/{sandbox_id}/files/content |
files.list(dir) |
sandbox.files.list(dir) |
GET /sandboxes/{sandbox_id}/files |
files.watchDir(dir) |
sandbox.files.watch(dir) |
GET /sandboxes/{sandbox_id}/files/watch (SSE) |
getHost(port) |
sandbox.ports.expose({ port, visibility }) returns url |
POST /sandboxes/{sandbox_id}/ports → https://<short_id>-<port>.taskhost.cloud |
Sandbox timeout / setTimeout |
auto_pause.idle_timeout_seconds (pause when idle) or ttl_seconds (delete after N s) |
PATCH /sandboxes/{sandbox_id} |
| Pause / resume | sandbox.pause() / sandbox.resume(), plus automatic pause and wake on request |
POST .../pause, POST .../resume |
kill() |
sandbox.delete() |
DELETE /sandboxes/{sandbox_id} |
| Custom template (Dockerfile-based) | Template from an OCI image | POST /templates with image |
| Desktop SDK | template: "desktop" + sandbox.desktop.* |
/desktop, /screenshot, /input |
| API key header | Authorization: Bearer taskcloud_sk_... |
— |
Before and after#
// E2B (illustrative)
// const sbx = await Sandbox.create("my-template");
// const r = await sbx.commands.run("python app.py");
// const url = `https://${sbx.getHost(3000)}`;
// TaskCloud
import { Client } from "@taskcloud/sdk";
const client = new Client(); // reads TASKCLOUD_API_KEY
const sandbox = await client.sandboxes.create({ template: "my-template" });
const r = await sandbox.exec({ command: "python app.py", timeout_seconds: 120 });
console.log(r.exit_code, r.stdout);
await sandbox.exec({ command: "python -m http.server 3000", background: true });
const { url } = await sandbox.ports.expose({ port: 3000, visibility: "public" });
from taskcloud import Client
client = Client()
sandbox = client.sandboxes.create(template="my-template")
r = sandbox.exec(command="python app.py", timeout_seconds=120)
print(r.exit_code, r.stdout)
Templates#
E2B templates are typically built from a Dockerfile. On TaskCloud:
- Build and push the image to any OCI registry:
docker build -t ghcr.io/acme/env:1 . && docker push .... POST /templateswith{"name": "my-template", "image": {"ref": "ghcr.io/acme/env:1"}}. Addstart_commandandready_checkif something must be running before the snapshot is taken.- Create sandboxes with
template: "my-template". The template is a RAM snapshot, so a new sandbox starts with your processes already running (target p95 < 500 ms).
A second option: set up one sandbox by hand, take a snapshot and turn it into a template (snapshot_id). See Templates.
Timeouts: think "idle" instead of "max lifetime"#
An E2B sandbox usually lives for a fixed time window. A TaskCloud sandbox is meant to persist, and the controls are:
auto_pause.idle_timeout_seconds(default 300): pause after this much idle time. Paused sandboxes cost $0 for compute, and the next exec, file, port, SSH or desktop request wakes them (target p95 < 800 ms).ttl_seconds: delete the sandbox automatically after N seconds. Use it for one-off jobs.always_on: true: never pause (fixed monthly price,defaultsize only).
So a typical "give every user their own VM" setup is: create one sandbox per user, keep it forever, and let auto-pause handle cost. See Lifecycle.
Cost comparison#
Hourly price for 4 vCPU / 8 GB, according to battleships.dev as of 2026-10: E2B $0.331 per hour; TaskCloud default $0.030 per active hour.
Example: 1000 VMs, each active 10% of the time (73 h/month):
| Monthly | |
|---|---|
| E2B (73 h × $0.331) | ~$24,163 |
| TaskCloud (73 h × $0.030, $0 while paused) | ~$2,190 |
Your numbers depend on your plan and usage. Check current pricing on both sides. See Billing.
Checklist#
- [ ] Create a
TaskCloudAPI key with thefull-sandboxpreset (POST /api-keys) - [ ] Rebuild custom templates from your image (
POST /templates) - [ ] Replace SDK calls using the table above (snake_case fields, e.g.
timeout_seconds,background) - [ ] Replace per-session timeouts with
auto_pauseand/orttl_seconds - [ ] Update preview URLs to
https://<short_id>-<port>.taskhost.cloud - [ ] Subscribe to webhooks (
sandbox.ready,sandbox.paused,sandbox.error, ...) - [ ] Handle errors by
error.codeanderror.retryable(see API reference)