> Discover all available pages from the documentation index: https://mastra.ai/llms.txt # Deploy to a sandbox `@mastra/deployer-sandbox` deploys a full Mastra server, including Studio, into an ephemeral workspace sandbox and returns a live public URL. Repeat deployments can finish faster because the deployer skips dependency installation. Use sandbox deployments for: - Agent-built apps: An agent generates a Mastra project and deploys it to verify the result. - Continuous integration (CI): Spin up a real server for checks, then tear it down. - Instant previews: Share a working agent with your team before merging. - Multi-tenant untrusted code: Run per-user Mastra instances isolated from your infrastructure. Sandboxes have provider-enforced runtime caps and expire. For production hosting, see the [deployment overview](https://mastra.ai/docs/deployment/overview). ## Supported sandboxes The deployer works with any workspace sandbox that supports networking (public port URLs): - [Vercel Sandbox](https://mastra.ai/reference/workspace/vercel-sandbox) (`@mastra/vercel`) - [E2B](https://mastra.ai/reference/workspace/e2b-sandbox) (`@mastra/e2b`) - [Daytona](https://mastra.ai/reference/workspace/daytona-sandbox) (`@mastra/daytona`) Provider authors can add support by implementing the optional `networking` capability on [`WorkspaceSandbox`](https://mastra.ai/reference/workspace/sandbox). ## Quickstart Install the deployer and a sandbox provider of your choice. This example uses Vercel Sandbox: **npm**: ```bash npm install @mastra/deployer-sandbox @mastra/vercel ``` **pnpm**: ```bash pnpm add @mastra/deployer-sandbox @mastra/vercel ``` **Yarn**: ```bash yarn add @mastra/deployer-sandbox @mastra/vercel ``` **Bun**: ```bash bun add @mastra/deployer-sandbox @mastra/vercel ``` Configure the deployer in your `src/mastra/index.ts` file. The `sandboxName` identifies the deployment, so subsequent deployments with the same name reuse the existing sandbox. ```typescript import { Mastra } from '@mastra/core/mastra' import { SandboxDeployer } from '@mastra/deployer-sandbox' import { VercelSandbox } from '@mastra/vercel' export const mastra = new Mastra({ deployer: new SandboxDeployer({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', timeout: 2_400_000, // 40 minutes ports: [4111], }), }), }) ``` Two Vercel-specific requirements: - `timeout` can't exceed the maximum sandbox lifetime of your plan, 45 minutes on Pro. A higher value fails the deploy with a 400 from the Vercel API. - Declare the server port in `ports`. Vercel exposes only ports declared at creation, unlike E2B and Daytona. Build and deploy in one command: ```bash mastra build ``` When a `SandboxDeployer()` is configured, `mastra build` bundles your project and deploys it into the sandbox. The deploy prints the API and Studio URLs and writes a `sandbox-deployment.json` manifest into `.mastra/output`: ```text API: https://-4111.vercel.run/api Studio: https://-4111.vercel.run ``` The manifest includes `expiresAt` when the sandbox provider reports an expiration time. Redeploys to the same sandbox skip the dependency install when its inputs are unchanged. These inputs are `package.json`, the bundled lockfiles, and the install command. ### What the URLs serve The Studio URL is the sandbox root, without `/api`. Open it in a browser to use Studio. When the deploy runs with `studio: false`, the root serves the Mastra welcome page instead. The API URL is only a prefix for the endpoints below it, such as `/api/agents`. `/api` has no handler of its own, so opening it in a browser returns a "Not Found" response even though the server is healthy. To check a deployment, call an endpoint directly: ```bash curl -s -X POST https://4111-.e2b.app/api/agents/weatherAgent/generate \ -H "Content-Type: application/json" \ -d '{"messages":[{"role":"user","content":"Weather in London"}]}' | jq -r '.text' ``` ### Provider credentials Each provider authenticates with its own credentials. Set the ones for the sandbox you deploy to: ```bash # E2B E2B_API_KEY= # Daytona DAYTONA_API_KEY= # Vercel VERCEL_TOKEN= VERCEL_TEAM_ID= VERCEL_PROJECT_ID= ``` Every provider also accepts these as constructor options. Self-hosted E2B and Daytona installations take `E2B_DOMAIN` or `DAYTONA_API_URL`. Unlike `mastra dev`, `mastra build` doesn't load `.env` files. The deploy happens inside the build, so a sandbox provider that reads its credentials from the environment, such as E2B with `E2B_API_KEY`, sees an empty value and the deploy fails with an authentication error. Adding `import 'dotenv/config'` to `src/mastra/index.ts` doesn't fix this. To find the deployer, the build extracts only the `deployer` option from your entry file and tree-shakes everything else away, including that import. Load the `.env` file into the shell environment before the build runs. [`dotenv-cli`](https://www.npmjs.com/package/dotenv-cli) is one way to do that: **npm**: ```bash npm install --save-dev dotenv-cli ``` **pnpm**: ```bash pnpm add --save-dev dotenv-cli ``` **Yarn**: ```bash yarn add --dev dotenv-cli ``` **Bun**: ```bash bun add --dev dotenv-cli ``` ```json { "scripts": { "deploy": "dotenv -e .env -- mastra build" } } ``` Then run `npm run deploy`. The script is named for what it does, since `mastra build` deploys once a `SandboxDeployer()` is configured. Keep it separate from a plain `build` script so a hosting platform or CI job that runs `npm run build` doesn't deploy a sandbox by accident. In continuous integration (CI), export the credentials as secrets instead. Whatever mechanism you use, the variables must exist in the shell environment, not only in the `.env` file. This applies to credentials the deployer needs on your machine. Variables the deployed server needs are handled separately: the deployer reads `.env`, `.env.production`, and `.env.local` and injects them into the sandbox. See [Security](#security). ### Using E2B For E2B, the `id` identifies the deployment, so subsequent deployments with the same value reconnect to the existing sandbox, whether it's running or paused. ```typescript import { SandboxDeployer } from '@mastra/deployer-sandbox' import { E2BSandbox } from '@mastra/e2b' const deployer = new SandboxDeployer({ sandbox: new E2BSandbox({ id: 'my-preview', template: 'base', timeout: 3_600_000, // 1 hour }), }) ``` Pass `template: 'base'` unless you need filesystem mounts; without it, the provider builds a custom Filesystem in Userspace (FUSE) template on first use. E2B pauses instead of stopping: `stop()` snapshots the whole virtual machine (VM), including memory and running processes. When a paused sandbox wakes, the Mastra server resumes where it left off, and there's no relaunch step like on Vercel. ### Using Daytona For Daytona, the `id` identifies the deployment, so subsequent deployments with the same value reconnect to the existing sandbox. Set `public: true` to make the preview URL accessible without a token. ```typescript import { SandboxDeployer } from '@mastra/deployer-sandbox' import { DaytonaSandbox } from '@mastra/daytona' const deployer = new SandboxDeployer({ sandbox: new DaytonaSandbox({ id: 'my-preview', public: true, autoStopInterval: 30, // minutes }), }) ``` Stopping a Daytona sandbox persists its filesystem but not running processes, so waking works like Vercel: the resolver relaunches the server on `wake: true`. Daytona filters outbound traffic per destination. Requests to some hosts connect over Transport Layer Security (TLS) normally, while others are reset during the handshake, which surfaces in an agent or a tool as Node's generic `fetch failed`. Rule out your code before debugging it, by running `curl` against the same host from inside the sandbox: ```typescript const sandbox = new DaytonaSandbox({ id: 'my-preview' }) await sandbox.start() const result = await sandbox.executeCommand('curl -v --max-time 10 https://api.example.com') console.info(result.stdout, result.stderr) ``` A `Connection reset by peer` during the TLS handshake points at the filtering rather than your agent. Ask Daytona support to allow the destination. Restricted Daytona tiers also block cloud storage endpoints, which the mount helpers report with a dedicated error. ## Deploying programmatically `deployToSandbox()` deploys a prebuilt output directory without the bundler. Unlike `SandboxDeployer()`, it doesn't include Studio unless you pass `studio: true`. Use it in CI or from agent code: ```typescript import { deployToSandbox } from '@mastra/deployer-sandbox' import { VercelSandbox } from '@mastra/vercel' const deployment = await deployToSandbox({ sandbox: new VercelSandbox({ sandboxName: 'ci-smoke', ports: [4111] }), dir: '.mastra/output', }) console.info(deployment.url) // https://-4111.vercel.run await deployment.logs() // tail the server log await deployment.stop() // stop the sandbox (resumable) await deployment.destroy() // permanently delete the sandbox ``` ## Lifecycle ### Managing a deployment Use the server-only `getDeployment()` export from `@mastra/deployer-sandbox/client` to retrieve an existing deployment. It identifies the sandbox through provider-specific configuration, such as a Vercel `sandboxName` or an E2B or Daytona `id`. The lookup isn't tied to the process that created the deployment, so you can use it from another server-side service or in CI. ```typescript import { getDeployment } from '@mastra/deployer-sandbox/client' import { VercelSandbox } from '@mastra/vercel' const deployment = await getDeployment({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), port: 4111, }) console.info(deployment.status, deployment.url) await deployment.logs() // tail the server log await deployment.stop() // stop the sandbox (resumable) await deployment.destroy() // permanently delete the sandbox ``` Pass `wake: true` to resume a stopped sandbox before returning. The server is relaunched only if it isn't healthy after the resume. Provider tooling works too, for example `vercel sandbox ls`, `vercel sandbox stop`, and `vercel sandbox rm`. ### Expiry and URLs Sandboxes expire according to the provider's runtime limits. When the provider reports an expiration time, the deploy logs it and `deployment.expiresAt` exposes it programmatically. Sandbox URLs can change when a sandbox stops and resumes. Treat the URL as plumbing and the sandbox identity (for example, the `sandboxName`) as the stable handle. The routing tiers below deal with URL rotation. ## Routing tiers ### Tier 1: Direct URL Use the printed URL directly for development, demos, and CI where a fresh URL per deploy is acceptable. ### Tier 2: Resolve at runtime `getDeployment()` resolves the current URL at runtime, so consumers never hold a stale URL. Any server that knows the sandbox name can resolve it, including one in a different codebase than the Mastra project: ```typescript import { getDeployment } from '@mastra/deployer-sandbox/client' import { VercelSandbox } from '@mastra/vercel' const deployment = await getDeployment({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), wake: true, }) console.info(deployment.url, deployment.status) ``` With `wake: false` (the default) the sandbox isn't started and you get `{ url, status }` to act on. Resolving the URL, `stop()`, and `destroy()` attach to the existing sandbox by name without resuming it, so lifecycle operations on a stopped sandbox never wake it or start billing. With `wake: true` the sandbox resumes and the server is relaunched if it isn't answering. Whether a relaunch is needed depends on the provider: Vercel and Daytona restore the filesystem but not running processes, while E2B resumes the whole VM including the server process. > **Warning:** `@mastra/deployer-sandbox/client` is server-only. Resolving a sandbox uses provider credentials that must never reach the browser. The module throws if imported in a browser context. ### Tier 3: Stable URL for end users Give end users a stable URL on your own domain and forward to the sandbox server-side, using either a route handler proxy or an Edge Config alias. The example below shows a Vercel & Next.js setup, but the concept applies to any server-side framework or provider that can forward requests. - **Route handler proxy.** `createSandboxHandler()` caches the sandbox URL and re-resolves after a connection-level failure, which covers URL rotation and cold wakes: ```typescript import { createSandboxHandler, getDeployment } from '@mastra/deployer-sandbox/client' import { VercelSandbox } from '@mastra/vercel' const handler = createSandboxHandler({ resolve: async () => { const deployment = await getDeployment({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), wake: true, }) return deployment.url! }, }) export { handler as GET, handler as POST } ``` - **Edge Config alias.** Set the `alias` option on the deployer to keep a [Vercel Edge Config](https://vercel.com/docs/edge-config) item pointed at the current URL on every deploy: ```typescript const deployer = new SandboxDeployer({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), alias: { edgeConfigId: 'ecfg_...', key: 'my-preview', token: process.env.VERCEL_TOKEN! }, }) ``` Then rewrite requests in Next.js middleware with `createSandboxProxy()`: ```typescript import { createSandboxProxy } from '@mastra/deployer-sandbox/client' export const middleware = createSandboxProxy({ key: 'my-preview' }) export const config = { matcher: '/api/:path*' } ``` ## CI example Deploy a preview on every pull request: ```yaml name: Sandbox preview on: pull_request jobs: preview: runs-on: ubuntu-latest env: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} VERCEL_TEAM_ID: ${{ secrets.VERCEL_TEAM_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: npx mastra build - run: curl --fail "$(jq -r .url .mastra/output/sandbox-deployment.json)/api" ``` ## Security - The sandbox URL is public. Anyone with the URL can reach your Mastra server, including Studio. Enable [server auth](https://mastra.ai/docs/server/auth) for anything beyond throwaway previews. - Environment variables from your `.env` files are injected into the remote sandbox VM so the server can run. The deploy logs a warning when this happens. Don't deploy secrets you wouldn't put on a shared preview server. - To restrict access to Tier 3 traffic, pass a `secret` to `createSandboxHandler()` or `createSandboxProxy()`. The helpers attach it as the `x-mastra-sandbox-secret` header on forwarded requests. Configure [server auth](https://mastra.ai/docs/server/auth) to require that header, and direct hits to the sandbox URL get rejected while traffic through your domain works. ## Related - [Deployment overview](https://mastra.ai/docs/deployment/overview) - [Server authentication](https://mastra.ai/docs/server/auth) - [`WorkspaceSandbox` reference](https://mastra.ai/reference/workspace/sandbox)