Skip to content
Runtime

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

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

On Runtime, a thousand projects with a hundred published sites cost about $220 a month. That is about 22 cents a project. The figure uses the rates in effect on 26 October 2026 and the usage worked through below. A project that nobody is editing pauses and pays only for storage. A published site wakes on the first visit. Each project's code runs in its own Firecracker microVM, 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 versions side by side fork({ count: 2 }): running copies with memory and processes
Resuming a project later 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 this 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"]

Put previewLink in an iframe in your editor. The dev server reloads as the model writes files. Live reload reaches the preview because WebSockets pass through it. The watch merges repeated writes to one file into a single event with a count. So if the model rewrites a file ten times, the editor receives one update.

Let the user pick between two versions

When a prompt could go two ways, fork the project. Let the model take each copy in a different direction. Both copies start running, with the dev server already up. Each copy 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 only for the capture. The source wakes before the call returns. The snapshot the fork takes for itself is deleted, and it is never billed (snapshots and forks).

Publish on the customer's own domain

Custom domains are included on paid accounts, up to 50 per account. Point the customer's hostname at the project. Show the customer the two DNS records. Then verify the domain:

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 after the TXT record matches. The first visit gets a Let's Encrypt certificate in a few seconds. A visit wakes a paused sandbox, so a site with little traffic can keep its idle pause. To keep editing without changing the live site, fork a copy for publishing. Point the domain at the copy. Running runtime domains add again moves the name to a new sandbox (networking).

Keep customers and costs apart

  • Isolation: every project runs in its own microVM. Root inside the microVM cannot change its network, CPU, memory or cost.
  • What the app can reach: network.allow limits a project to package registries and the APIs it calls. deny always takes precedence.
  • API keys: the app uses a customer's Stripe or OpenAI key, stored as a secret. The generated code never reads the key.
  • Spend: maxTotalCostMicros caps the total cost of each project over its whole life.
  • Preview origin: preview addresses are under runtimehost.com. They are never under Runtime's own site.

What it costs

Assume 1,000 projects a month. Each is edited for 2 hours on 2 vCPU and 4 GiB. The model's builds use 0.2 of a vCPU on average. Each is then paused for 718 hours with 1 GB stored. A hundred of the projects are published. Visits wake them for 20 hours a month, at the CPU floor of 0.05 vCPU. They are paused the other 700 hours:

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

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

Start

Terminalnpx withruntime sandboxes run --keep -- node --version

Open the browser link to approve the first run. From the CLI, runtime domains 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 26 October 2026.

Related.

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