Persistence

Persistence

Persistent sandboxes automatically save their filesystem state when stopped and restore it when resumed. You don't need to manually create or track snapshots between runs.

Persistence is the default. Every sandbox created with Sandbox.create() or sandbox create is persistent unless you explicitly opt out.

Each automatic snapshot consumes Snapshot Storage, which is billed separately from compute. For one-off or ephemeral workloads, pass persistent: false to Sandbox.create() (or --non-persistent to sandbox create) to opt out. See Opt out of persistence.

Persistent vs. non-persistent sandboxes

Persistence changes how a sandbox behaves when its current session stops. The table below summarizes the key differences between the two modes:

Aspect Persistent (default) Non-persistent
State on stop Automatically snapshotted Discarded unless manually snapshotted
Resuming Sandbox.get({ name }) or any SDK call auto-resumes Cannot be resumed; create a new sandbox
Identification User-defined name (unique per project) Same; name is still used
Snapshot management Automatic, handled by the SDK None
Typical use cases Developer environments, agent workspaces, long-lived jobs One-off CI tasks, build-and-discard workflows

Opt out of persistence

To opt out, pass persistent: false at creation time, or update the sandbox later with sandbox.update({ persistent: false }). From the CLI, pass --non-persistent to sandbox create. Non-persistent sandboxes discard their filesystem when the session stops and don't accrue Snapshot Storage costs.

const sandbox = await Sandbox.create({ persistent: false });

Key concepts

These are the building blocks you'll encounter when working with persistent sandboxes: the two-level sandbox/session model, sandbox names, snapshot retention, automatic resume, and lifecycle hooks.

Sandboxes and sessions

Persistent sandboxes use a two-level model:

When you stop a persistent sandbox, the SDK automatically snapshots the filesystem. When you resume it, a new session boots from that snapshot with a fresh session timeout. A sandbox is made up of many sessions, so the maximum duration caps each one, not the sandbox.

Sandbox names

Every sandbox has a name that is unique within your project. The name is the primary way to identify and retrieve sandboxes.

Default snapshot expiration and retention

You can set a default expiration for the automatic snapshots, plus a retention policy that keeps only the N most recent snapshots:

const sandbox = await Sandbox.create({
  name: 'my-sandbox',
  snapshotExpiration: 7 * 24 * 60 * 60 * 1000, // 7 days
  keepLastSnapshots: {
    count: 1, // Keep only the most recent snapshot
    expiration: 30 * 24 * 60 * 60 * 1000, // 30 days for kept snapshots
    deleteEvicted: true, // Delete evicted snapshots immediately
  },
});

keepLastSnapshots: { count: 1 } is the recommended setting when you only care about the latest snapshot. It keeps snapshot storage flat.

Sandbox retention

Vercel removes sandboxes that can't resume from a snapshot after 14 days of inactivity. This applies to non-persistent (ephemeral) sandboxes and to sandboxes whose source snapshot has been deleted or has expired. To keep a sandbox around, keep it persistent and make sure its snapshots don't expire before you use it again.

Automatic resume

If a persistent sandbox is stopped and you call runCommand, writeFiles, or other SDK methods on it, the SDK automatically starts a new session and retries the operation. You don't need to check the sandbox status or restart it manually.

Two methods do not auto-resume:

Lifecycle hooks

onCreate and onResume let you run setup code at the right moment:

Create a persistent sandbox

Call Sandbox.create() with a name to create a persistent sandbox. Persistence is on by default, so you only need to set name; the snippet below also sets a 7-day snapshot expiration:

import { Sandbox } from '@vercel/sandbox';

const sandbox = await Sandbox.create({
  name: 'my-sandbox',
  // `persistent: true` is the default. Pass `false` to opt out.
  snapshotExpiration: 7 * 24 * 60 * 60 * 1000, // 7 days
});

await sandbox.runCommand('npm', ['install']);
await sandbox.stop(); // Filesystem is snapshotted automatically

Get or create (idempotent)

Sandbox.getOrCreate is the recommended pattern for long-lived sandboxes. It resumes the sandbox if it exists, or creates it if it doesn't.

Creation parameters (such as keepLastSnapshots or snapshotExpiration) apply only when getOrCreate creates the sandbox. If a sandbox with the same name already exists, getOrCreate returns it with its existing configuration and ignores the creation parameters you pass. To change the configuration of an existing sandbox, use sandbox.update.

const sandbox = await Sandbox.getOrCreate({
  name: 'my-sandbox',
  onCreate: async (sbx) => {
    // Runs only the first time the sandbox is created
    await sbx.runCommand('git', ['clone', repoUrl, '.']);
    await sbx.runCommand('npm', ['install']);
  },
  onResume: async (sbx) => {
    // Runs every time the session resumes
    await sbx.runCommand({
      cmd: 'npm',
      args: ['run', 'dev'],
      detached: true,
    });
  },
});

Behavior:

Resume where you left off

Use Sandbox.get({ name }) to retrieve a persistent sandbox by name. The handle is returned immediately; the SDK starts a new session on the next call that needs a running VM:

