Skip to content

Getting started

Run a command in a sandbox from your own TypeScript application. You’ll need a provider account and Bun to run this example. The SDK also supports server-side Node.js.

In your project, install the SDK and the adapter for your provider:

Terminal window
npm install beachside @beachside/daytona

pnpm add and bun add work too. boxd and Vercel install the same way; see boxd setup and Vercel setup. Compare provider support before choosing.

To run OpenCode rather than a shell command, use the runnable agent examples.

Create an untracked .env.local file in your project root (the repository root for Modal):

.env.local
DAYTONA_API_KEY="<your-daytona-api-key>"

The example uses the daytona-small Linux snapshot in us. Select an active snapshot available in your region. daytona-default permits Daytona’s essential services; it does not mean all egress is blocked.

Add .env.local to your .gitignore. Keep credentials on the server and out of browser code. Running the example creates a sandbox in your provider account and may incur usage charges.

Choose a provider and save the example as quickstart.ts:

quickstart.ts
import { Image, Beachside } from "beachside";
import { daytonaAdapter } from "@beachside/daytona";
const apiKey = process.env.DAYTONA_API_KEY;
if (!apiKey) throw new Error("Set DAYTONA_API_KEY before running this example");
const beachside = await Beachside.connect({
adapter: daytonaAdapter,
credentials: { apiKey },
config: {
target: "us",
environment: Image.prepared("daytona-small"),
lifecycle: { lifetimeSeconds: 900 },
network: "daytona-default",
},
});
try {
await beachside.sandboxes.with({}, async (box) => {
const output = await box.exec(["printf", "Hello from Beachside!\n"]);
console.log(output.stdoutText());
});
} finally {
await beachside.close();
}

Run it with Bun:

Terminal window
bun --env-file=.env.local run quickstart.ts

You should see Hello from Beachside!. The Daytona and E2B examples use sandboxes.with() to clean up the sandbox after the callback succeeds or throws. An outer finally closes the client. The experimental Modal example shows manual sandbox cleanup. close() alone does not destroy compute. If creation or cleanup has an uncertain outcome, follow Errors and recovery before retrying.

For temporary work, prefer scoped sandbox cleanup. The scope retains snapshots and volumes, and cleanup failures preserve the callback result or error plus recovery information. Use manual create() when the sandbox should outlive the callback.

Beachside.connect(adapter, { signal, timeoutMs: 30_000 }) accepts ConnectOptions. The advanced definition/config/credentials form accepts the same controls in its options object. await client.close({ signal: cleanupSignal, timeoutMs: 5_000 }) accepts CloseOptions. Only positive finite safe-integer budgets are accepted; longer budgets are allowed.

Compatibility change: setup now defaults to a 30-second local wait and each close call defaults to a 5-second local wait. These budgets do not change sandbox lifetime, command deadlines or remote compute. Setup cancellation reports WAIT_ABORTED; expiry reports TIMEOUT, with effect none, cause and a phase-specific message. Aborting the setup signal after the client returns has no effect on the usable client.

Close immediately prevents new work and starts local cleanup once. Cancelling or timing out one close wait leaves that attempt running; a later close can wait for it. Cleanup runs in reverse registration order. A hanging hook can leave later hooks pending; failed releases are sent to onDiagnostic and remaining cleanup continues. Successful close means registered cleanup was attempted, not that every failed release succeeded. Late resources from abandoned setup are released locally. Neither setup failure nor close deletes sandboxes, volumes or snapshots; use explicit resource operations for those actions.