Skip to content
Docs

Working with Sandbox

Use Vercel Sandbox to run code, stream command output, manage files, capture snapshots, and stop sandboxes from your application.

Sandboxes are persistent by default: when a sandbox stops, the SDK automatically snapshots its filesystem and restores it on the next resume. Pass persistent: false at creation time for one-off, ephemeral workloads.

Create a sandbox, write a file into it, run the file, and inspect the command output.

index.ts
import { Sandbox } from '@vercel/sandbox';
 
const sandbox = await Sandbox.create({
  runtime: 'node24',
  timeout: 60_000,
});
 
try {
  await sandbox.writeFiles([
    {
      path: 'hello.js',
      content: Buffer.from("console.log('Hello from Vercel Sandbox!')\n"),
    },
  ]);
 
  const result = await sandbox.runCommand('node', ['hello.js']);
 
  if (result.exitCode !== 0) {
    throw new Error(await result.stderr());
  }
 
  console.log(await result.stdout());
} finally {
  await sandbox.stop();
}
main.py
import asyncio
 
from vercel.sandbox import AsyncSandbox
 
async def main() -> None:
    async with await AsyncSandbox.create(
        runtime="python3.13",
        timeout=60_000,
    ) as sandbox:
        await sandbox.write_files(
            [
                {
                    "path": "hello.py",
                    "content": b"print('Hello from Vercel Sandbox!')\n",
                }
            ]
        )
 
        result = await sandbox.run_command("python3", ["hello.py"])
 
        if result.exit_code != 0:
            raise RuntimeError(await result.stderr())
 
        print(await result.stdout())
 
asyncio.run(main())

To start from your own system packages and tooling instead of a base runtime, pass a custom image from Vercel Container Registry. See Images for how images work and how to push one:

index.ts
import { Sandbox } from '@vercel/sandbox';
 
const sandbox = await Sandbox.create({
  image: 'my-repository:latest',
});

Persistent sandboxes keep their filesystem across sessions. Create a sandbox, write a file, stop it, then resume by name and read the file back — no snapshot ID to track and no setup to repeat.

index.ts
import { Sandbox } from '@vercel/sandbox';
 
// First run: create a named sandbox, write a file, stop it.
const sandbox = await Sandbox.create({ name: 'my-sandbox' });
await sandbox.writeFiles([
  {
    path: '/vercel/sandbox/notes.txt',
    content: Buffer.from('Hello from the first session.\n'),
  },
]);
await sandbox.stop();
 
// Later, in a separate process: resume the same sandbox by name and
// read the file back. The next SDK call auto-resumes the session.
const resumed = await Sandbox.get({ name: 'my-sandbox' });
const notes = await resumed.runCommand('cat', ['/vercel/sandbox/notes.txt']);
console.log(await notes.stdout()); // Hello from the first session.

By default, sandboxes timeout after 5 minutes. For longer tasks, set a custom timeout when creating the sandbox:

index.ts
import { Sandbox } from '@vercel/sandbox';
 
const sandbox = await Sandbox.create({
  timeout: 3 * 60 * 60 * 1000, // 3 hours
});
 
try {
  console.log(sandbox.timeout);
} finally {
  await sandbox.stop();
}
main.py
import asyncio
 
from vercel.sandbox import AsyncSandbox
 
async def main() -> None:
    async with await AsyncSandbox.create(
        timeout=3 * 60 * 60 * 1000,  # 3 hours
    ) as sandbox:
        print(sandbox.timeout)
 
asyncio.run(main())

To extend a running sandbox, call extendTimeout in TypeScript or extend_timeout() in Python:

index.ts
import { Sandbox } from '@vercel/sandbox';
 
const sandbox = await Sandbox.create();
 
try {
  await sandbox.extendTimeout(2 * 60 * 60 * 1000); // Add 2 hours
} finally {
  await sandbox.stop();
}
main.py
import asyncio
 
from vercel.sandbox import AsyncSandbox
 
async def main() -> None:
    async with await AsyncSandbox.create() as sandbox:
        await sandbox.extend_timeout(2 * 60 * 60 * 1000)  # Add 2 hours
 
