mcpField
S
sandbox

sandbox

github.com/vercel云与 DevOps216613 次安装今天更新
代码仓库

关于 sandbox

sandbox 是一项技能,要求编码 Agent 在处理沙箱任务前查阅最新的 `@vercel/sandbox` SDK 和 CLI 文档。其参考内容涵盖具名持久沙箱、会话、快照、文件、命令及相关 API。

主要功能

  1. 01提供官方 SDK 和 CLI 文档链接
  2. 02具名持久沙箱使用指引
  3. 03沙箱创建与获取方式
  4. 04命令执行和文件操作
  5. 05Git、tarball 和快照来源
  6. 06沙箱与 Drive 分叉

适用场景

  1. 01处理沙箱任务前核对 API 细节
  2. 02创建或恢复具名开发沙箱
  3. 03在沙箱中执行命令并管理文件
  4. 04为不同工作进程分叉沙箱环境

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel/sandbox --skill sandbox

安装后在 Claude Code 中输入/sandbox确认已加载。

详细介绍

关键提示:始终使用正确的 @vercel/sandbox 文档

你对 @vercel/sandbox 的了解可能已经过时。 开始任何与沙箱相关的任务前,请遵循以下说明:

官方资源
v2 中的变化

@vercel/sandbox@2 SDK 和 sandbox@3 CLI 用具名、持久化沙箱取代了匿名、临时沙箱。与 v1 相比,主要差异如下:

  • 沙箱通过 name(而非 sandboxId)标识。每个项目中的名称都必须唯一。
  • 沙箱默认是持久化的——沙箱停止时,SDK 会自动为其创建快照,并在下次恢复时还原文件系统。
  • **会话(Session)**是沙箱内单个正在运行的 VM 实例。runCommand、writeFiles 等 SDK 调用会自动恢复已停止的沙箱。
  • 新方法:Sandbox.getOrCreate、Sandbox.fork、sandbox.update、sandbox.delete、sandbox.listSessions、sandbox.listSnapshots、Snapshot.tree、defineSandboxProxy。
  • 新参数:name、persistent、tags、onResume、snapshotExpiration、keepLastSnapshots、L7 网络策略匹配器、forwardURL。
  • 分页使用基于游标的迭代器(异步可迭代),而非 since/until。
  • Sandbox.list({ since, until }) → Sandbox.list({ cursor, namePrefix, sortBy, tags })。
  • v1 沙箱已回填,因此唯一必需的代码更改是使用 name 替代 sandboxId。
快速参考

必要的导入:

// Core SDK
import {
  Sandbox,
  Session,
  Snapshot,
  Drive,
  Command,
  CommandFinished,
} from "@vercel/sandbox";
import { APIError, StreamError } from "@vercel/sandbox";

// For advanced network policy with credential brokering and L7 matchers
import type {
  NetworkPolicy,
  NetworkPolicyRule,
  NetworkTransformer,
} from "@vercel/sandbox";

// For implementing a request-forwarding proxy (forwardURL)
import { defineSandboxProxy } from "@vercel/sandbox/proxy";

// For timeouts
import ms from "ms"; // e.g., ms("5m"), ms("1h")

**默认镜像:**沙箱使用 vercel/sandbox/universal:latest,这是基于 Ubuntu 的镜像,包含 Node.js 24、Bun、Python 3.14、编码 Agent 以及常见的开发和调试工具。

创建沙箱

基本创建
import { Sandbox } from "@vercel/sandbox";

const sandbox = await Sandbox.create({
  name: "my-dev-env", // Optional, random if omitted. Unique per project.
  resources: { vcpus: 4 }, // 2048 MB RAM per vCPU
  ports: [3000], // Expose up to 15 ports
  timeout: ms("10m"), // Default: 5 minutes
  env: { NODE_ENV: "production" }, // Env vars inherited by all commands
  tags: { env: "staging", team: "infra" }, // Up to 5 key:value tags
  persistent: true, // Default: true. Auto-snapshots on stop, restores on resume.
  snapshotExpiration: ms("7d"), // Default TTL for snapshots. Use 0 for no expiration.
  region: "<region>", // Optional, defaults to iad1. Any Vercel region, e.g. sfo1, fra1, hnd1, syd1.
  failoverRegions: ["<region>"], // Optional, e.g. ["sfo1", "fra1"]. Must not include `region`.
});

console.log(sandbox.name);
获取现有沙箱
// Retrieve by name. The sandbox will resume automatically the next time
// you run a command.
const sandbox = await Sandbox.get({ name: "my-dev-env" });
获取或创建(幂等)

