Architecture
CLI → platform → raptor → CAP. Four hops between you and a live URL.
LILY is four pieces that talk over HTTPS. Each one does exactly one job and hands off to the next. The whole system stays small enough that you can hold it in your head while you debug a deploy.
1. The CLI on your machine
Everything starts with lly. It is a single static binary that ships frontends for C, C++, and JavaScript, the Wasmtime-based runtime, and a plugin loader for everything else. The CLI does the expensive work locally: parsing your source, lowering it to BIR, emitting WebAssembly, and packing the result into a brick — a content-addressed bundle of one or more .wasm modules plus a manifest and any static assets.
Bricks are produced on your hardware, not in a build farm. That means you can inspect them with lly bundle inspect before they ever leave the laptop, and reproduce a deploy byte-for-byte from the same source tree. Authentication lives in ~/.lly/credentials, refreshed through a browser round-trip to auth.lilylabs.io.
2. Platform — the source of truth for ownership
platform.lilylabs.io is a Next.js dashboard backed by Postgres. It owns the user table, the app table, and the placement registry. When you run lly void deploy, the CLI uploads the brick to platform, which records that you own this placement and then forwards the request to Raptor. When you visit the dashboard later, platform is what knows your apps exist.
Platform never picks a CAP and never holds a brick longer than the upload. It is the system of record for who owns what, nothing more. That separation is deliberate: platform can be redeployed during business hours without affecting any running app.
3. Raptor — the placer
Raptor is a small Rust service that runs internally. It receives an allocation request from platform, looks at the current fleet of CAPs with their pool, region, and free capacity, picks one, mints an HMAC lease, and pushes the brick over to the chosen host. It also reissues leases on heartbeat and drains a CAP gracefully when one needs to come down.
Raptor stays stateless apart from its Postgres SSoT so we can roll it without a maintenance window. A new Raptor binary picks up the live placement table on the first request — no warm-up, no cache to rebuild, no in-flight state to migrate. The trade-off is that every allocation is a database round-trip; in practice that costs single-digit milliseconds and buys us boring deploys.
4. CAP — the host
A CAP is a GCE VM running the LILY runtime: a Wasmtime engine, a supervisor that watches lifecycle, and a thin HTTPS terminator that routes <slug>.app.lilylabs.io to the right brick. CAPs are interchangeable. Losing one rebalances onto its neighbours within a heartbeat cycle. CAPs hold no durable user data — anything stateful belongs in a brick that talks to an external store.
Logs and stdout/stderr stream from the runtime back through Raptor and out to lly void logs <id>. Health checks are part of the heartbeat. If your brick traps, the supervisor restarts it and the next request sees a fresh instance.
Side channels
Two services sit beside the main flow. rc.lilylabs.io is the resolver and registry: it hosts install.sh, the lly binary itself, and the manifests for every published plugin. lly plugin install nextjs talks to RC, never to platform. auth.lilylabs.io handles browser-only sign-in and hands the CLI a short-lived access token over a WebSocket. Neither RC nor auth is in the hot path of a request to your deployed app.
What this means for you
You build locally, deploy with one command, and get a URL. The four layers above are the reason that command finishes in seconds: each one is small, narrow, and replaceable. If you want to dig deeper, the bricks concept page explains the bundle format the CLI hands to platform, and the void CLI reference covers the deploy subcommands end-to-end.