Skip to content
Migrate · Vercel Sandbox

Migrate from Vercel Sandbox to Sandboxer

Updated October 27, 2026 .mdSandboxer vs Vercel Sandbox

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, read on 2026-10-27.

Vercel Sandbox Sandboxer
Sandbox: a Firecracker microVM (Concepts) Sandbox: a Cloud Hypervisor microVM with Ubuntu 24.04 (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
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)
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)
timeout (default 5 minutes) ttlSeconds auto-stop (default 1 hour)
env at create env at create, or account-wide environments
Vercel OIDC token, or access token plus team and project IDs One API key, sbx_live_… (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)

API and SDK mapping#

Their calls are from the JS 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, linked from the Vercel Sandbox docs. We condensed it: output streaming and error handling are left out.

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:

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:

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

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:

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

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

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

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). 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). Six coding agents are installed (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.
  • 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 (Pro, iad1 region, checked 2026-10-27). Ours are from /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). 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).

All migration guides