对于长期运行的沙箱,推荐使用 Sandbox.getOrCreate 模式。

const sandbox = await Sandbox.getOrCreate({
  name: "my-workspace",
  // Runs only the first time the sandbox is created.
  onCreate: async (sbx) => {
    await sbx.writeFiles([
      { path: "README.md", content: Buffer.from("# Hello") },
    ]);
    await sbx.runCommand("npm", ["install"]);
  },
  // Runs every time the sandbox session is resumed (including after auto-resume).
  onResume: async (sbx) => {
    await sbx.runCommand({ cmd: "npm", args: ["run", "dev"], detached: true });
  },
});

行为:

  • 如果存在具有该 name 的沙箱 → 恢复该沙箱并触发 onResume。
  • 如果不存在 → 创建全新的沙箱并触发 onCreate。
  • 如果沙箱存在,但其快照已过期 → 删除过期沙箱,使用相同名称重新创建,并触发 onCreate。
恢复时重新预热

使用 onResume,在恢复持久化沙箱的会话时重新启动后台服务或恢复缓存:

const sandbox = await Sandbox.get({
  name: "my-workspace",
  onResume: async (sbx) => {
    await sbx.runCommand({ cmd: "npm", args: ["run", "dev"], detached: true });
  },
});

SDK 调用(例如 runCommand、writeFiles)自动恢复已停止的沙箱时,也会触发 onResume。

使用 Git 源
const sandbox = await Sandbox.create({
  source: {
    type: "git",
    url: "https://github.com/vercel/sandbox-example-next.git",
    depth: 1, // Shallow clone (optional)
    revision: "main", // Branch, tag, or commit (optional)
  },
  ports: [3000],
});

Git 源会克隆到以仓库命名的子目录中。对于上面的示例,运行命令时使用 cwd: "sandbox-example-next"。

使用私有 Git 仓库
const sandbox = await Sandbox.create({
  source: {
    type: "git",
    url: "https://github.com/org/private-repo.git",
    username: process.env.GIT_USERNAME!,
    password: process.env.GIT_TOKEN!, // Use PAT for password
  },
});
使用 Tarball
const sandbox = await Sandbox.create({
  source: {
    type: "tarball",
    url: "https://example.com/project.tar.gz",
  },
  ports: [3000],
});
使用快照
const sandbox = await Sandbox.create({
  source: {
    type: "snapshot",
    snapshotId: "snap_abc123",
  },
  ports: [3000],
});
使用自定义镜像(VCR)

使用存储在沙箱所属项目中的 Vercel 容器注册表 (VCR)镜像覆盖默认镜像。

如果需要使用其他发行版或可复现的系统工具,建议将其预先构建到自定义镜像中,而不是在沙箱启动时安装软件包。

const sandbox = await Sandbox.create({
  image: "my-repo:v1",
  ports: [3000],
});

image 接受沙箱所属项目中的仓库(可选附带标签或摘要),也接受完整限定的 VCR URL。仅使用仓库名称时,将解析为 latest 标签:

await Sandbox.create({ image: "my-repo" }); // latest tag
await Sandbox.create({ image: "my-repo:v1" }); // specific tag
await Sandbox.create({ image: "my-repo@sha256:..." }); // specific digest
await Sandbox.create({
  image: "vcr.vercel.com/my-team/my-project/my-repo:v1", // fully-qualified
});

引用镜像前,请使用与 Docker 兼容的工具将其推送到 VCR。推送说明请参阅容器注册表文档。

派生现有沙箱

Sandbox.fork 会根据另一个沙箱的当前快照创建新沙箱, 并复制其配置(resources、timeout、networkPolicy、tags、 ports、image、persistent、snapshotExpiration、keepLastSnapshots 和 env)。你传入的任何字段都会覆盖继承的值。如果源沙箱没有当前快照, 派生操作会回退到源沙箱的基础环境以及复制的配置。你只能在自己有权访问的 项目中派生沙箱;派生未知的源沙箱会返回 404。派生的沙箱会在源沙箱所在的 区域运行,除非你传入 region(以及可选的 failoverRegions)进行覆盖。

// Inherit everything from the source (env included)
const fork = await Sandbox.fork({ sourceSandbox: "prod-agent" });

// Override specific fields; the rest are copied from the source.
// A provided `env` fully replaces the source's env (no per-key merge).
const fork = await Sandbox.fork({
  sourceSandbox: "prod-agent",
  name: "forked-prod-agent",
  resources: { vcpus: 4 },
  env: { OPENAI_API_KEY: process.env.OPENAI_API_KEY! },
});
何时派生沙箱或驱动器

