关于 sandbox
sandbox 是一项技能,要求编码 Agent 在处理沙箱任务前查阅最新的 `@vercel/sandbox` SDK 和 CLI 文档。其参考内容涵盖具名持久沙箱、会话、快照、文件、命令及相关 API。
主要功能
- 01提供官方 SDK 和 CLI 文档链接
- 02具名持久沙箱使用指引
- 03沙箱创建与获取方式
- 04命令执行和文件操作
- 05Git、tarball 和快照来源
- 06沙箱与 Drive 分叉
适用场景
- 01处理沙箱任务前核对 API 细节
- 02创建或恢复具名开发沙箱
- 03在沙箱中执行命令并管理文件
- 04为不同工作进程分叉沙箱环境
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vercel/ sandbox --skill sandbox安装后在 Claude Code 中输入/sandbox确认已加载。
详细介绍
关键提示:始终使用正确的 @vercel/sandbox 文档
你对 @vercel/sandbox 的了解可能已经过时。
开始任何与沙箱相关的任务前,请遵循以下说明:
官方资源
- 文档:https://vercel.com/docs/vercel-sandbox
- SDK 参考:https://vercel.com/docs/vercel-sandbox/sdk-reference
- CLI 参考:https://vercel.com/docs/vercel-sandbox/cli-reference
- GitHub:https://github.com/vercel/sandbox
- REST API:https://vercel.com/docs/rest-api/sandboxes
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"]);