Skip to content
Docs

Working with Sandbox

Use 分镜故事板 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({
  timeout: 60_000,
});
 
try {
  await sandbox.writeFiles([
    {
      path: 'hello.js',
      content: Buffer.from("console.log('Hello from 分镜故事板!')\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 datetime import timedelta
 
from vercel import sandbox
 
 
async def main() -> None:
    async with sandbox.create_sandbox(
        execution_time_limit=timedelta(minutes=1),
    ) as box:
        await box.fs.write_text(
            "hello.py",
            "print('Hello from 分镜故事板!')\n",
        )
 
        result = await box.run_process(
            "python",
            ["hello.py"],
            capture_output=True,
            check=True,
        )
        print(result.stdout)
 
 
asyncio.run(main())

You can use any of the Vercel Managed Image, or start from your own or a shared custom image hosted on Vercel Container Registry. See Images for how to use it:

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, with 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.
main.py
from vercel import sandbox
 
# First run: create a named sandbox, write a file, stop it.
box = await sandbox.create_sandbox(
    name="my-sandbox",
    persistent=True,
)
await box.fs.write_text("notes.txt", "Hello from the first session.\n")
await box.stop()
 
# Later, in a separate process: retrieve the sandbox by name. Reading
# the restored file automatically resumes the session.
resumed = await sandbox.get_sandbox(name="my-sandbox")
print(await resumed.fs.read_text("notes.txt"))

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 datetime import timedelta
 
from vercel import sandbox
 
 
async def main() -> None:
    async with sandbox.create_sandbox(
        execution_time_limit=timedelta(hours=3),
    ) as box:
        print(box.execution_time_limit)
 
 
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 datetime import timedelta
 
from vercel import sandbox
 
 
async def main() -> None:
    async with sandbox.create_sandbox() as box:
        await box.extend_execution_time_limit(timedelta(hours=2))
 
 
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
import sys
from datetime import timedelta
 
from vercel import sandbox
 
 
async def main() -> None:
    async with sandbox.create_sandbox(
        execution_time_limit=timedelta(minutes=2),
    ) as box:
        process = await box.create_process(
            "sh",
            ["-lc", "for i in 1 2 3; do echo $i; sleep 1; done"],
        )
 
        assert process.stdout is not None
        async for line in process.stdout:
            sys.stdout.write(line)
 
        print(await process.wait())
 
 
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();
 
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
 
import anyio
 
from vercel import sandbox
 
 
async def main() -> None:
    async with sandbox.create_sandbox() as box:
        await box.fs.write_text(
            "src/build_artifact.py",
            """\
from pathlib import Path
 
Path('dist').mkdir(exist_ok=True)
Path('dist/output.txt').write_text('hello from sandbox\\n')
""",
        )
        await box.run_process(
            "python",
            ["src/build_artifact.py"],
            check=True,
        )
 
        local_path = anyio.Path("artifacts/dist/output.txt")
        await local_path.parent.mkdir(parents=True, exist_ok=True)
        async with (
            box.fs.open("dist/output.txt", "rb") as source,
            await anyio.open_file(local_path, "wb") as target,
        ):
            while chunk := await source.read(64 * 1024):
                await target.write(chunk)
 
 
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 datetime import timedelta
 
from vercel import sandbox
from vercel.sandbox import SnapshotSource
 
 
async def main() -> None:
    async with sandbox.create_sandbox() as box:
        await box.fs.write_text("config.json", '{"env": "prod"}')
        snapshot = await box.snapshot(expiration=timedelta(days=1))
 
    async with sandbox.create_sandbox(
        source=SnapshotSource(snapshot_id=snapshot.id),
        execution_time_limit=timedelta(minutes=2),
    ) as restored:
        print(await restored.fs.read_text("config.json"))
 
    await snapshot.delete()
 
 
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 your sandboxes send to the internet, plus all traffic to and from exposed ports, is billable. Data your sandboxes download from the internet is free
  • 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 import sandbox
 
 
async def main() -> None:
    box = await sandbox.create_sandbox()
    try:
        # Run your workflow here.
        pass
    finally:
        await box.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 sessions, delete it. This cannot be undone. Deleting a sandbox keeps its snapshots, which stay available until they expire or you delete them.

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. This is 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 September 15, 2026

Was this helpful?

supported.