如果每个工作器都需要独立的沙箱环境,包括源沙箱快照中已安装的工具和文件, 请使用 Sandbox.fork。如果每个工作器都需要持久文件的独立可写副本,请使用 drive.fork。Sandbox.fork 不会继承驱动器挂载;如果派生的沙箱需要驱动器, 请传入 mounts。 如果文件需要在多次沙箱运行之间持久保存,请挂载驱动器。多个沙箱可以挂载 drive.snapshot() 以只读方式访问;每次挂载都会捕获挂载时驱动器中的文件。 要查看之后的写入,请挂载新的快照。驱动器一次只能有一个读写挂载。沙箱和 驱动器必须位于同一区域。在派生驱动器或挂载其快照之前,请先对读写挂载执行 一次写入。

import { Drive, Sandbox } from "@vercel/sandbox";

const baseDrive = await Drive.getOrCreate({ name: "agent-base-files" });
const seed = await Sandbox.create({
  region: baseDrive.region,
  mounts: { "/workspace": baseDrive },
});
await seed.writeFiles([
  { path: "/workspace/README.md", content: "starter" },
]);
await seed.stop();

// The source drive has now been written to and can be forked.
const workerDrive = await baseDrive.fork({ name: "agent-worker-1-files" });
const worker = await Sandbox.create({
  region: workerDrive.region,
  mounts: { "/workspace": workerDrive },
});
await worker.writeFiles([
  { path: "/workspace/result.txt", content: "worker" },
]);
await worker.stop();

工作器的写入会保留在 workerDrive 中;基础驱动器则保留初始文件。要为另一个 工作器创建副本,请使用不同的名称再次派生基础驱动器。

自动释放模式

使用 await using 自动清理:

async function runInSandbox() {
  await using sandbox = await Sandbox.create();
  // Sandbox automatically stopped when scope exits
  await sandbox.runCommand("echo", ["Hello"]);
}

运行命令

基本命令执行
const result = await sandbox.runCommand("npm", ["install"]);
if (result.exitCode !== 0) {
  console.error("Install failed:", await result.stderr());
}
使用选项
const result = await sandbox.runCommand({
  cmd: "npm",
  args: ["run", "build"],
  cwd: "/vercel/sandbox/app",
  env: { NODE_ENV: "production" },
  sudo: false,
  stdout: process.stdout, // Stream output
  stderr: process.stderr,
});
分离运行命令(后台进程)
// Start dev server in background
const devServer = await sandbox.runCommand({
  cmd: "npm",
  args: ["run", "dev"],
  detached: true, // Returns immediately
  stdout: process.stdout,
});

// Later: wait for completion or kill
const finished = await devServer.wait();
// Supported signals: SIGHUP, SIGINT, SIGQUIT, SIGKILL, SIGTERM, SIGCONT, SIGSTOP (or numeric)
await devServer.kill("SIGTERM");
Root 访问权限
await sandbox.runCommand({
  cmd: "apt-get",
  args: ["update"],
  sudo: true,
});
await sandbox.runCommand({
  cmd: "apt-get",
  args: ["install", "-y", "golang-go"],
  sudo: true, // Execute as root
});

文件操作

写入文件
await sandbox.writeFiles([
  {
    path: "/vercel/sandbox/config.json",
    content: Buffer.from(JSON.stringify({ key: "value" })),
  },
  {
    path: "/vercel/sandbox/script.sh",
    content: Buffer.from("#!/bin/bash\necho 'Hello'"),
    mode: 0o755,
  },
]);
读取文件
// Returns a Buffer object
const buffer = await sandbox.readFileToBuffer({
  path: "/vercel/sandbox/output.txt",
});

// Returns a NodeJS.ReadableStream
const stream = await sandbox.readFile({
  path: "/vercel/sandbox/large-file.bin",
});
下载文件
const localPath = await sandbox.downloadFile(
  { path: "/vercel/sandbox/report.pdf" }, // source path on the sandbox
  { path: "./downloads/report.pdf" }, // destination path on the local machine
  { mkdirRecursive: true },
);
创建目录
await sandbox.mkDir("/vercel/sandbox/my-app/src");
sandbox.fs — 兼容 node:fs/promises 的 API
const content = await sandbox.fs.readFile("/etc/hostname", "utf8");
await sandbox.fs.writeFile("/tmp/hello.txt", "Hello, world!");
const files = await sandbox.fs.readdir("/tmp");
const stats = await sandbox.fs.stat("/tmp/hello.txt");

