Runtime

How to build a Lovable-style AI app builder on sandboxes

Give every project a sandbox from a template snapshot, let the model edit it through tools, preview it live, and publish on a domain.

On Runtime a thousand projects with a hundred published sites cost about $220 a month, about 22 cents a project, at the rates in force on 25 September 2026 and the usage worked through below. Projects nobody is editing pause and pay only storage, published sites wake on the first visit, and each project's code runs in a Firecracker microVM of its own, so one customer's generated app never shares a kernel with another's.

The pieces of an app builder

Piece of the product Runtime call
A new project in seconds Create from a snapshot of a template with its dev server running
The model writing code sandboxTools(sbx): exec, read, write and list, bound to one sandbox
The editor showing each change files.watch streams creates, writes, renames and removes
The live preview pane previews.create(port), private with a token, WebSockets included
Two ideas side by side fork({ count: 2 }): running copies with memory and processes
Coming back tomorrow getOrCreate(name) and idlePauseSeconds
Publishing runtime.domains.add for the customer's own hostname, with HTTPS

Make the template once: install the framework's dependencies in a sandbox, start its dev server with spawn, and take a snapshot. The agent-built app preview guide shows that step in full.

The short answer: open a project and hand the model its tools

TypeScriptimport { Sandbox } from "withruntime";import { sandboxTools } from "withruntime/tools";export async function openProject(projectId: string, templateSnapshot: string) {  const sbx = await Sandbox.getOrCreate(`project-${projectId}`, {    snapshot: templateSnapshot, // the template, dev server already running on 3000    labels: { kind: "app", project: projectId },  });  if (!sbx.info.reused) await sbx.update({ idlePauseSeconds: 900 });  // Stream every file the model touches to the user's editor.  const watch = await sbx.files.watch(    "/workspace/app",    (event) => console.log(event.type, event.path), // send to the browser instead    { recursive: true, exclude: ["node_modules", ".git/**"] },  );  const preview = await sbx.previews.create(3000);  const tools = sandboxTools(sbx); // give these to your model's tool loop  return { sbx, watch, tools, previewLink: preview.urlWithToken };}
Pythonfrom withruntime import Sandboxfrom withruntime.tools import sandbox_toolsdef open_project(project_id: str, template_snapshot: str):    sbx = Sandbox.get_or_create(        f"project-{project_id}",        snapshot=template_snapshot,  # the template, dev server already running on 3000        labels={"kind": "app", "project": project_id},    )    if not sbx.info.get("reused"):        sbx.update(idle_pause_seconds=900)    watch = sbx.files.watch("/workspace/app", recursive=True, exclude=["node_modules", ".git/**"])    preview = sbx.previews.create(3000)    run, read, write, ls = sandbox_tools(sbx)  # give these to your model's tool loop    return sbx, watch, [run, read, write, ls], preview["urlWithToken"]

The dev server reloads as the model writes files, and live reload reaches the preview because WebSockets pass through it. The watch folds repeated writes to one file into a single event with a count, so a model rewriting a file ten times sends the editor one update.

Let the user pick between two versions

When a prompt could go two ways, fork the project and let the model take each copy in a different direction. Both copies start running, with the dev server already up, and each gets its own preview:

TypeScriptimport { Sandbox } from "withruntime";const project = await Sandbox.getOrCreate("project-p-77", { idlePauseSeconds: 900 });const [left, right] = await project.fork({ count: 2 });const a = await left!.previews.create(3000);const b = await right!.previews.create(3000);console.log(a.urlWithToken, b.urlWithToken);// Keep the one the user chooses; stop the other.await right!.stop();

A fork pauses the source for about a second on a fresh sandbox, longer the more memory it holds, and the snapshot it takes for itself is deleted and never billed (snapshots and forks).

Publish on the customer's own domain

Custom domains are included on paid accounts, up to 50 an account. Point the customer's hostname at the project, show them the two DNS records, and verify:

TypeScriptimport { Runtime } from "withruntime";const runtime = new Runtime();const domain = await runtime.domains.add({  hostname: "shop.customer-site.com",  sandboxId: process.env.PROJECT_SANDBOX_ID!,  port: 3000,});console.log(domain.records); // a TXT record that proves the name, and a CNAMEawait runtime.domains.verify("shop.customer-site.com");
Pythonimport osfrom withruntime import Runtimeruntime = Runtime()domain = runtime.domains.add("shop.customer-site.com", sandbox_id=os.environ["PROJECT_SANDBOX_ID"], port=3000)print(domain["records"])  # a TXT record that proves the name, and a CNAMEruntime.domains.verify("shop.customer-site.com")

The name goes live once the TXT record matches, and the first visit gets a Let's Encrypt certificate in a few seconds. A paused sandbox is woken by a visit, so a site with little traffic can keep its idle pause. To keep editing without touching the live site, fork a copy for publishing and point the domain at the copy; runtime domain add again moves the name to a new sandbox (networking).

Keep customers and costs apart

  • Isolation: every project runs in its own microVM, with root inside it and no way for root to change its network, CPU, memory or cost.
  • What the app can reach: network.allow narrows a project to package registries and the APIs it calls; deny always wins.
  • API keys: a customer's Stripe or OpenAI key stored as a secret is used by the app without the generated code ever reading it.
  • Spend: maxTotalCostMicros caps each project for its whole life.
  • Preview origin: preview addresses are under runtimehost.com, never under Runtime's own site.

What it costs

Take 1,000 projects a month, each edited for 2 hours on 2 vCPU and 4 GiB with the model's builds using 0.2 of a vCPU on average, then paused 718 hours with 1 GB stored. A hundred of them are published and woken by visits for 20 hours a month at the CPU floor of 0.05 vCPU, and paused the other 700 hours:

TextEditing CPU:      1,000 × 2 h × 0.2 vCPU × $0.025           = $10.00Editing memory:   1,000 × 2 h × 4 GiB × $0.0075             = $60.00Editing paused:   1,000 × 1 GB × $0.08 × 718 h / 720 h      = $79.78Published CPU:    100 × 20 h × 0.05 vCPU × $0.025           = $2.50Published memory: 100 × 20 h × 4 GiB × $0.0075              = $60.00Published paused: 100 × 1 GB × $0.08 × 700 h / 720 h        = $7.78Total:                                                        $220.06

Custom domains cost nothing extra. The template snapshot is storage at the same $0.08 per GB-month (pricing).

Start

Terminalnpx withruntime sandbox run --trial --keep -- node --version

Approve the browser link on first use. From the CLI, runtime domain add <hostname> <sandbox> 3000 publishes a port once the account has credit.

Related: preview apps an agent builds, sandbox forks, online IDE on sandboxes, coding agent sandbox.

Facts on this page were checked on 25 September 2026.