asyncio.run(main())

See Pricing and Limits for maximum durations by plan.

Use a detached command when you need to follow long-running output, keep a server alive, or wait for completion later.

index.ts
import { Sandbox } from '@vercel/sandbox';
 
const sandbox = await Sandbox.create({ timeout: 120_000 });
 
try {
  const command = await sandbox.runCommand({
    cmd: 'bash',
    args: ['-lc', 'for i in 1 2 3; do echo $i; sleep 1; done'],
    detached: true,
  });
 
  for await (const line of command.logs()) {
    if (line.stream === 'stdout') {
      process.stdout.write(line.data);
    } else {
      process.stderr.write(line.data);
    }
  }
 
  const finished = await command.wait();
  console.log(finished.exitCode);
} finally {
  await sandbox.stop();
}
main.py
import asyncio
 
from vercel.sandbox import AsyncSandbox
 
async def main() -> None:
    async with await AsyncSandbox.create(timeout=120_000) as sandbox:
        command = await sandbox.run_command_detached(
            "bash",
            ["-lc", "for i in 1 2 3; do echo $i; sleep 1; done"],
        )
 
        async for line in command.logs():
            print(line.stream, line.data, end="")
 
        finished = await command.wait()
        print(finished.exit_code)
 
asyncio.run(main())

Use file APIs when your local application needs to send input files to the sandbox and retrieve a build output.

index.ts
import { Sandbox } from '@vercel/sandbox';
 
const sandbox = await Sandbox.create({ runtime: 'node24' });
 
try {
  await sandbox.mkDir('src');
  await sandbox.writeFiles([
    {
      path: 'src/build-artifact.js',
      content: Buffer.from(
        "import { mkdir, writeFile } from 'node:fs/promises';\n\n" +
          "await mkdir('dist', { recursive: true });\n" +
          "await writeFile('dist/output.txt', 'hello from sandbox\\n');\n"
      ),
    },
  ]);
 
  const result = await sandbox.runCommand('node', ['src/build-artifact.js']);
 
  if (result.exitCode !== 0) {
    throw new Error(await result.stderr());
  }
 
  await sandbox.downloadFile(
    { path: 'dist/output.txt' },
    { path: './artifacts/dist/output.txt' },
    { mkdirRecursive: true }
  );
} finally {
  await sandbox.stop();
}
main.py
import asyncio
 
from vercel.sandbox import AsyncSandbox
 
async def main() -> None:
    async with await AsyncSandbox.create(runtime="python3.13") as sandbox:
        await sandbox.mk_dir("src")
        await sandbox.write_files(
            [
                {
                    "path": "src/build_artifact.py",
                    "content": (
                        b"from pathlib import Path\n\n"
                        b"Path('dist').mkdir(exist_ok=True)\n"
                        b"Path('dist/output.txt').write_text('hello from sandbox\\n')\n"
                    ),
                }
            ]
        )
 
        result = await sandbox.run_command("python3", ["src/build_artifact.py"])
 
        if result.exit_code != 0:
            raise RuntimeError(await result.stderr())
 
        await sandbox.download_file(
            "dist/output.txt",
            "./artifacts/dist/output.txt",
            create_parents=True,
        )
 
asyncio.run(main())

Use snapshots after dependency installation or environment setup so future sandboxes start from the same filesystem state. With persistent sandboxes, snapshots are also created automatically every time the sandbox stops; for manual checkpoints, call snapshot() explicitly.

To spawn fresh children from another sandbox's current snapshot without tracking IDs manually, use Sandbox.fork. The fork inherits the source's config and is seeded from its latest snapshot:

index.ts
import { Sandbox } from '@vercel/sandbox';
 
const child = await Sandbox.fork({
  sourceSandbox: 'my-base-sandbox',
  persistent: false,
});
index.ts
import { Sandbox } from '@vercel/sandbox';
 
const MIN_SNAPSHOT_EXPIRATION_MS = 24 * 60 * 60 * 1000;
 