多用户和群组

在沙箱内创建隔离的 Linux 用户和共享群组。此功能完全由 SDK 端实现,适用于在 单个沙箱中隔离多个 Agent 或工作负载。系统会验证用户名和群组名称,以防止 命令注入。

创建用户

createUser 会创建一个 Linux 用户,并在 /home/<username> 下为其创建隔离的 主目录,同时返回一个 SandboxUser,其操作会在该用户的上下文中运行。

const alice = await sandbox.createUser("alice");
alice.username; // "alice"
alice.homeDir; // "/home/alice"

// Commands run as alice; cwd defaults to /home/alice
const whoami = await alice.runCommand("whoami");
await whoami.stdout(); // "alice\n"

// Env vars, custom cwd, sudo escalation, and detached mode all work
await alice.runCommand({
  cmd: "node",
  args: ["-e", "console.log(process.env.SECRET)"],
  env: { SECRET: "hunter2" },
});
await alice.runCommand({
  cmd: "apt-get",
  args: ["update"],
  sudo: true,
});
await alice.runCommand({
  cmd: "apt-get",
  args: ["install", "-y", "git"],
  sudo: true,
});
const server = await alice.runCommand({
  cmd: "node",
  args: ["server.js"],
  detached: true,
});

获取现有用户(例如 root)的句柄,而不创建该用户:

const existing = sandbox.asUser("bob");
await existing.runCommand("whoami");
限定到用户的文件操作

相对路径会相对于用户的主目录解析,文件也归该用户所有。也支持绝对路径。

// Written to /home/alice, owned by alice:alice
await alice.writeFiles([
  { path: "app.js", content: Buffer.from('console.log("hi")') },
  { path: "data/config.json", content: Buffer.from("{}") },
]);

const buf = await alice.readFileToBuffer({ path: "app.js" });
const stream = await alice.readFile({ path: "app.js" });
await alice.mkDir("projects/my-app");

用户之间的文件相互隔离——一个用户无法读取、列出或写入另一个用户的主目录 (命令会返回非零退出代码,并显示“Permission denied”)。

群组和共享目录

createGroup 会创建一个 Linux 群组,并在 /shared/<groupname> 下创建共享目录 (setgid 2770),使其中创建的文件自动继承群组所有权。群组成员可以在该目录 中读写;非成员则会被拒绝访问。

const devs = await sandbox.createGroup("devs");
devs.sharedDir; // "/shared/devs"

await sandbox.addUserToGroup("alice", "devs");
await sandbox.addUserToGroup("bob", "devs");

// Or via convenience methods on SandboxUser
await alice.addToGroup("devs");
await alice.removeFromGroup("devs");

await sandbox.removeUserFromGroup("alice", "devs");

网络策略

完全访问互联网(默认)
const sandbox = await Sandbox.create({
  networkPolicy: "allow-all",
});
无网络访问权限
const sandbox = await Sandbox.create({
  networkPolicy: "deny-all",
});
限制访问(简单域名列表)
const sandbox = await Sandbox.create({
  networkPolicy: {
    allow: ["*.npmjs.org", "github.com", "registry.yarnpkg.com"],
    subnets: {
      allow: ["10.0.0.0/8"],
      deny: ["10.1.0.0/16"], // Takes precedence over allowed
    },
  },
});
使用凭据代理限制访问
const sandbox = await Sandbox.create({
  networkPolicy: {
    allow: {
      "ai-gateway.vercel.sh": [
        {
          transform: [
            {
              headers: { authorization: "Bearer ..." },
            },
          ],
        },
      ],
      "*": [], // Allow all other domains without transforms
    },
  },
});
L7 请求匹配器

规则可以根据方法、路径、查询字符串和标头进行匹配。所有指定的维度都必须 匹配;多个方法之间为 OR 关系;多个标头和查询字符串匹配器之间为 AND 关系。

const sandbox = await Sandbox.create({
  networkPolicy: {
    allow: {
      "ai-gateway.vercel.sh": [
        {
          match: {
            method: ["POST"],
            path: { startsWith: "/v1/" },
            headers: [
              { key: { exact: "x-api-key" }, value: { exact: "placeholder" } },
            ],
          },
          transform: [{ headers: { authorization: "Bearer ..." } }],
        },
      ],
    },
  },
});

匹配器支持 exact、startsWith 和 regex(RE2)。

将匹配的请求转发到代理

