# 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 `TaskCloud` after 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

```ts
// 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" });
```

```python
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`:

1. Build and push the image to any OCI registry: `docker build -t ghcr.io/acme/env:1 . && docker push ...`.
2. `POST /templates` with `{"name": "my-template", "image": {"ref": "ghcr.io/acme/env:1"}}`. Add `start_command` and `ready_check` if something must be running before the snapshot is taken.
3. 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](../concepts/templates.md).

## 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, `default` size 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](../concepts/lifecycle.md).

## 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](../billing.md).

## Checklist

- [ ] Create a `TaskCloud` API key with the `full-sandbox` preset (`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_pause` and/or `ttl_seconds`
- [ ] Update preview URLs to `https://<short_id>-<port>.taskhost.cloud`
- [ ] Subscribe to [webhooks](webhooks.md) (`sandbox.ready`, `sandbox.paused`, `sandbox.error`, ...)
- [ ] Handle errors by `error.code` and `error.retryable` (see [API reference](../api-reference.md))