let snapshotId = '';
const sandbox = await Sandbox.create({ runtime: 'node24' });
 
try {
  await sandbox.writeFiles([
    { path: 'config.json', content: Buffer.from('{"env": "prod"}') },
  ]);
 
  const snapshot = await sandbox.snapshot({
    expiration: MIN_SNAPSHOT_EXPIRATION_MS,
  });
  snapshotId = snapshot.snapshotId;
} catch (error) {
  await sandbox.stop();
  throw error;
}
 
const restored = await Sandbox.create({
  source: { type: 'snapshot', snapshotId },
  timeout: 120_000,
});
 
try {
  const result = await restored.runCommand('cat', ['config.json']);
  console.log(await result.stdout());
} finally {
  await restored.stop();
}
main.py
import asyncio
 
from vercel.sandbox import AsyncSandbox, MIN_SNAPSHOT_EXPIRATION_MS
 
async def main() -> None:
    async with await AsyncSandbox.create(runtime="python3.13") as sandbox:
        await sandbox.write_files(
            [{"path": "config.json", "content": b'{"env": "prod"}'}]
        )
        snapshot = await sandbox.snapshot(
            expiration=MIN_SNAPSHOT_EXPIRATION_MS
        )
 
    async with await AsyncSandbox.create(
        source={"type": "snapshot", "snapshot_id": snapshot.snapshot_id},
        timeout=120_000,
    ) as restored:
        print(await restored.read_file("config.json"))
 
asyncio.run(main())

Connect to a running sandbox for interactive debugging with an SSH-like experience:

Terminal
sandbox connect <name>

Once connected, you have full shell access to inspect logs, check processes, and explore the filesystem.

See CLI Reference for all options.

View your sandboxes in the Sandboxes dashboard. For each project, you can see:

  • Total sandboxes created
  • Currently running sandboxes
  • Stopped sandboxes
  • Command history and sandbox URLs

Track compute usage across projects in the Usage dashboard, which measures:

  • Sandbox Provisioned Memory: Memory allocated to your sandboxes
  • Sandbox Data Transfer: Data transferred in and out
  • Sandbox Active CPU: CPU time consumed
  • Sandbox Creations: Number of sandboxes created
  • Snapshot Storage: Sandbox snapshot storage

There are three ways to stop a sandbox:

  1. Go to Sandboxes in Observability.
  2. Select your sandbox.
  3. Click Stop Sandbox.
index.ts
import { Sandbox } from '@vercel/sandbox';
 
const sandbox = await Sandbox.create();
 
try {
  // Run your workflow here.
} finally {
  await sandbox.stop();
}
main.py
import asyncio
 
from vercel.sandbox import AsyncSandbox
 
async def main() -> None:
    sandbox = await AsyncSandbox.create()
    try:
        # Run your workflow here.
        pass
    finally:
        await sandbox.stop()
 
asyncio.run(main())

Sandboxes stop automatically when their timeout expires. The default is 5 minutes.

Stopping a persistent sandbox ends the current session but keeps the sandbox so it can be resumed later. To remove the sandbox along with all of its snapshots and sessions, delete it. This cannot be undone.

The dashboard is the safest way to delete a single sandbox interactively. It requires you to type the sandbox name plus a verification phrase before the deletion goes through:

  1. Go to Sandboxes in Observability and select the sandbox you want to delete.
  2. Scroll to the Delete Sandbox section at the bottom of the detail page.
  3. Click Delete Sandbox.
  4. In the confirmation modal, type the sandbox name and the verification phrase delete my sandbox, then click Delete Sandbox.

Use sandbox.delete() from the JS SDK to remove the sandbox in code — useful for cleanup at the end of a job or when reacting to an event:

index.ts
import { Sandbox } from '@vercel/sandbox';
 
const sandbox = await Sandbox.get({ name: 'my-sandbox' });
await sandbox.delete();

Run sandbox remove for ad-hoc cleanup or to script deletion alongside other CLI commands:

Terminal
sandbox remove my-sandbox
Last updated June 30, 2026

Was this helpful?

supported.