使用 forwardURL,通过你控制的 HTTPS 代理重定向任何匹配的请求。代理会收到原始请求,以及转发标头中的沙箱元数据。

const sandbox = await Sandbox.create({
  networkPolicy: {
    allow: {
      "api.example.com": [
        {
          match: { path: { startsWith: "/secure/" } },
          forwardURL: "https://my-proxy.example.com",
        },
      ],
    },
  },
});

使用 defineSandboxProxy 实现代理处理程序,并使用 Web Request 和 Response 对象——它会验证沙箱 OIDC 令牌,并提取源沙箱的元数据:

// app/api/sandbox-proxy/route.ts
import { defineSandboxProxy } from "@vercel/sandbox/proxy";

const handler = defineSandboxProxy(async (request, meta) => {
  // meta: { host, teamId, projectId, sandboxId, sandboxName }
  console.log("Proxied from sandbox", meta.sandboxName);
  return fetch(request);
});

// Sandboxes forward requests using their original method, so the handler
// must be exposed under every verb the network policy can route.
export {
  handler as GET,
  handler as POST,
  handler as PUT,
  handler as PATCH,
  handler as DELETE,
};
在运行时更新网络策略

使用 sandbox.update(推荐)。updateNetworkPolicy 已弃用,但仍可使用。

await sandbox.update({ networkPolicy: { allow: ["api.openai.com"] } });

更新沙箱配置

sandbox.update 取代了单项更新辅助函数,并接受任何可变参数。提供 ports 时,它会被视为期望的完整端口列表——当前已公开但未出现在数组中的端口将被注销。

await sandbox.update({
  resources: { vcpus: 4 }, // Memory auto-scales to 2048 MB per vCPU
  timeout: ms("30m"),
  networkPolicy: "deny-all",
  ports: [3000, 8000],
  tags: { env: "prod" },
  persistent: false,
  snapshotExpiration: ms("14d"),
  keepLastSnapshots: { count: 1 },
  currentSnapshotId: "snap_xyz", // Rollback to a previous snapshot
  region: "sfo1", // Applies to the next session
  failoverRegions: ["cle1"], // Replaces the list; pass [] to remove them
});

可以分别更新 region 和 failoverRegions,但 failoverRegions 不得包含已配置的 region。

删除沙箱

// Permanently remove a sandbox. Its snapshots are kept until they expire.
await sandbox.delete();

// Also delete the snapshots that no other sandbox uses.
await sandbox.delete({ deleteOrphanSnapshots: true });

停止沙箱

stop() 是同步的:它会阻塞,直到 VM 完全停止,并返回最终会话状态,其中包括关机期间创建的快照(当 persistent: true 时)。

const result = await sandbox.stop();
console.log(result.snapshot?.id);
console.log(result.activeCpuUsageMs);
console.log(result.networkTransfer); // { ingress, egress }

标签

沙箱最多支持 5 个键值标签。可以在创建时设置标签,通过 sandbox.update({ tags }) 更新标签,并在列表查询时将其用作筛选条件。

await Sandbox.create({ tags: { env: "staging", team: "infra" } });

const result = await Sandbox.list({ tags: { env: "staging" } });

列出沙箱、会话和快照

所有列表 API 均使用基于游标的分页,并返回可自动迭代所有页面的异步可迭代对象。你也可以逐页迭代,或一次性收集所有条目。

Sandbox.list
const result = await Sandbox.list({
  namePrefix: "ci-", // Filter by name prefix
  tags: { env: "staging" }, // Filter by tags
  sortBy: "createdAt", // "createdAt" (default), "name", or "statusUpdatedAt"
  sortOrder: "desc", // "asc" or "desc" (default)
  limit: 50,
});

// Per-item async iteration (auto-paginates)
for await (const sandbox of result) {
  console.log(sandbox.name);
}

// Per-page iteration
for await (const page of result.pages()) {
  console.log(page.sandboxes.length);
}

// Collect everything
const all = await result.toArray();

// Or use the cursor directly
const next = result.pagination.next;
sandbox.listSessions 和 sandbox.listSnapshots
// List all VM sessions for this sandbox
const sessions = await sandbox.listSessions();
for await (const session of sessions) {
  console.log(session.sessionId, session.status);
}

// List snapshots belonging to this sandbox
const snapshots = await sandbox.listSnapshots();
for await (const snapshot of snapshots) {
  console.log(snapshot.snapshotId, snapshot.status);
}
Snapshot.list
const snapshots = await Snapshot.list({
  name: "my-dev-env", // Filter by sandbox name
  sortOrder: "desc",
  limit: 50,
});
for await (const snapshot of snapshots) {
  console.log(snapshot.snapshotId, snapshot.status);
}

