# Migrate from Vercel Sandbox to Sandboxer

> Swap @vercel/sandbox for sandboxer-sdk and a Vercel OIDC token for one Sandboxer API key. Replace the git source with a clone command, ports and domain() with host(port), and persistent sandboxes with stop and resume, which also save memory. Billing changes from active CPU plus memory to a flat per-second price per size.

Source: https://sandboxer.com/migrate/vercel-sandbox. Updated 2026-10-27.

**Short answer:** Swap @vercel/sandbox for sandboxer-sdk and a Vercel OIDC token for one Sandboxer API key. Replace the git source with a clone command, ports and domain() with host(port), and persistent sandboxes with stop and resume, which also save memory. Billing changes from active CPU plus memory to a flat per-second price per size.

## Concept map

Vercel facts come from the [Vercel Sandbox docs](https://vercel.com/docs/sandbox), read on 2026-10-27.

| Vercel Sandbox | Sandboxer |
| - | - |
| Sandbox: a Firecracker microVM ([Concepts](https://vercel.com/docs/sandbox/concepts)) | Sandbox: a Cloud Hypervisor microVM with Ubuntu 24.04 ([Machines](/docs/machines)) |
| `resources.vcpus`, 2 GB of memory per vCPU | Four sizes: `small` 2 vCPU / 4 GB up to `xlarge` 16 vCPU / 32 GB |
| Image (`vercel/sandbox/universal`, or your own in Vercel Container Registry) | One base image with Node.js 22, Python 3 and Docker, plus your named snapshots and setup scripts |
| `source: { type: "git" }` | A `git clone` command, a setup script, or a repo attached to an [environment](/docs/environments) |
| Working directory `/vercel/sandbox` | Home directory `/home/user` |
| Persistent sandbox (filesystem saved on stop, restored on resume) | Stop and resume: disk **and memory** saved ([Snapshots](/docs/snapshots)) |
| `sandbox.snapshot()`, then `source: { type: "snapshot" }` | Named snapshot, then `from: "<name>"` |
| `Sandbox.fork()` | `sandbox.fork()` |
| `ports` at create, `sandbox.domain(port)` (public URL) | `sandbox.host(port)` at any time, private with a token or public ([Hosting](/docs/hosting)) |
| `timeout` (default 5 minutes) | `ttlSeconds` auto-stop (default 1 hour) |
| `env` at create | `env` at create, or account-wide [environments](/docs/environments) |
| Vercel OIDC token, or access token plus team and project IDs | One API key, `sbx_live_…` ([API keys](/docs/api-keys)) |
| JS/TS and Python SDKs, `sandbox` CLI | TypeScript and Python SDKs (`sandboxer-sdk`), `sbx` CLI |
| 19 regions, default `iad1` | `us-east` and `eu-central`; set `region`, or get the nearest ([regions](/docs/regions)) |

## API and SDK mapping

Their calls are from the [JS SDK reference](https://vercel.com/docs/sandbox/sdk-reference). Ours are from `sandboxer-sdk` 0.1.1, which we ran against prod.

| Vercel Sandbox (`@vercel/sandbox`) | Sandboxer SDK | Sandboxer CLI |
| - | - | - |
| `Sandbox.create({ name, timeout, resources })` | `sandboxer.create({ name, ttlSeconds, type })` | `sbx new --name … --ttl … --type …` |
| `source: { type: "git", url }` | `sandbox.exec("git clone …")` or `setupScript` | `sbx new --setup-file ./setup.sh` |
| `sandbox.runCommand("node", ["--version"])` | `sandbox.exec(["node", "--version"])` | `sbx exec <id> "node --version"` |
| `runCommand({ …, detached: true })` | `sandbox.start("npm run dev")` | `sbx exec <id> --detach "…"` |
| `command.logs()`, `command.wait()` | `command.attach()`, `command.wait()` | `sbx exec <id> --attach <pid>` |
| `sandbox.writeFiles([{ path, content }])` | `sandbox.files.write(path, content)` | `sbx scp ./file <id>:/home/user/` |
| `sandbox.readFile()`, `downloadFile()` | `files.read()`, `files.download()` | `sbx scp <id>:/home/user/out.zip .` |
| `sandbox.domain(3000)` | `(await sandbox.host(3000)).url` | `sbx host <id> 3000` |
| `sandbox.extendTimeout(ms)` | `sandbox.extend(ttlSeconds)` | `sbx extend <id> --ttl …` |
| `sandbox.stop()` | `sandbox.stop()` | `sbx stop <id>` |
| `Sandbox.get({ name })`, auto-resume | `sandboxer.get(id)`, then `sandbox.resume()` | `sbx resume <id>` |
| `Sandbox.fork({ sourceSandbox })` | `sandbox.fork()` | `sbx fork <id>` |
| `sandbox.snapshot()` | `sandbox.snapshot("name")` | `sbx snapshot <id> <name>` |
| `Sandbox.create({ source: { type: "snapshot", snapshotId } })` | `sandboxer.create({ from: "name" })` | `sbx new --from <name>` |
| `sandbox.delete()` | `sandbox.delete()` | `sbx delete <id> --yes` |
| `sandbox connect <name>` | `sandbox.pty()` | `sbx ssh <id>` |

Two behaviors change. Vercel's `snapshot()` shuts the sandbox down. Ours left the source sandbox `ready`. And `sandbox.exec` waits at most 600 seconds, so run long jobs with `sandbox.start`.

## Before and after

From Vercel's docs: [the `@vercel/sandbox` README](https://github.com/vercel/sandbox/blob/main/packages/vercel-sandbox/README.md), linked from the [Vercel Sandbox docs](https://vercel.com/docs/sandbox). We condensed it: output streaming and error handling are left out.

```typescript
import { Sandbox } from "@vercel/sandbox";

const sandbox = await Sandbox.create({
  source: { url: "https://github.com/vercel/sandbox-example-next.git", type: "git" },
  resources: { vcpus: 4 },
  ports: [3000],
  name: "vercel-sandbox-example",
});
await sandbox.runCommand({ cmd: "npm", args: ["install", "--loglevel", "info"], cwd: "sandbox-example-next" });
await sandbox.runCommand({ cmd: "npm", args: ["run", "dev"], cwd: "sandbox-example-next", detached: true });
spawn("open", [sandbox.domain(3000)]);
```

Writing a file, from the [JS SDK reference](https://vercel.com/docs/sandbox/sdk-reference#sandbox.writeFiles):

```typescript
await sandbox.writeFiles([{ path: "hello.txt", content: Buffer.from("hi") }]);
```

The same app on Sandboxer. Install with `npm i sandboxer-sdk` and set `SANDBOXER_API_KEY`. We ran this file with `node index.mts` on Node 24:

```typescript
import { Sandboxer } from "sandboxer-sdk";

const sandboxer = new Sandboxer(); // reads SANDBOXER_API_KEY
const sandbox = await sandboxer.create({ type: "default", name: "aeo-migrate-vercel-1", ttlSeconds: 3600 });
console.log("created", sandbox.id, sandbox.subdomain);

await sandbox.exec("git clone --depth 1 https://github.com/vercel/sandbox-example-next.git", { check: true, timeoutSeconds: 120 });
await sandbox.files.write("sandbox-example-next/public/hello.txt", "written by files.write\n");

const install = await sandbox.exec("npm install --no-audit --no-fund", { cwd: "sandbox-example-next", timeoutSeconds: 600 });
console.log("npm install exit", install.exitCode);

await sandbox.start("npm run dev", { cwd: "sandbox-example-next" });
const { url } = await sandbox.host(3000, { public: true });
console.log(url);
```

```text
created sb_4vcm57bokm open-tidy-kestrel
npm install exit 0
https://open-tidy-kestrel-3000.box.sandboxer.com
```

The whole script took 28 seconds (one run on prod, 2026-10-27). A few seconds later, the URL served the Next.js page (`<title>Create Next App</title>`), and `/hello.txt` returned `written by files.write`. Node and npm were already installed, so no image choice was needed.

Stop and resume, with the same SDK:

```typescript
const sandbox = await sandboxer.get("sb_4vcm57bokm");
await sandbox.stop();     // waits until archived
await sandbox.resume();   // waits until ready
const fork = await sandbox.fork({ ttlSeconds: 900 });
```

The dev server was PID 593 before the stop and PID 593 after the resume. The hosted URL answered `200` with no restart. On Vercel, a resume restores the filesystem, and its SDK offers an `onResume` hook to restart servers. You can usually drop that hook (see the FAQ for the cold-boot cases). The fork (`sb_qt0b8aulhi`) had the files but no running dev server, because a fork boots fresh from disk. It also gets its own subdomain, so host its ports again.

### The CLI

The same steps with `sbx`, run on the fork:

```bash
sbx scp ./proj.tgz sb_qt0b8aulhi:/home/user/
sbx exec sb_qt0b8aulhi "tar xzf proj.tgz && APP_MODE=dev node proj/main.js"   # hi from my project, APP_MODE=dev
sbx exec sb_qt0b8aulhi --detach "cd proj && python3 -m http.server 8080"     # {"type":"command.started","pid":636,…}
sbx host sb_qt0b8aulhi 8080
# {"type":"port.hosted","url":"https://cheery-azure-jay-8080.box.sandboxer.com?_token=…","access":"private",…}
```

Without the `_token`, that URL returned `401`. Vercel's `domain()` URLs are public. To match them, pass `--public` on the CLI or `{ public: true }` in the SDK.

## Move your data and environment

**Source and git checkout.** There is no `source` option. Clone in a command, as above, or put the clone in a setup script with `sbx new --setup-file ./setup.sh`. For private repos, connect GitHub on the dashboard and run `sbx env add-repo base acme/web --branch main`. New sandboxes then clone it into `/home/user/web` with a token that can push ([Environments](/docs/environments#repositories)). Change any absolute `/vercel/sandbox` paths to `/home/user`.

**Files.** Sandboxes don't move between providers. On Vercel, copy files out with `sandbox copy` or `downloadFile()`. Then tar them and upload with `sbx scp`, as above. Use `sbx scp -r` for a directory. `files.write` takes files up to 5 MiB.

**Images and snapshots.** Vercel snapshots and VCR images don't carry across. Build your stack once in a sandbox, then save it as a named snapshot. New sandboxes start from it:

```bash
sbx snapshot sb_4vcm57bokm aeo-migrate-vercel-tpl
# {"type":"snapshot.named.info","snapshot":{"name":"aeo-migrate-vercel-tpl","status":"ready","type":"default",…}}
sbx new --from aeo-migrate-vercel-tpl --name aeo-migrate-vercel-3 --ttl 1800 -e APP_MODE=staging
```

```text
{"event":"ready","id":"sb_r02kkcui6y","state":"ready","url":"https://happy-plucky-quail.box.sandboxer.com",…}
```

The new sandbox was ready 2 seconds after the create. It already had the clone, all 290 `node_modules` entries and `hello.txt`, and `echo $APP_MODE` printed `staging`. Named snapshots have no expiry. Vercel's expire 30 days after last use by default.

**Env vars and secrets.** `env` on create works as on Vercel: `-e KEY=value` on the CLI, `env: {…}` in the SDK. For values every sandbox needs, use `sbx env set-var base KEY=value` or `sbx env set-file base path --from ./file` ([Environments](/docs/environments)).

**Auth.** Vercel needs an OIDC token from `vercel link` and `vercel env pull`, which expires after 12 hours, or an access token plus team and project IDs ([Vercel authentication](https://vercel.com/docs/sandbox/concepts/authentication)). Sandboxer needs one key. Create it on the dashboard under **API keys**, or with `sbx api-key create`, and set `SANDBOXER_API_KEY`. Keys can be limited to actions and sandboxes, which suits CI ([API keys](/docs/api-keys)). Code running inside a sandbox already has `SANDBOXER_TOKEN`, so `new Sandboxer()` works there with no arguments.

**Timeouts.** Vercel's `timeout` is in milliseconds, defaults to 5 minutes, and a session is capped at 45 minutes on Hobby and 24 hours on Pro ([limits](https://vercel.com/docs/sandbox/pricing#runtime-limits)). Our `ttlSeconds` is in seconds and defaults to 1 hour. On a paid plan, `--no-auto-stop` (or `ttlSeconds: null`) removes the limit. During the trial the most is 8 hours. Separately, an idle sandbox pauses itself (after 30 minutes on the $20 plan) and wakes on the next request ([Long-running tasks](/docs/long-running)).

## What's different

What you gain:

- **Memory on stop.** A resume brings back running processes with the same PIDs, as we saw above, when it lands on a machine with the same CPU type. Otherwise it boots from disk and shows `memoryRestored: false` ([Snapshots](/docs/snapshots#resume)). Vercel's docs describe saving the filesystem.
- **No session cap on paid plans.** One sandbox can run for days.
- **A fixed price per size.** You know the hourly cost before you start.
- **A full machine.** Every sandbox has a streamed desktop, Docker and `/dev/kvm` ([Machines](/docs/machines)). Six coding agents are installed ([Agents](/docs/agents)).

What you lose or must change:

- **Regions.** Vercel offers 19. Sandboxer has two regions, us-east (Ashburn, Virginia) and eu-central (Helsinki, Finland); see [regions](/docs/regions).
- **Active-CPU billing.** If your sandboxes mostly wait on model calls, Vercel's billing can cost less. See below.
- **Network features.** Vercel has an egress `networkPolicy`, Secure Compute with static IPs and VPC peering, and Drives. Our docs describe none of these.
- **Custom OCI images.** Vercel starts from any image in its registry. We start from our base image plus your named snapshot.
- **Scale.** Vercel Pro allows 10,000 concurrent sandboxes and up to 8 vCPUs, and Enterprise up to 32 vCPUs. Our plans allow 100 to 2,000 at once, and `xlarge` (16 vCPU) needs the $100 plan.
- **Vercel integration.** If your app deploys on Vercel, its OIDC tokens and one bill go away.

## Pricing

Vercel's rates are from its [pricing page](https://vercel.com/docs/sandbox/pricing) (Pro, `iad1` region, checked 2026-10-27). Ours are from [/pricing](/pricing). Vercel bills active CPU at $0.128 per hour and provisioned memory at $0.0212 per GB-hour. We bill a flat rate per second while a sandbox runs.

| 4 vCPU / 8 GB, one hour | Price |
| - | - |
| Sandboxer `default` | $0.036/hour |
| Vercel, CPU busy the whole hour | $0.68 (4 × $0.128 + 8 × $0.0212) |
| Vercel, CPU busy 10% of the hour | $0.22 (0.4 × $0.128 + 8 × $0.0212) |

Memory is billed on Vercel even while the CPU waits. So a 4 vCPU sandbox costs at least $0.17 an hour there, before any CPU. A machine running 8 hours a day for 22 days (176 hours) costs $6.34 on Sandboxer, and $38.86 to $119.96 on Vercel, depending on CPU use. Vercel's Hobby plan includes 5 active-CPU hours and 420 GB-hours a month for free. Pro includes a $20 monthly credit. Vercel also bills snapshot storage ($0.08 per GB-month) and sandbox creations. Storage isn't billed on Sandboxer: each plan includes some (50 GB on the $20 plan), and at the cap new saves return `402 storage_limit_reached` ([Billing](/docs/billing#storage)). Our trial is 7 days with 25 hours and needs a card.

## FAQ

### Do I need to rewrite my code?

Yes, but the shapes are close. `Sandbox.create` becomes `sandboxer.create`, `runCommand` becomes `exec` or `start`, `writeFiles` becomes `files.write`, and `domain(port)` becomes `host(port)`. There is no wire-compatible mode for `@vercel/sandbox`.

### What replaces `ports` and `domain()`?

Call `sandbox.host(port)` or `sbx host <id> <port>` when the server is up. You don't declare ports at create time. URLs are private by default; pass `public: true` for a URL like Vercel's.

### How do I clone a private repo without `source.password`?

Connect GitHub once on the dashboard and attach the repo to an environment with `sbx env add-repo`. New sandboxes clone it into `/home/user` with a token that can push.

### Will my `onResume` hook still be needed?

Usually not. A resume restores memory, so a dev server started before the stop keeps running. Keep the hook for the cases that cold boot: a size change on resume, or no machine with the same CPU type free.

### Is there a Python SDK?

Yes. Install `sandboxer-sdk` from PyPI. It imports as `sandboxer` and has the same methods in snake_case ([Python SDK](/docs/sdk-python)).

## Related

- [Sandboxer vs Vercel Sandbox](/compare/vercel-sandbox)
- [Quickstart](/docs/quickstart)
- [TypeScript SDK](/docs/sdk-typescript)
- [Hosting](/docs/hosting)
- [Snapshots](/docs/snapshots)