// Retrieve the sandbox by name. The next SDK call resumes the session.
const sandbox = await Sandbox.get({ name: 'my-sandbox' });

// The filesystem is restored from the last session
await sandbox.runCommand('npm', ['run', 'dev']);

Pass resume: false to skip auto-resume. The sandbox resumes on the next SDK call that requires a running VM.

Update sandbox configuration

sandbox.update replaces individual update helpers and accepts any of the mutable parameters at once. When ports is provided, it is treated as the full desired port list; any currently exposed port not present in the array is deregistered.

await sandbox.update({
  resources: { vcpus: 4 }, // Memory auto-scales to 2048 MB per vCPU
  timeout: 30 * 60 * 1000, // 30 minutes
  persistent: true,
  snapshotExpiration: 14 * 24 * 60 * 60 * 1000, // 14 days
  keepLastSnapshots: { count: 1 },
  networkPolicy: 'deny-all',
  ports: [3000, 8000],
  tags: { env: 'production' },
  currentSnapshotId: 'snap_xyz', // Roll back to a previous snapshot
});

Delete a sandbox

Deleting a sandbox permanently removes the sandbox and all of its sessions. Its snapshots survive the deletion, because several sandboxes can start from the same snapshot. They stay available until they expire or you delete them, and they keep incurring storage charges in the meantime.

await sandbox.delete();

List and search sandboxes

Sandbox.list supports cursor-based pagination and returns an async-iterable that auto-paginates through every page.

const result = await Sandbox.list({
  namePrefix: 'user-a', // Filter by name prefix (requires sortBy: "name")
  tags: { env: 'production' }, // Filter by tags
  sortBy: 'createdAt', // "createdAt" (default), "name", or "statusUpdatedAt"
  sortOrder: 'desc', // "asc" or "desc" (default)
});

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

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

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

CLI usage

The sandbox CLI exposes the same persistent-sandbox primitives as the SDK. The sections below cover the most common commands; see the CLI reference for every option.

Create a persistent sandbox

Pass --name to sandbox create to give the sandbox a stable identifier. Optionally set --snapshot-expiration to control how long automatic snapshots live, or --non-persistent to opt out of persistence entirely:

# Create a persistent sandbox
sandbox create --name my-sandbox

# Create with a default snapshot expiration of 7 days
sandbox create --name my-sandbox --snapshot-expiration 7d

# Create with no snapshot expiration
sandbox create --name my-sandbox --snapshot-expiration none

# Create a non-persistent (ephemeral) sandbox
sandbox create --name my-sandbox --non-persistent

Run a command with automatic resume

If the sandbox is stopped, run resumes it before executing the command:

sandbox run --name my-sandbox -- npm test

Inspect sessions

List the sandbox sessions that have run inside the sandbox to audit lifecycle events or troubleshoot a stopped session:

sandbox sessions list my-sandbox

Configure a sandbox

Use sandbox config <subcommand> to inspect or update one parameter at a time. The full subcommand list is in the CLI reference; common ones look like this:

sandbox config list my-sandbox
sandbox config vcpus my-sandbox 4
sandbox config timeout my-sandbox 30m
sandbox config persistent my-sandbox true
sandbox config snapshot-expiration my-sandbox 7d
sandbox config keep-last-snapshots my-sandbox 1
sandbox config ports my-sandbox -p 3000 -p 8000
sandbox config tags my-sandbox --tag env=production

Delete a sandbox

sandbox remove permanently deletes the sandbox along with all of its sessions. The operation is irreversible. The sandbox's snapshots survive it, so delete them separately with sandbox snapshots delete:

sandbox remove my-sandbox

Migrating from @vercel/sandbox@1

Sandboxes created with v1 are backfilled with their old sandboxId as the new name, so the only required code change is using name instead of sandboxId:

// Before (v1)
const sandbox = await Sandbox.get({ sandboxId: 'sbx_123' });

// After (v2)
const sandbox = await Sandbox.get({ name: 'sbx_123' });

Other changes to be aware of:

Change Before (v1) After (v2)
Get a sandbox Sandbox.get({ sandboxId: "sbx_123" }) Sandbox.get({ name: "my-sandbox" })
Sandbox identifier sandbox.sandboxId sandbox.name
Default persistence Ephemeral (no auto-snapshot) Persistent (auto-snapshot on stop)
List pagination since / until parameters cursor-based pagination
List response { json: { sandboxes, pagination } } { sandboxes, pagination } (async-iterable)
Auto-resume Commands fail on stopped sandboxes Commands automatically resume the sandbox first
Update helpers updateNetworkPolicy sandbox.update({ networkPolicy, ... })

sandbox.updateNetworkPolicy() is deprecated. Use sandbox.update({ networkPolicy }) instead.

Managing persistent sandboxes in the dashboard

Persistent sandboxes appear in your project's Sandboxes page. Each sandbox shows its name, status, resources, and image.

Select a sandbox to view its detail page, which includes:

From the dashboard you can stop the current session or permanently remove the sandbox.