@deno/sandbox
Create isolated sandboxes on Deno Deploy to securely run code in a lightweight Linux microVM. You
can securely run shell scripts, spawn processes, execute JavaScript applications and REPLs, and
interact with files remotely.
This TypeScript/JavaScript SDK let's you create and manage sandboxes programmatically from Deno or
Node.js.
Note: Bun is not supported. Bun replaces the ws WebSocket library with its own native
implementation, which does not support the upgrade event required for sandbox connections. See
oven-sh/bun#5951 for details.
Features
- Create secure, isolated, and ephemeral Linux microVM sandboxes to execute arbitrary code and
subprocesses.
- Run arbitrary shell commands and scripts within the sandbox.
- Run JavaScript applications and REPLs in the sandbox.
- Install NPM and JSR packages in the sandbox.
- Execute web framework dev servers like Vite or
next dev.
- Simply execute snippets of JS code, one after the other, maintaining state between snippets
(great for progressively generated code from AI agents!)
- Expose any HTTP endpoints in the sandbox to the public internet via random and secure HTTPS URLs.
- Get SSH access to the sandbox for interactive debugging.
- Upload local files or directories into the sandbox.
- Control sandbox timeout and memory allocation.
- Restrict sandbox network access to specific hosts/IPs using
allowNet.
Installation
deno add jsr:@deno/sandbox
npm install @deno/sandbox
pnpm install jsr:@deno/sandbox
yarn add jsr:@deno/sandbox
Runtime Support
This SDK is tested and supported on:
- Deno: Latest stable version
- Node.js: Version 24+
Note: The await using syntax in the examples requires Node.js 24+. For earlier Node.js versions,
use try/finally blocks instead (see Compatibility note).
Quick Start
-
Go to the "Sandbox" tab in the Deno Deploy dashboard.
-
Create an access token and set it as the DENO_DEPLOY_TOKEN environment variable.
-
Use the @deno/sandbox SDK to create a new sandbox and interact with it.
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
await sandbox.sh`ls -lh /`;
APIs
View the full API documentation here.
Examples
The snippets below assume package.json contains { "type": "module" }, the @deno/sandbox
package is installed, and that DENO_DEPLOY_TOKEN env var is set.
Evaluate a JS snippet
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const result = await sandbox.deno.eval(`
const a = 1;
const b = 2;
a + b;
`);
console.log("result:", result);
Spawn a subprocess, and get buffered output
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const text = await sandbox.sh`pwd`.text();
console.log("result:", text);
Tip: for long‑running processes or large output, stream the stdout/stderr.
Create a package.json, install deps, run a web framework (Express), and expose it publicly
This creates a minimal Express app inside the sandbox, runs it on port 3000, and exposes it publicly
using sandbox.exposeHttp().
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const PACKAGE_JSON = {
name: "sandbox-express-demo",
private: true,
type: "module",
dependencies: { express: "^4.19.2" },
};
await sandbox.fs.writeTextFile("package.json", JSON.stringify(PACKAGE_JSON, null, 2));
await sandbox.fs.writeTextFile(
"server.js",
`import express from 'express';
const app = express();
app.get('/', (req, res) => res.send('Hello from Express in @deno/sandbox!'));
app.get('/time', (req, res) => res.json({ now: new Date().toISOString() }));
app.listen(3000, () => console.log('listening on :3000'));
`,
);
await sandbox.sh`deno install`;
const server = await sandbox.deno.run({ entrypoint: "server.js" });
const publicUrl = await sandbox.exposeHttp({ port: 3000 });
console.log("Public URL:", publicUrl);
const resp = await fetch(`${publicUrl}/time`);
console.log(await resp.json());
Get SSH access to the sandbox
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const { hostname, username } = await sandbox.exposeSsh();
console.log(`ssh ${username}@${hostname}`);
await new Promise((resolve) => setTimeout(resolve, 10 * 60 * 1000));
Interactive JavaScript REPL
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const repl = await sandbox.deno.repl();
await repl.eval("const x = 42;");
await repl.eval("const y = 8;");
const result = await repl.eval("x + y");
console.log("result:", result);
VSCode browser instance
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const vscode = await sandbox.exposeVscode();
console.log(vscode.url);
await vscode.status;
Template literal commands with variable interpolation
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const filename = "file with spaces.txt";
const content = "Hello, world!";
await sandbox.sh`echo ${content} > ${filename}`;
const files = ["file1.txt", "file2.txt", "file3.txt"];
await sandbox.sh`rm ${files}`;
const data = await sandbox.sh`echo '{"count": 42}'`.json<{ count: number }>();
console.log(data.count);
Error handling with noThrow()
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
try {
await sandbox.sh`exit 1`;
} catch (error) {
console.log("Command failed:", error);
}
const result = await sandbox.sh`exit 1`.noThrow();
console.log("Exit code:", result.status.code);
console.log("Success:", result.status.success);
Command cancellation
import { KillController, Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const controller = new KillController();
const cmd = sandbox.sh`sleep 30`.signal(controller.signal);
const promise = cmd.text();
setTimeout(() => {
controller.kill();
}, 2000);
try {
await promise;
} catch (error) {
console.log("Command was cancelled:", error);
}
Access both string and binary output
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
const result = await sandbox.sh`cat binary-file.png`
.stdout("piped");
console.log("Binary length:", result.stdout!.length);
console.log("Text length:", result.stdoutText!.length);
import fs from "node:fs";
fs.writeFileSync("output.png", result.stdout!);
Error handling with custom error class
import { Sandbox, SandboxCommandError } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
try {
await sandbox.sh`exit 42`;
} catch (error) {
if (error instanceof SandboxCommandError) {
console.log("Exit code:", error.code);
console.log("Error message:", error.message);
}
}
Set environment variables
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
await sandbox.env.set("API_KEY", "secret-key-123");
await sandbox.env.set("NODE_ENV", "production");
const apiKey = await sandbox.sh`echo $API_KEY`.text();
console.log("API_KEY:", apiKey.trim());
Stream command output to local file
child.stdout is a Web ReadableStream. In Node, convert a Node fs.WriteStream to a Web
WritableStream to pipe efficiently.
import { Sandbox } from "@deno/sandbox";
import fs from "node:fs";
import { Writable } from "node:stream";
await using sandbox = await Sandbox.create();
await sandbox.fs.writeTextFile("big.txt", "#".repeat(5_000_000));
const child = await sandbox.spawn("cat", {
args: ["big.txt"],
stdout: "piped",
});
const file = fs.createWriteStream("./big-local-copy.txt");
await child.stdout.pipeTo(Writable.toWeb(file));
const status = await child.status;
console.log("done:", status);
Upload local files or directories
Copy files from your machine into the sandbox using sandbox.upload(localPath, sandboxPath).
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create();
await sandbox.fs.upload("./README.md", "./readme-copy.md");
await sandbox.fs.upload("./my-project", ".");
Control sandbox timeout
You can control how long your sandbox stays alive using the timeout option:
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create({ timeout: "session" });
import { Sandbox } from "@deno/sandbox";
const sandbox = await Sandbox.create({ timeout: "5m" });
const id = sandbox.id;
await sandbox.close();
const reconnected = await Sandbox.connect(id);
await reconnected.sh`echo 'Still alive!'`;
await reconnected.kill();
Supported duration suffixes: s (seconds), m (minutes). Examples: "30s", "5m", "90s".
Need other timeout modes? Contact deploy@deno.com.
Configure sandbox memory
You can customize the amount of memory allocated to your sandbox using the memory option. This
allows you to allocate more resources for memory-intensive workloads or reduce memory for lighter
tasks.
The memory option supports human-readable strings with binary (GiB, MiB, KiB) or decimal (GB, MB,
kB) units, as well as plain numbers (interpreted as bytes).
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create({ memory: "1GiB" });
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create({ memory: "4GiB" });
const memInfo = await sandbox.deno.eval<{ total: number }>("Deno.systemMemoryInfo()");
console.log("Total memory:", memInfo.total);
Memory limits (may change in the future):
- Minimum: 768 MiB
- Maximum: 4096 MiB (4 GiB)
The actual available memory inside the sandbox may be slightly less than the configured value due to
system overhead.
Want to allocate more memory? Contact deploy@deno.com.
Restrict outbound network access
You can restrict which hosts the sandbox can make outbound network requests to using the allowNet
option. This is useful for security-sensitive environments where you want to limit network access.
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create({
allowNet: ["example.com", "*.example.net"],
});
await sandbox.sh`curl https://example.com`;
Supported patterns:
- Exact hostnames with optional ports:
"example.com", "example.com:80"
- Wildcard subdomains with optional ports:
"*.example.com", "*.example.com:443"
- IP addresses with optional ports:
"203.0.113.110", "203.0.113.110:80"
- IPv6 addresses with optional ports:
"[2001:db8::1]", "[2001:db8::1]:443"
If allowNet is not specified, no network restrictions are applied.
Secret on the Wire
Set secret environment variables that are never exposed to sandbox code. The real secret values are
injected on the wire when the sandbox makes HTTPS requests to the specified hosts.
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create({
secrets: {
OPENAI_API_KEY: {
hosts: ["api.openai.com"],
value: "sk-proj-your-real-key",
},
ANTHROPIC_API_KEY: {
hosts: ["api.anthropic.com"],
value: "sk-ant-your-real-key",
},
},
});
Compatibility note on await using
The examples use Explicit Resource Management (await using), added in Node.js 24.
For Node.js 22/23 or earlier, use try/finally:
import { Sandbox } from "@deno/sandbox";
const sandbox = await Sandbox.create();
try {
} finally {
await sandbox.close();
}
App Management Examples
This package also provides the feature of managing Apps in your organization.
Creating App
import { Client } from "@deno/sandbox";
const client = new Client();
const app = await client.apps.create({
slug: "my-app-from-sdk",
});
console.log(app);
Listing apps
import { Client } from "@deno/sandbox";
const client = new Client();
const list = await client.apps.list();
console.log(list.items);
for await (const app of list) {
console.log(app);
}
Retrieving an app
import { Client } from "@deno/sandbox";
const client = new Client();
const appBySlug = await client.apps.get("my-app-from-sdk");
const appById = await client.apps.get("bec265c1-ed8e-4a7e-ad24-e2465b93be88");
Updating app info
import { Client } from "@deno/sandbox";
const client = new Client();
const updatedApp = await client.apps.update("bec265c1-ed8e-4a7e-ad24-e2465b93be88", {
slug: "my-cool-app",
});
Deleting an app
import { Client } from "@deno/sandbox";
const client = new Client();
await client.apps.delete("legacy-chaotic-app");
await client.apps.delete("bec265c1-ed8e-4a7e-ad24-e2465b93be88");
FAQ
How do I get a token?
Go to the console at https://console.deno.com and navigate to the Settings page where you can find
Organization Tokens section. Create a new token by clicking the Create a new token button.
How does it work?
When creating a new sandbox, the SDK communicates with the Deno Deploy API to provision a secure,
isolated Linux microVM. The SDK then establishes a secure connection to the sandbox, allowing you to
interact with it programmatically.
When you're done with the sandbox, you can simply destroy the Sandbox object. Doing this will
automatically shut down the sandbox and free up any resources it was using.
In which region are my sandboxes running?
You can specify the region where the sandbox will be created when creating a new sandbox:
import { Sandbox } from "@deno/sandbox";
await using sandbox = await Sandbox.create({ region: "ams" });
If not specified, the sandbox will be created in the default region.
What limits do sandboxes have?
Sandboxes have the following limits:
- CPU: 2 vCPU
- Memory: 768 MiB to 4096 MiB
- Disk: 10 GiB
Exceeding these limits may result in throttling or termination of your sandbox.