sandbox について
sandboxは、サンドボックス関連の作業前に最新の`@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 との主な違いは次のとおりです。
- サンドボックスは
sandboxIdではなくnameで識別されます。名前はプロジェクトごとに一意です。 - サンドボックスはデフォルトで永続的です。サンドボックスが停止すると、SDK が自動的にスナップショットを作成し、次回の再開時にファイルシステムを復元します。
- セッションは、サンドボックス内で実行中の 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 のサンドボックスにはデータがバックフィルされるため、必要なコード変更は
sandboxIdの代わりにnameを使用することだけです。
クイックリファレンス
必須のインポート:
// 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 を使用します。これは Node.js 24、Bun、Python 3.14、コーディングエージェント、および一般的な開発・デバッグ用ユーティリティを備えた、Ubuntu ベースのイメージです。
サンドボックスの作成
基本的な作成
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 });
},
});
onResume は、SDK 呼び出し(例: runCommand、writeFiles)によって停止中のサンドボックスが自動再開される場合にも実行されます。
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 Container Registry(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 にプッシュしてください。プッシュ方法については、Container Registry のドキュメントをご覧ください。
既存のサンドボックスをフォークする
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()を読み取り専用でマウントできます。マウントごとに、マウント時点のドライブ内のファイルが取得されます。それ以降の書き込みを確認するには、新しいスナップショットをマウントしてください。ドライブで同時に使用できる読み書き可能なマウントは1つです。サンドボックスとドライブは同じリージョンに存在する必要があります。ドライブをフォークするか、そのスナップショットをマウントする前に、読み書き可能なマウントに一度書き込んでください。
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");
ルートアクセス
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側のみで機能し、単一のサンドボックス内で複数のエージェントやワークロードを分離するのに便利です。ユーザー名とグループ名は、コマンドインジェクションを防ぐために検証されます。
ユーザーの作成
createUserは、/home/<username>に分離されたホームディレクトリを持つLinuxユーザーを作成し、そのユーザーのコンテキストで操作を実行する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は、/shared/<groupname>に共有ディレクトリ(setgid 2770)を持つLinuxグループを作成します。そのため、ディレクトリ内に作成されたファイルは、自動的にグループの所有権を継承します。グループメンバーはそこで読み取りと書き込みができますが、メンバー以外はアクセスできません。
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);
}
セッション
セッションとは、サンドボックス内で実行されている単一の 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 および読み書き可能なドライブのマウントパス |
| リージョン | サンドボックスごとにプライマリリージョンを 1 つ、必要に応じてフェイルオーバーリージョンを追加できます。Vercel の 19 リージョンすべてに対応しています(デフォルトは 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、コーディングエージェント、および一般的な開発・デバッグ用ユーティリティが含まれています。
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 は、それぞれ 2 進単位のエイリアスです。--max-size を省略すると、既存のサイズを取得するか、新しいドライブにはプランのデフォルトサイズを使用します。出力には、サイズは作成時に固定されることを知らせるメッセージが含まれます。
エージェントのショートカットではユニバーサルイメージが使用され、対話型ターミナルが必要です。--image、--runtime、または --snapshot と併用しないでください。エージェントの認証は sandbox login とは別に設定してください。
エージェントを終了しても、サンドボックスは実行されたままです。--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"]);