会话

**会话(Session)**是沙箱内单个正在运行的 VM 实例。通常不需要直接与会话交互——SDK 会为你创建并恢复会话——但你可以检查当前会话。

const session = sandbox.currentSession();
console.log(session.sessionId);
console.log(session.status); // "pending" | "running" | "stopping" | "stopped" | ...

快照

快照会保存整个沙箱文件系统,以便日后供任意数量的沙箱重复使用。

快照存储在沙箱所在的区域;快照可用的所有区域都列在 snapshot.regions 中。如果在快照不可用的区域中从该快照创建沙箱,操作会失败并返回 snapshot_region_mismatch 错误。

创建快照
const sandbox = await Sandbox.create();
await sandbox.runCommand("npm", ["install"]);

// Create snapshot (stops the sandbox)
const snapshot = await sandbox.snapshot({
  expiration: ms("14d"), // Default: 30 days, use 0 for no expiration
});
console.log("Snapshot ID:", snapshot.snapshotId);
默认快照过期时间和保留策略

为每个沙箱配置默认过期时间和保留策略:

await Sandbox.create({
  name: "my-app",
  snapshotExpiration: ms("7d"), // Default TTL for any snapshot of this sandbox
  keepLastSnapshots: {
    count: 1, // Keep only the most recent snapshot (1-10)
    expiration: ms("30d"), // Override expiration for kept snapshots
    deleteEvicted: true, // Delete evicted snapshots immediately (default)
  },
});

如果你只关心最新快照,建议设置 keepLastSnapshots: { count: 1 }——这样 SDK 可以将快照存储成本保持在固定水平。

列出、获取和删除
// List all snapshots in the project (auto-paginates)
const snapshots = await Snapshot.list();
for await (const snapshot of snapshots) {
  console.log(snapshot.snapshotId, snapshot.status);
}

// Get a specific snapshot
const snapshot = await Snapshot.get({ snapshotId: "snap_abc123" });

// Delete a snapshot
await snapshot.delete();
快照树

快照构成一棵树:从某个快照创建的任何沙箱都会继承父子关系。遍历这棵树,可以查看指定快照的祖先或后代。

// Walk ancestors (default direction)
const ancestors = await Snapshot.tree({
  snapshotId: "snap_abc",
  sortOrder: "desc",
});
for await (const node of ancestors) {
  console.log(node.snapshotId, node.parentId);
}

// Walk descendants
const descendants = await Snapshot.tree({
  snapshotId: "snap_abc",
  sortOrder: "asc",
});
快照回滚

通过更新 currentSnapshotId,将现有沙箱指向之前的快照。新会话会从该快照恢复。

await sandbox.update({ currentSnapshotId: "snap_previous" });

已公开端口

const sandbox = await Sandbox.create({ ports: [3000, 8000] });

// Get public URL for a port
const url = sandbox.domain(3000);
// Returns: https://subdomain.vercel.run

使用 sandbox.update({ ports }) 在运行时替换已公开端口列表。当前已公开但未出现在新数组中的端口将被注销。

await sandbox.update({ ports: [3000, 8443] });

超时管理

const sandbox = await Sandbox.create({
  timeout: ms("10m"), // Initial timeout, default of 5 minutes
});

// Extend timeout by 5 more minutes
await sandbox.extendTimeout(ms("5m"));
// New total: 15 minutes

身份验证

Vercel OIDC 令牌(推荐)
# Pull development credentials
vercel link
vercel env pull

SDK 会自动使用环境变量中的 VERCEL_OIDC_TOKEN。

访问令牌(替代方式)
const sandbox = await Sandbox.create({
  teamId: process.env.VERCEL_TEAM_ID!,
  projectId: process.env.VERCEL_PROJECT_ID!,
  token: process.env.VERCEL_TOKEN!,
  // ... other options
});

错误处理

import { APIError, StreamError } from "@vercel/sandbox";

try {
  const sandbox = await Sandbox.create();
} catch (error) {
  if (error instanceof APIError) {
    console.error("API Error:", error.message, error.response.status);
  } else if (error instanceof StreamError) {
    console.error("Stream Error:", error.message);
  }
  throw error;
}

使用 AbortSignal 取消

const controller = new AbortController();

// Cancel after 30 seconds
setTimeout(() => controller.abort(), 30000);

const sandbox = await Sandbox.create({
  signal: controller.signal,
});

