How to build a browser-based online IDE on cloud sandboxes
Back each workspace with a sandbox: files API for the editor, a terminal over WebSocket, live file events, and an app preview.
On Runtime an online IDE workspace used 40 hours a month costs about $1.60
on 2 vCPU and 4 GiB, at the rates in force on 25 September 2026, because the
sandbox is billed for the CPU that builds and servers use and pauses when the
browser tab goes quiet. Each workspace is a full Ubuntu 24.04 machine with
sudo, Python 3.12, Node.js 24, Bun, git and a C compiler, woken in about half
a second when the user comes back.
What each IDE feature calls
Your front end draws the editor; your back end holds the API key and makes these calls on the user's behalf:
| IDE feature | Runtime call |
|---|---|
| Open a workspace | Sandbox.getOrCreate(name, { idlePauseSeconds }) |
| File tree | files.list(path, { depth }): type, size and path of each entry |
| Open and save a file | files.readText(path), and files.write(path, text), which replaces atomically |
| Rename, delete, new folder | files.rename, files.remove, files.mkdir |
| Quick open | files.glob("**/*.ts") |
| Search in files | exec(["rg", "--json", query]): ripgrep is in the image |
| Tree kept in sync | files.watch: create, write, remove, rename and chmod as they happen |
| Terminal | terminal({ cols, rows, onData }), with write and resize |
| Run and preview | spawn the dev server, then previews.create(port) |
| Upload and download | files.upload and files.download move whole folders as one archive |
The short answer: a workspace and its terminal
TypeScriptimport { Sandbox } from "withruntime";export async function openWorkspace(userId: string, onTerminalBytes: (b: Uint8Array) => void) { const sbx = await Sandbox.getOrCreate(`ide-${userId}`, { diskMiB: 16_384, idlePauseSeconds: 1200, // pause 20 minutes after the last request labels: { kind: "ide", user: userId }, }); const tree = await sbx.files.list("/workspace", { depth: 2 }); const term = await sbx.terminal({ cols: 120, rows: 32, onData: onTerminalBytes }); const watch = await sbx.files.watch( "/workspace", (event) => console.log(event.type, event.path), // push to the browser to refresh the tree { recursive: true, exclude: ["node_modules", ".git/**"] }, ); return { sbx, tree, term, watch };}const ws = await openWorkspace("u-7", (bytes) => process.stdout.write(bytes));await ws.sbx.files.write("/workspace/hello.py", "print('saved from the editor')\n");ws.term.write("python3 hello.py\n"); // keystrokes from xterm.js go herews.term.resize(100, 30); // when the browser pane changes sizePythonfrom withruntime import Sandboxdef open_workspace(user_id: str): sbx = Sandbox.get_or_create( f"ide-{user_id}", disk_mib=16_384, idle_pause_seconds=1200, # pause 20 minutes after the last request labels={"kind": "ide", "user": user_id}, ) tree = sbx.files.list("/workspace", depth=2) term = sbx.terminal(cols=120, rows=32) watch = sbx.files.watch("/workspace", recursive=True, exclude=["node_modules", ".git/**"]) return sbx, tree, term, watchsbx, tree, term, watch = open_workspace("u-7")sbx.files.write("/workspace/hello.py", "print('saved from the editor')\n")term.write("python3 hello.py\n")term.write("exit\n")while (chunk := term.recv()) is not None: print(chunk.decode(errors="replace"), end="")The terminal is a real one, colours and all, so an in-browser terminal
component can pass its bytes straight through. A watch sees changes the
terminal, a build or an agent made, not only the editor's own saves, so the file
tree stays right. A sandbox runs up to four watches, each for an hour by
default; when a sandbox pauses, a watch ends with the reason paused and
resume() after the wake carries on with nothing lost.
Run the user's app
TypeScriptimport { Sandbox } from "withruntime";const sbx = await Sandbox.getOrCreate("ide-u-7", { idlePauseSeconds: 1200 });const server = await sbx.spawn("npm run dev -- --port 5173", { cwd: "/workspace/app" });const preview = await sbx.previews.create(5173);console.log(server.id, preview.urlWithToken); // open in the IDE's preview paneA preview is private unless you make it public, carries WebSockets for hot
reload, and wakes a paused workspace on the next visit behind a short "Waking
up" page. Preview addresses are under runtimehost.com, so the user's app
never shares an origin with your IDE.
Or run VS Code itself in the sandbox
Two ways to give users the full VS Code instead of your own editor:
- Their desktop VS Code:
runtime sandbox ssh config --installonce on their machine, then Remote-SSH toide-u-7.runtime. It goes through Runtime's API with their key, and the sandbox opens no port (SSH and editors). - In the browser: install code-server, the open-source VS Code for the browser, and share its port through a private preview, whose token is then the only way in.
Terminalcurl -fsSL https://code-server.dev/install.sh | shcode-server --bind-addr 127.0.0.1:8080 --auth none # start it with runtime sandbox spawnruntime sandbox preview ide-u-7 8080 # a private HTTPS address for itWith --auth none, keep the preview private: the preview token takes the place
of code-server's password.
What a hosted IDE needs
| Need | How Runtime covers it |
|---|---|
| A machine per user | A Firecracker microVM with its own kernel per workspace |
| Work kept overnight | Pause keeps files, memory and running processes, 1 to 365 days |
| The project's toolchain | A custom image from its own Dockerfile |
| Containers in the workspace | sudo enable-docker for Docker Engine and Compose |
| Databases and debuggers locally | runtime sandbox port-forward <name> <port> |
| Teams | Owner, admin, developer and billing roles on one account (teams) |
| Big repositories | Disk sized with diskMiB; uploads in checked, resumable chunks |
What it costs
Take 300 workspaces, each open 40 hours a month on 2 vCPU and 4 GiB with builds and the dev server using 0.25 of a vCPU on average, and paused the other 680 hours of a 30-day month with 2 GB of its own disk and memory stored:
TextCPU: 300 × 40 h × 0.25 vCPU × $0.025 = $75.00Memory: 300 × 40 h × 4 GiB × $0.0075 = $360.00Paused: 300 × 2 GB × $0.08 × 680 h / 720 h = $45.33Total: $480.33That is $1.60 a workspace a month. Paused storage is charged on the blocks the workspace alone owns, measured when it pauses (pricing).
Start
Terminalnpx withruntime sandbox create --trial --name ide-demo --get-or-create --idle-pause 1200npx withruntime sandbox shell ide-demoThe first command asks you to approve the connection in a browser; the second opens the same terminal your IDE would show.
Related: per-user dev environments, coding playground, app builder platform, run Docker in a sandbox.
Sources
- code-server install: the install script, read 25 September 2026.
- code-server FAQ:
--bind-addrand--auth none, read 25 September 2026.
Facts on this page were checked on 25 September 2026.