---
title: Vercel Sandbox
description: Create persistent sandboxes on Vercel, from a prepared snapshot when the environment has build-time setup.
url: "https://eve.dev/docs/sandbox/vercel"
docs_index: /llms.txt
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`VercelSandbox.environment()` runs each eve session in its own Vercel Sandbox, which keeps its files between turns. When the environment has build-time setup, eve prepares a reusable snapshot and starts session sandboxes from it.

```ts title="agent/sandbox.ts"
import { defineSandbox } from "eve/sandbox";
import { VercelSandbox } from "eve/sandbox/vercel";

export const environment = VercelSandbox.environment({
  // optional: setup captured in the snapshot that every session starts from
  prepare: async (sandbox) => {
    await sandbox.run({ command: "npm ci" });
  },
});

export default defineSandbox(() =>
  // optional: live sandbox options, applied when eve creates the sandbox
  environment.open({
    networkPolicy: {
      allow: {
        "api.github.com": [],
      },
    },
    resources: { vcpus: 4 },
  }),
);
```

## Authentication

eve needs a Vercel token, team ID, and project ID. `eve build` uses them to capture the snapshot, and the runtime uses them to create and resume session sandboxes. Deployments on Vercel provide them automatically. eve resolves each value from the first source that is set:

| Value      | Sources, in order                                               |
| ---------- | --------------------------------------------------------------- |
| Token      | `token` environment option, `VERCEL_OIDC_TOKEN`, `VERCEL_TOKEN` |
| Team ID    | `teamId` environment option, `VERCEL_TEAM_ID`, `VERCEL_ORG_ID`  |
| Project ID | `projectId` environment option, `VERCEL_PROJECT_ID`             |

When any of the three is unset, eve uses a Vercel OIDC token and reads the team and project from it. Outside a Vercel deployment, eve reads `VERCEL_OIDC_TOKEN`. It refreshes a missing or expired token with your Vercel CLI login and the project linked in `.vercel/project.json`. If that fails, the Vercel Sandbox SDK's own credential lookup runs, and it reports an authentication error when it finds none.

For local development, link the app to a Vercel project and pull its environment into `.env.local`:

```bash
eve link
```

`vercel link` followed by `vercel env pull` produces the same `.env.local`. A running `eve dev` reloads env files automatically.

Outside Vercel and without a linked project, such as in CI or on a self-hosted server, set `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, and `VERCEL_PROJECT_ID`. You can also pass `token`, `teamId`, and `projectId` to `VercelSandbox.environment()`.

## Live sandbox options

`open()` accepts Vercel compute resources, timeout, environment variables, network policy, and Drive mounts. They apply only when eve creates the sandbox. Reopening the sandbox or resuming it keeps the original environment variables, mounts, and network policy. Pass `env` to `run()` or `spawn()` to override a variable for one command, or call `setNetworkPolicy()` to change the live policy.

Set `image`, `source`, `tags`, project credentials, and `fetch` on the environment, because `open()` doesn't accept them. eve sets the sandbox's name and persistence itself.

If initialization fails, eve keeps the sandbox and runs the selector against it again on the next access. If the named Vercel Sandbox later disappears, eve runs the selector again to start a fresh sandbox.

## Snapshot preparation

During build, eve installs its base runtime in a temporary Vercel Sandbox, writes managed workspace and skill files, and runs the authored `prepare` callback. A `prepare` callback that breaks the base runtime fails the build. eve creates each new session sandbox from the captured snapshot.

eve skips the snapshot when the environment has no `prepare` callback, managed workspace or skill files, or `source`. Each new session sandbox then starts from the image and installs the base runtime itself. A `source` always produces a snapshot, so sessions start from what the build captured instead of fetching the source again.

A new session sandbox from a snapshot of [eve's default image](/docs/sandbox/docker#eves-default-image), without Drive mounts, runs no setup command. eve runs base-runtime setup when it attaches any other sandbox. These include existing or resumed sandboxes, custom images or snapshot sources, sandboxes with Drive mounts, and sandboxes created without a snapshot.

## Initialize after open

Initialize state for one session after `open()` and before you return the sandbox. Relative paths, including a relative `workingDirectory` passed to `run()` or `spawn()`, resolve from `/workspace`:

```ts
import { defineSandbox } from "eve/sandbox";
import { VercelSandbox } from "eve/sandbox/vercel";

export const environment = VercelSandbox.environment();

