Skip to content
Runtime

How to run JavaScript and Node.js code in a sandbox

Write the script to an isolated Linux microVM that already has Node.js. Run node with a timeout, then read the output.

On Runtime, Node.js 24.21.0, npm and Bun 1.4.0 are in every sandbox. Nothing installs before the first run. Each sandbox is a Firecracker microVM with its own kernel. Runtime bills the CPU the script uses, at $0.025 per vCPU-hour. A sandbox that waits on a slow API costs only a fraction of the cost of one that computes (pricing).

Run a script

TypeScriptimport { Sandbox } from "withruntime";const script = `const primes = [];for (let n = 2; primes.length < 10; n++) if (primes.every((p) => n % p)) primes.push(n);console.log(JSON.stringify(primes));`;await using sbx = await Sandbox.create({  network: { internet: false },  timeoutSeconds: 300,  onLeaseEnd: "stop",});await sbx.files.write("/workspace/job.mjs", script);const run = await sbx.exec(["node", "job.mjs"], { timeoutMs: 20_000 });console.log(run.exitCode, run.timedOut, run.stdout);
Pythonfrom withruntime import Sandboxscript = """const primes = [];for (let n = 2; primes.length < 10; n++) if (primes.every((p) => n % p)) primes.push(n);console.log(JSON.stringify(primes));"""with Sandbox.create(network={"internet": False}, timeout_seconds=300, on_lease_end="stop") as sbx:    sbx.files.write("/workspace/job.mjs", script)    run = sbx.exec(["node", "job.mjs"], timeout_ms=20_000)    print(run.exit_code, run.timed_out, run.stdout)

The command is an array, so it runs node directly with no shell. Nothing in the script or its arguments can inject a command. A script stuck in a loop returns timedOut: true and whatever it printed.

What JavaScript has in the image

Tool In the image
Node.js 24.21.0: node, npm, npx
Bun 1.4.0: bun
Native modules gcc, g++, make and Python 3.12
Source git, curl, wget
OS Ubuntu 24.04.5 LTS (noble), amd64

All outbound traffic goes through a proxy on the host. The image sets HTTP_PROXY, HTTPS_PROXY and NODE_USE_ENV_PROXY=1. So npm, fetch and most HTTP clients find the proxy without setup (the network).

Install from npm, then close the network

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({  network: { internet: true, allow: ["registry.npmjs.org"] },  timeoutSeconds: 600,  onLeaseEnd: "stop",});await sbx.exec("npm init -y && npm install --no-fund --no-audit lodash", {  check: true,  timeoutMs: 180_000,});await sbx.network.set({ internet: false });await sbx.files.write(  "/workspace/job.cjs",  "console.log(require('lodash').chunk([1, 2, 3, 4], 2))",);console.log((await sbx.exec(["node", "job.cjs"], { timeoutMs: 20_000 })).stdout);
Pythonfrom withruntime import Sandboxwith Sandbox.create(    network={"internet": True, "allow": ["registry.npmjs.org"]},    timeout_seconds=600,    on_lease_end="stop",) as sbx:    sbx.exec("npm init -y && npm install --no-fund --no-audit lodash", check=True, timeout_ms=180_000)    sbx.network.set(internet=False)    sbx.files.write("/workspace/job.cjs", "console.log(require('lodash').chunk([1, 2, 3, 4], 2))")    print(sbx.exec(["node", "job.cjs"], timeout_ms=20_000).stdout)

registry.npmjs.org is npm's default registry (npm). Installing in the sandbox also keeps whatever a package does at install time off your server.

A stateful JavaScript session

The code interpreter keeps variables between cells. An array of objects is returned as a table:

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ network: { internet: false } });await sbx.interpreter.run("const rows = [{ city: 'Oslo', t: 4 }, { city: 'Rome', t: 19 }]", {  language: "javascript",});const cell = await sbx.interpreter.run("rows.map((r) => r.t).reduce((a, b) => a + b)", {  language: "javascript",});console.log(cell.results[0]?.data["text/plain"]); // 23
Pythonfrom withruntime import Sandboxwith Sandbox.create(network={"internet": False}) as sbx:    sbx.interpreter.run("const rows = [{ city: 'Oslo', t: 4 }, { city: 'Rome', t: 19 }]",                        language="javascript")    cell = sbx.interpreter.run("rows.map((r) => r.t).reduce((a, b) => a + b)", language="javascript")    print(cell["results"][0]["data"]["text/plain"])  # 23

The interpreter runs seven languages. Four are in the default image. The other three install from Ubuntu's archive the first time a sandbox uses them (code interpreter):

Language language Ready First use, 26 October 2026
Python "python" In the image (default) No install
JavaScript "javascript" In the image No install
TypeScript "typescript" In the image No install
Bash "bash" In the image No install
Java "java" Installed on first use About 30 seconds, once
Go "go" Installed on first use About 20 seconds, once
R "r" Installed on first use About 15 seconds, once

Serve an app and look at it

For an app rather than a script, start it with spawn. The process keeps running after the call returns. Share the port as a private HTTPS preview. An exec ends everything it started when its command ends.

TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ timeoutSeconds: 1800 });await sbx.files.upload("./app", "/workspace/app");await sbx.exec("cd app && npm ci", { check: true, timeoutMs: 300_000 });const server = await sbx.spawn("cd app && npm start");console.log(server.id, server.info.state);

Bake your dependencies into an image

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await runtime.images.build({  name: "node-tools",  recipe: { npm: ["typescript", "prettier"], apt: ["jq"] },});await using sbx = await runtime.sandboxes.create({ image: "node-tools" });console.log((await sbx.exec(["npx", "prettier", "--version"])).stdout);
Pythonfrom withruntime import Runtimeruntime = Runtime()runtime.images.build(name="node-tools", recipe={"npm": ["typescript", "prettier"], "apt": ["jq"]})with runtime.sandboxes.create(image="node-tools") as sbx:    print(sbx.exec(["npx", "prettier", "--version"]).stdout)

Building an image is free. A stored image is billed by its size (custom images).

A new account gets 100 free hours of a 1 GB machine in its first 30 days. No payment card is needed:

Terminalnpx withruntime sandboxes run -- node --version

Sources

Facts on this page were checked on 26 October 2026.

100 free hoursin your first 30 days.

of a 1 GB machine

  • No credit card
  • Four sandboxes at once, 2 vCPU and 4 GiB each
  • Then prepaid credit from $10, no plan fee
Start free, no card
Start free, no card