const result = await sandbox.runCommand({
  cmd: "npm",
  args: ["test"],
  signal: controller.signal,
});

限制

限制详情
最大 vCPU 数Hobby 套餐为 4 个 vCPU,Pro 套餐为 8 个 vCPU,Enterprise 套餐为 32 个 vCPU(每个 vCPU 配备 2048 MB RAM)
最大端口数15 个公开端口
最大标签数每个沙盒最多 5 个键值标签
最大超时时间24 小时(Pro/Enterprise),45 分钟(Hobby)
默认超时时间5 分钟
基础系统Ubuntu 26.04
用户上下文ubuntu 用户
可写路径/vercel/sandbox 和读写驱动器挂载路径
区域每个沙盒有一个主区域,以及可选的故障转移区域。支持全部 19 个 Vercel 区域(默认值为 iad1,还包括 sfo1、cle1、cdg1、fra1、arn1、sin1、pdx1、lhr1、icn1、bom1、cpt1、dub1、gru1、hkg1、syd1、yul1、hnd1、kix1)。快照仅能在其可用的区域中恢复。

系统软件包

默认镜像包含 Node.js 24、Bun、Python 3.14、pnpm、uv、Git、GitHub CLI、编码 Agent,以及常见的开发和调试工具。

使用 sudo 安装其他软件包:

await sandbox.runCommand({
  cmd: "apt-get",
  args: ["update"],
  sudo: true,
});
await sandbox.runCommand({
  cmd: "apt-get",
  args: ["install", "-y", "package-name"],
  sudo: true,
});

临时使用时,这样做没有问题。若要使用可复现的依赖项或不同的基础 环境,请改用自定义镜像。

CLI 快速参考

# Install CLI
pnpm i -g sandbox

# Login / Logout
sandbox login
sandbox logout

# Create and connect
sandbox create --connect
sandbox create --name my-app
sandbox create --image my-repo:v1            # Boot from a VCR image
sandbox create --non-persistent              # Disable filesystem persistence
sandbox create --snapshot-expiration 7d      # Default snapshot TTL
sandbox create --keep-last-snapshots 1       # Retention policy
sandbox create --tag env=staging             # Repeatable
sandbox create --region <region>             # Defaults to iad1; any Vercel region, e.g. sfo1, fra1, hnd1, syd1
sandbox create --failover-regions <region>,<region>  # Comma-separated
sandbox create --failover-regions none               # No failover, overrides the project default

# Start a coding agent (interactive terminal required)
sandbox create opencode
sandbox create pi
sandbox create claude
sandbox create codex

# Fork an existing sandbox (inherits config, incl. env; --env replaces it)
sandbox fork <source>
sandbox fork <source> --name my-fork --vcpus 4 --env FOO=1
sandbox fork <source> --region <region>      # Override the region copied from the source

# List sandboxes (paginated, filterable)
sandbox ls
sandbox ls --name-prefix ci- --sort-by name
sandbox ls --tag env=staging --limit 100 --cursor <token>

# Run a command in a new sandbox (create + exec in one step)
sandbox run -- node -e "console.log('hello')"
sandbox run --name my-app -- npm test        # Resumes existing sandbox if present
sandbox run --stop -- npm build              # Stop the session when the command exits
sandbox run --rm -- npm build                # DELETES the sandbox after running

# Execute command in an existing sandbox
sandbox exec <name> -- npm install
sandbox exec <name> --stop -- npm build

# Start an interactive shell
sandbox connect <name>

# Copy files
sandbox cp local-file.txt <name>:/vercel/sandbox/

# Add time to the current running session without changing future defaults
sandbox extend <name> <duration>

# Stop sandbox (synchronous; reports snapshot + usage)
sandbox stop <name>

# Permanently delete sandbox; its snapshots are kept until they expire
sandbox remove <name>
sandbox remove <name> --delete-orphan-snapshots  # Also delete snapshots no other sandbox uses

# Sessions
sandbox sessions list <name>

# Snapshots
sandbox snapshot <name>
sandbox snapshots list --name <name>
sandbox snapshots get <snapshot-id>
sandbox snapshots remove <snapshot-id>
sandbox snapshots tree <name>                # Walk the tree from the sandbox's current snapshot
sandbox snapshots tree <name> --cursor <snapshot-id> --sort-order asc

# Create or retrieve a drive; size is fixed at creation
sandbox drives get-or-create workspace --max-size 200GiB
sandbox drives get-or-create archive --max-size 2TiB
sandbox drives get-or-create workspace --max-size 214748364800  # Raw bytes

