Skip to main content

Self-hosting Limitr

There is no Limitr server to deploy. The engine is the @formata/limitr package, and it runs inside your app, in Node, Bun, Deno, or the browser, wherever your app already runs. No Docker image, no Kubernetes chart, no binary, no port to open. Self-hosting Limitr is:

npm i @formata/limitr
import { Limitr } from '@formata/limitr';

const limitr = await Limitr.new(policy); // the policy: a document in your repo

That's the whole deployment. Every allow, check, reserve, and settle runs in your process, with no network call, and the engine never phones home. The same engine runs in Limitr Cloud: the only difference is who runs the parts around it.

What you own​

Self-hosted, the engine is the whole product in your process, and you run the parts around it:

PartSelf-hostedWith Limitr Cloud
The policyA document in your repo, reviewed and deployed like codeEdited and published from Cloud, live everywhere with no deploy
Customer stateYour database: save it from events, load it on startStored and synced across your servers
Billing periodsYour subscriptions (setCustomerPeriod), or the calendarFrom Cloud's subscriptions and contracts
BillingFrom the events, with your Stripe or whatever you bill withLedgers and invoices through your own Stripe
Contracts and dealsRules in the policy, assigned in your codeSet up by sales or finance in Cloud
AnalyticsYour own, from the eventsUsage, cost, and margin by customer, feature, model, or task

Nothing here is a lesser engine. The policy and the calls are the same, so you can start self-hosted and move to Cloud, or the other way, without changing your integration.

System requirements​

  • A JavaScript runtime: Node, Bun, Deno, or a browser, from the same package. The engine is WebAssembly, so there's nothing to compile and nothing platform-specific.
  • Memory: one engine holds its policy and its customers in memory, and grows with customers, not with calls (runtime). A long-running engine stays flat.
  • No other services. The engine makes no network calls unless the policy's own capabilities do, and those are off until you allow them.

Running it in production​

  1. One engine per process. Create it once at startup and share it, since it holds customer state in memory.
  2. Save customer state. Save a customer's record when its events say it changed, and load records on start or on first use. Save customer state shows the loop, including running more than one server.
  3. Bill from the events. meter-overage, cap-limit, and the threshold events carry the price and cost of each call. Bill from events covers what to bill from and who pays.
  4. Keep the policy in version control. It's a plain document. Review changes like code, and test it in CI with stof test or from TypeScript.
  5. Keep the sandbox closed. Limitr.new policies have no file system or network access. If a capability needs the network, allow only the hosts it calls: limitr.doc.allowHttp(['api.example.com']) (runtime).
  6. Scale by process. Every process runs its own engine against the shared customer state in your database. There's no engine cluster to size: capacity is your app's capacity.

Running it in your app, in the tutorial, puts all of this together in one runnable example.