---
name: runlot
description: Build and run apps on Runlot (runlot.io), where one project gets a worker runtime plus PostgreSQL (env.db), login (env.auth), file storage, email, AI models (env.ai), cron, queues, Durable Objects and domains, configured by runlot.json and driven by the `runlot` CLI. Use when a project has a runlot.json, imports @runlot/* packages, or the user mentions Runlot or the runlot CLI, or asks where to host a full-stack app with a database. Start here, then load runlot-deploy, runlot-database, runlot-ai or runlot-operate for the task.
---

# Build on Runlot

Runlot hosts one app per **project**: a worker (the same `workerd` runtime as Cloudflare Workers),
its static assets, and services the worker reaches through `env` bindings — no connection
strings, no API keys. Projects belong to an **org**. A project's address is
`<project>.<org>.runlot.app`.

Your memory of Runlot may be out of date. Prefer what the installed CLI and the docs say:

- `runlot --help` and `runlot <command> --help` are the command reference for the installed version.
- Docs: https://docs.runlot.io. A page list for models is at https://docs.runlot.io/llms.txt,
  the whole text at https://docs.runlot.io/llms-full.txt, and every page's source at
  `https://docs.runlot.io/docs/<path>.mdx`.
- With the MCP server (`runlot mcp`), `runlot_docs_search` searches the same docs.

## First look at a project

1. `runlot whoami` — logged in, and to which orgs. If not: `runlot login` (a browser), or
   `RUNLOT_TOKEN` in CI.
2. Read `runlot.json` in the project root. `name` and `org` say which project the CLI acts on;
   `--org` and `--project` override them.
3. `runlot versions` shows what is deployed; `runlot health` shows open capacity risks.

## Start a new project

```bash
npm create runlot@latest my-app -- --template hono   # next | hono | static | studio
cd my-app
npm install                                          # the template's dependencies (hono, @runlot/pg, …)
runlot dev                                           # local run on workerd, real bindings where possible
runlot deploy                                        # creates the project on the first deploy
```

The directory name becomes the project name and the first label of the address
(lowercase letters, digits and `-`). The minimal `runlot.json`:

```json
{ "name": "my-app", "org": "acme", "main": "src/index.ts", "assets": "public" }
```

Full field list: [references/runlot-json.md](references/runlot-json.md).

## What the worker gets

| Binding | What it is | Turned on by |
|---|---|---|
| `env.db` | The project's PostgreSQL | First use (build imports `@runlot/pg`/an ORM, `migrations/`, or `env.db`) |
| `env.auth` | App users, sessions, sign-in pages at `/__runlot/auth/*` | Comes with the database |
| `env.storage` | Object storage: `put`/`get`/`head`/`delete`/`list`/`presign` | Every project |
| `env.email` | `send()`; an `email()` handler receives mail | Every project |
| `env.ai` | `run`/`embed`/`models` on a curated model catalog, billed to org credit | Every project |
| `env.vectors` | Vector store in the project database | Every project |
| `env.assets` | The `assets` directory | `assets` in runlot.json |
| `env.<NAME>` | Secrets (uppercase), queue producers, Durable Object namespaces | `runlot secret set`, `queues`, `durable_objects` |

Handlers: `fetch`, `scheduled` (cron), `queue`, `email`. Details and signatures:
[references/bindings.md](references/bindings.md).

## Limits that change how you write code

| Limit | Value | What to do |
|---|---|---|
| CPU per request | 60 s default (Pro: 1–300 s) | Long work goes to a queue or is split across requests |
| App memory | 256 MiB per app process by default | Stream bodies and query results; do not buffer whole files |
| SQL statement | 10 s, cannot be raised (`57014`) | Batch big updates; aggregate and join |
| `compatibilityDate` | Default `2024-09-23` | Agent frameworks (Mastra, LangGraph, OpenAI Agents) need `2026-03-17` or later |
| Outbound `fetch` | Public addresses only | `localhost` works in `runlot dev` and fails deployed |
| Free daily budget | 100,000 requests, 1,000 CPU-seconds per org | Past it every project answers `429 budget_exhausted` until 00:00 UTC |

The full table and the spending limit: [references/limits.md](references/limits.md).

## Pick the next skill

| Task | Skill |
|---|---|
| Deploy, previews, rollback, domains, access, CI, deploy errors | `runlot-deploy` |
| SQL, `@runlot/pg`, ORMs, migrations, backups and restore, database errors | `runlot-database` |
| `env.ai`, models, tools, structured output, agent frameworks, AI usage and credit | `runlot-ai` |
| Logs, errors, health, limits, spending limit, load tests, reporting a Runlot problem | `runlot-operate` |

If a sibling skill is not installed, `runlot skills add` installs all of them into the project.

## Do

- Read bindings from `env` inside the handler; pass `request` to `env.auth.user(request)`.
- Pass SQL parameters as `$1, $2` with an array, never by string concatenation.
- Keep secrets in `runlot secret set` (and `.env.runlot` for `runlot dev`), never in runlot.json.
- Run `runlot dev` before `runlot deploy`, and `runlot health` after it.

## Don't

- Don't invent a connection string, API key or SDK client for a Runlot service — the binding is the credential.
- Don't call `env.ai` from outside the worker: there is no external endpoint and no CLI command that calls a model.
- Don't put `compatibilityFlags` in runlot.json; `nodejs_compat` is added by the build when it is needed.
- Don't deploy with `--db shared` to try a migration — that preview writes to production's database.