# Config (view + update any sandbox parameter)
sandbox config list <name>
sandbox config vcpus <name> <count>
sandbox config timeout <name> <duration>  # Total runtime; a higher value also extends the running session
sandbox config persistent <name> <true|false>
sandbox config region <name> <region>
sandbox config failover-regions <name> <region,...|none>
sandbox config snapshot-expiration <name> <duration|none>
sandbox config keep-last-snapshots <name> <count>
sandbox config keep-last-snapshots-for <name> <duration|none>
sandbox config delete-evicted-snapshots <name> <true|false>
sandbox config current-snapshot <name> <snapshot-id>
sandbox config network-policy <name> --network-policy deny-all
sandbox config tags <name> --tag env=prod

驱动器大小可以用字节或 GiB、TiB 等单位表示。单位名称 不区分大小写;GB 和 TB 是二进制单位的别名。省略 --max-size 时,会获取现有大小;对于新驱动器,则使用套餐默认值。 输出中会提醒你,大小在创建时确定。

Agent 快捷方式使用通用镜像,并且需要交互式终端。 请勿将它们与 --image、--runtime 或 --snapshot 一起使用。 Agent 的身份验证需与 sandbox login 分开配置。

退出 Agent 后,沙盒仍会继续运行。除非传入 --silent,否则 CLI 会打印重新连接和停止命令。请遵循重新连接命令旁打印的所有环境变量提示。

常见模式

开发服务器模式(持久运行)
const sandbox = await Sandbox.getOrCreate({
  name: "my-dev-env",
  source: { type: "git", url: "https://github.com/org/repo.git" },
  ports: [3000],
  timeout: ms("30m"),
  onCreate: async (sbx) => {
    await sbx.runCommand({ cmd: "npm", args: ["install"], cwd: "repo" });
  },
  onResume: async (sbx) => {
    await sbx.runCommand({
      cmd: "npm",
      args: ["run", "dev"],
      cwd: "repo",
      detached: true,
    });
  },
});

console.log("App running at:", sandbox.domain(3000));
构建和测试模式(临时运行)
const repoUrl = "https://github.com/org/repo.git";
const repoDir = "repo";

await using sandbox = await Sandbox.create({
  source: { type: "git", url: repoUrl },
  persistent: false, // Skip snapshotting on shutdown
  snapshotExpiration: ms("1d"), // Short TTL for any incidental snapshot
});

const install = await sandbox.runCommand({
  cmd: "npm",
  args: ["ci"],
  cwd: repoDir,
});
if (install.exitCode !== 0) throw new Error("Install failed");

const build = await sandbox.runCommand({
  cmd: "npm",
  args: ["run", "build"],
  cwd: repoDir,
});
if (build.exitCode !== 0) throw new Error("Build failed");

const test = await sandbox.runCommand({
  cmd: "npm",
  args: ["test"],
  cwd: repoDir,
});
process.exit(test.exitCode);
基础沙盒 + 分叉模式

维护一个已安装依赖项的“基础”沙盒,并使用 Sandbox.fork 从中生成新的子沙盒。 每个分叉都会继承基础沙盒的配置,并以其当前快照为初始状态,因此无需在 代码中存储快照 ID。新的基础沙盒快照会在下一次分叉时自动采用。

// Once: bootstrap the base sandbox
await Sandbox.getOrCreate({
  name: "my-base",
  keepLastSnapshots: { count: 5 }, // Keep storage flat
  onCreate: async (sbx) => {
    await sbx.runCommand("npm", ["install", "-g", "typescript", "tsx"]);
  },
});

// On every run: fork the base sandbox
async function runFromBase(code: string) {
  await using sandbox = await Sandbox.fork({
    sourceSandbox: "my-base",
    persistent: false,
  });
  await sandbox.writeFiles([
    { path: "/vercel/sandbox/index.ts", content: Buffer.from(code) },
  ]);
  return sandbox.runCommand("tsx", ["index.ts"]);
}
长期工作区模式
// Idempotent: first call creates, subsequent calls resume
const sandbox = await Sandbox.getOrCreate({
  name: `workspace-${userId}`,
  keepLastSnapshots: { count: 1, expiration: ms("5d") },
  onCreate: async (sbx) => {
    await sbx.runCommand("git", ["clone", repoUrl, "."]);
    await sbx.runCommand("npm", ["install"]);
  },
});

// Use the sandbox — auto-resumes if it was stopped
await sandbox.runCommand("git", ["pull"]);

评论

支持 Markdown