export default defineSandbox(async ({ session }) => {
  const sandbox = await environment.open();
  // writes /workspace/.eve/session.json
  await sandbox.writeTextFile({
    content: JSON.stringify({ sessionId: session.id, workspace: "analysis" }),
    path: ".eve/session.json",
  });
  const result = await sandbox.run({ command: "mkdir -p .cache/app" });
  if (result.exitCode !== 0) throw new Error(result.stderr);
  return sandbox;
});
```

Later workflow steps and process restarts resume the sandbox without running the selector again, so the file isn't rewritten at each step. Use the environment's `prepare` callback for setup that every new session inherits, and post-open initialization for state that belongs to one session. See [Initialize a session's sandbox](/docs/sandbox#initialize-a-sessions-sandbox) for why initialization must be safe to repeat.

## Network policy

Pass the initial policy for the live sandbox to `open()`:

```ts
return environment.open({
  networkPolicy: {
    allow: {
      "api.example.com": [
        {
          transform: [{ headers: { authorization: `Bearer ${token}` } }],
        },
      ],
    },
  },
});
```

Credential transforms add headers at the network boundary, so the secret never enters the sandbox process.

Sandboxes from a Vercel environment always have `setNetworkPolicy()`, so you can update the policy without checking that the method exists:

```ts
const sandbox = await environment.open();
await sandbox.setNetworkPolicy("deny-all");
return sandbox;
```

## Live sandbox name

Sandboxes from `environment.open()` and `ctx.getSandbox(environment)` have a read-only `name`. It identifies the live Vercel Sandbox and stays the same across stop and resume. Use it as `sandboxName` when you create Vercel git-credential-broker grants for that sandbox:

```ts
const sandbox = await ctx.getSandbox(environment);
const sandboxName = sandbox.name;
```

## Mount a Drive

A [Vercel Sandbox Drive](https://vercel.com/docs/sandbox/concepts/drives) stores files independently of a sandbox. Install `@vercel/sandbox` in your app and create or retrieve the Drive before calling `open()`. Then pass its name and mount mode in `mounts` under an absolute path:

```bash
pnpm add @vercel/sandbox
```

```ts
import { Drive } from "@vercel/sandbox";
import { defineSandbox } from "eve/sandbox";
import { VercelSandbox } from "eve/sandbox/vercel";

// use one region for the environment and the Drive, so sandboxes run next to the Drive
const REGION = "iad1";

export const environment = VercelSandbox.environment({ region: REGION });

export default defineSandbox(async () => {
  const drive = await Drive.getOrCreate({
    name: "agent-workspace",
    region: REGION,
  });

  return environment.open({
    mounts: {
      "/data": { drive: drive.name, mode: "read-write" },
    },
  });
});
```

Files written to `/data` remain on the Drive after the sandbox stops. Keep Drive mounts separate from eve's managed `/workspace` directory.

Each `mounts` entry is one of these:

- A `Drive`, which mounts read-write.
- A `{ drive, mode }` reference to an existing Drive by name, where `mode` is `"read-write"` or `"snapshot"`. `drive.snapshot()` returns such a reference.

Vercel allows up to four non-overlapping, absolute Drive mount paths per sandbox. See the Vercel documentation for [how Drives work, their limits, and pricing](https://vercel.com/docs/sandbox/concepts/drives).

### Drive credentials

`Drive.getOrCreate()` gets credentials through your app's `@vercel/sandbox`, not through eve. It reads the `token`, `teamId`, and `projectId` passed to it. Without them, it uses a Vercel OIDC token that the SDK refreshes from your Vercel CLI login and linked project.

`Drive.getOrCreate()` ignores `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, `VERCEL_PROJECT_ID`, and the credentials passed to `VercelSandbox.environment()`. Outside Vercel without a linked project, pass the credentials to `Drive.getOrCreate()` too.

### Concurrent sessions

A Drive supports one active read-write mount, and each eve session gets its own sandbox. The fixed `agent-workspace` name therefore works only when one session uses the Drive at a time. If sessions can overlap, derive an opaque, stable Drive name per user or tenant. The [Vercel guide to using Drives with eve](https://vercel.com/kb/guide/vercel-drives-eve-agent) shows how to derive a Drive name from the user who started the session.

To share a point-in-time view across concurrent sandboxes, mount a read-only snapshot instead:

```ts
return environment.open({
  mounts: {
    "/data": { drive: drive.name, mode: "snapshot" },
  },
});
```

Before you mount a Drive snapshot, seed a new Drive by mounting it read-write and writing at least one file. An uninitialized Drive can't be mounted as a snapshot.

See the [sandbox overview](/docs/sandbox) for managed workspace, skills, parent inheritance, and custom providers. Use [Docker Sandbox](/docs/sandbox/docker) for local containers.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)