How to run a browser automation agent in a cloud sandbox
Give the agent a Linux desktop in a sandbox, send it screenshots, and apply the clicks and typing it chooses.
On Runtime the browser runs on a machine of its own and you pay for the CPU it uses. A 2 vCPU, 4 GiB sandbox costs $0.03125 an hour while the agent waits on its model and $0.08 an hour with both CPUs busy. The desktop takes clicks, typing and screenshots through the SDK, streams live to a private link, and records to MP4, with nothing to install on your side.
The short answer
Start a desktop, open the page, then loop: screenshot to the model, action back to the desktop.
TypeScriptimport { Sandbox } from "withruntime";type Action = | { kind: "click"; x: number; y: number } | { kind: "type"; text: string } | { kind: "open"; url: string } | { kind: "done" };type Model = (screenshot: Uint8Array) => Promise<Action>; // your computer-use modelexport async function browse(model: Model, startUrl: string, maxSteps = 30) { await using sbx = await Sandbox.create({ diskMiB: 8192, timeoutSeconds: 1800 }); const { streamUrl } = await sbx.desktop.start({ width: 1280, height: 800 }); console.log("watch it live:", streamUrl); // private: it carries a one-time token await sbx.desktop.open(startUrl); for (let step = 0; step < maxSteps; step++) { const action = await model(await sbx.desktop.screenshot()); if (action.kind === "done") break; if (action.kind === "click") await sbx.desktop.click(action.x, action.y); if (action.kind === "type") await sbx.desktop.type(action.text); if (action.kind === "open") await sbx.desktop.open(action.url); } return sbx.desktop.screenshot(); // the final state}Pythonfrom withruntime import Sandboxdef browse(model, start_url: str, max_steps: int = 30) -> bytes: """model(png_bytes) returns a dict: {"kind": "click", "x": .., "y": ..}, {"kind": "type", "text": ..}, {"kind": "open", "url": ..} or {"kind": "done"}.""" with Sandbox.create(disk_mib=8192, timeout_seconds=1800) as sbx: desktop = sbx.desktop.start(width=1280, height=800) print("watch it live:", desktop["streamUrl"]) sbx.desktop.open(start_url) for _ in range(max_steps): action = model(sbx.desktop.screenshot()) if action["kind"] == "done": break if action["kind"] == "click": sbx.desktop.click(action["x"], action["y"]) elif action["kind"] == "type": sbx.desktop.type(action["text"]) elif action["kind"] == "open": sbx.desktop.open(action["url"]) return sbx.desktop.screenshot()The first start in a sandbox installs the desktop, about 90 seconds and 1 GB of
its disk, once; Firefox follows in the background, and an open before it is
ready waits for it. That is why the sample asks for an 8 GiB disk.
Three ways to drive a browser
| Way | What the agent sees and does | Best for |
|---|---|---|
| The desktop | Screenshots in; clicks, typing and URLs out | Computer-use models; any site or app |
| Playwright MCP server | Page structure and browser tools over MCP | Agents that already speak MCP |
| Playwright or Puppeteer code | Your script, run with exec |
Known flows, scraping, end-to-end tests |
Start the Playwright MCP server inside the sandbox with one call, and give your agent its URL and headers:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();await using sbx = await runtime.sandboxes.create({ diskMiB: 8192 });await sbx.mcp.start([{ id: "playwright" }]);const gateway = await sbx.mcp.ready();for (const server of gateway.servers) console.log(server.name, server.url);console.log(gateway.headers); // send these with every requestruntime.mcp.catalog() lists every server with its licence and pinned
version. For scripted Playwright, see Playwright in a cloud
sandbox.
Watch it and keep a record
The live view is a private preview of the desktop, so a person can watch the agent and step in. A recording saves the session as MP4:
TypeScriptimport { writeFile } from "node:fs/promises";import { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ diskMiB: 8192 });await sbx.desktop.start();const rec = await sbx.desktop.recordings.start({ fps: 10, maxMiB: 256 });await sbx.desktop.open("https://example.com");await sbx.desktop.recordings.stop(rec.id);await writeFile("session.mp4", await sbx.desktop.recordings.download(rec.id));Pythonfrom withruntime import Sandboxwith Sandbox.create(disk_mib=8192) as sbx: sbx.desktop.start() recording = sbx.desktop.recordings.start(fps=10, max_mib=256) sbx.desktop.open("https://example.com") sbx.desktop.recordings.stop(recording["id"]) with open("session.mp4", "wb") as file: file.write(sbx.desktop.recordings.download(recording["id"]))A recording uses one encoder thread at low priority and at most 4 Mbit/s, so the
browser comes first. It never grows past maxMiB.
Keep the agent on the sites it should visit
Once the desktop and browser are in, narrow the network to the sites the task needs. The rule applies at once, binds root inside the sandbox, and is enforced on the host:
TypeScriptimport { Sandbox } from "withruntime";await using sbx = await Sandbox.create({ diskMiB: 8192 });await sbx.desktop.start();await sbx.desktop.open("https://example.com");await sbx.network.set({ internet: true, allow: ["example.com", "*.example.com"] });To give the agent an API it should call, store the key as a secret: the sandbox holds only a placeholder, and the host's proxy adds the value on HTTPS requests to the hosts you name (egress control).
Start ready
Install the desktop once, keep that sandbox as a snapshot, and start every task from it. The copy starts with the desktop installed and running:
TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();const base = await runtime.sandboxes.create({ diskMiB: 8192 });await base.desktop.start({ width: 1280, height: 800 });await base.desktop.open("https://example.com");const snapshot = await base.snapshot({ name: "desktop-ready", retentionDays: 30 });await base.stop();await using task = await runtime.sandboxes.create({ snapshot: snapshot.id });await task.desktop.open("https://example.com");What a browser agent needs
| Need | How Runtime covers it |
|---|---|
| A real browser and screen | A Linux desktop with Firefox; clicks, typing, screenshots |
| A person watching | A private live stream link |
| An audit trail | MP4 recordings, capped by maxMiB |
| One task per browser profile | A Firecracker microVM per sandbox: no shared cookies, downloads or sessions |
| Sites the agent may visit | Allow and deny lists, enforced on the host |
| Many sessions at once | 100 sandboxes at once on a paid account to start |
| No setup per task | Start from a snapshot with the desktop already running |
What it costs
Take 500 browser tasks a month. Each keeps a 2 vCPU, 4 GiB sandbox running for 3 minutes, with the browser using 0.8 of a vCPU on average:
TextCPU: 500 × 3 min / 60 × 0.8 vCPU × $0.025 = $0.50Memory: 500 × 3 min / 60 × 4 GiB × $0.0075 = $0.75Total: $1.25Runtime charges $0.025 per vCPU-hour of measured CPU and $0.0075 per GiB-hour of memory (pricing). A 1 GB snapshot to start from adds $0.08 a month. New accounts get 50 free sandbox hours, no card; the trial reaches ports 80 and 443, which is what browsing needs.
Start
Terminalnpx withruntime sandbox run --trial --keep -- echo readyThe first run prints a link to approve in your browser. Then
runtime sandbox desktop <id> start prints a link to watch it (CLI).
Related: Playwright in a cloud sandbox, coding agent sandbox, egress control, sandbox snapshots.
Facts on this page were checked on 25 September 2026.