Cloudflare
Multi-Tenant Trilogy
Dynamic Workers, Durable Object Facets, and Dynamic Workflows — the three-level stack for building secure, isolated, stateful multi-tenant platforms at the edge.
Request Lifecycle
Every tenant request flows through the Loader Worker, which dispatches to the appropriate execution model.
Three Levels of Isolation
Each level adds capabilities while preserving full tenant isolation. Start stateless, add per-tenant state, then compose into durable workflows.
Dynamic Workers
StatelessEach tenant runs their own Worker script dispatched through a shared namespace. Zero cold-start overhead — Workers are warm and ready within single-digit milliseconds. No per-tenant VM boot, no container spin-up, no orchestration.
- Per-tenant code isolation via Dispatch Namespaces
- Sub-millisecond cold starts — Workers are pre-warmed in the isolate pool
- CPU/memory limits enforced per-tenant at the isolate boundary
- No shared state — each invocation is a clean sandbox
- Scale to thousands of tenants on a single dispatch namespace
// Loader Worker — routes tenant requests to their sandboxed Worker
import { Router } from "itty-router";
interface Env {
TENANT_WORKERS: DynamicNamespace;
TENANT_REGISTRY: KVNamespace;
}
const router = Router();
router.get("/api/tenants/:tenantId/*", async (req, env: Env) => {
const { tenantId } = req.params;
const config = await env.TENANT_REGISTRY.get(tenantId, "json");
// Resolve the tenant's dynamic worker binding
const worker = env.TENANT_WORKERS.get(tenantId);
if (!worker) return new Response("Tenant not found", { status: 404 });
// Dispatch — full subrequest isolation
return worker.fetch(req.original);
});
export default { fetch: router.handle };Durable Object Facets
StatefulEach tenant gets their own Durable Object instance with an embedded SQLite database. The Facet pattern wraps the DO with a schema-first API, giving tenants their own isolated, consistent, single-threaded state runtime.
- Durable Object per tenant — deterministic idFromName() routing
- Embedded SQLite — per-tenant isolated database with no connection pooling
- Strong consistency — single-threaded execution eliminates race conditions
- Transactional reads and writes within the tenant's storage shard
- Automatic persistence — state survives Worker restarts and region failovers
// Durable Object Facet — per-tenant stateful runtime
import { DurableObject } from "cloudflare:workers";
interface Env {
TENANT_FACET: DurableObjectNamespace<TenantFacet>;
}
export class TenantFacet extends DurableObject {
private sql: SqlStorage;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
this.sql = ctx.storage.sql;
// Idempotent schema init
this.sql.exec(`
CREATE TABLE IF NOT EXISTS tenant_store (
key TEXT PRIMARY KEY,
value TEXT,
created_at TEXT DEFAULT (datetime('now'))
)
`);
}
async query(sql: string, params?: unknown[]) {
const result = this.sql.exec(sql, params);
return result.toArray();
}
async set(key: string, value: unknown) {
this.sql.exec(
"INSERT OR REPLACE INTO tenant_store (key, value) VALUES (?, ?)",
[key, JSON.stringify(value)],
);
}
async get(key: string) {
const row = this.sql.exec(
"SELECT value FROM tenant_store WHERE key = ?", [key]
).one();
return row ? JSON.parse(row.value as string) : null;
}
}Dynamic Workflows
DurableLong-running, durable execution workflows where tenants inject their own logic. Each step is a checkpoint — if the Worker crashes mid-flow, the workflow resumes from the last completed step, not from the beginning.
- Step-based durable execution with automatic checkpointing
- Dynamic customer-defined logic via script injection at each step
- Built-in retries with exponential backoff per step
- Pause/resume via API — workflows can wait for external signals
- Visualize execution history in the Cloudflare Dashboard
// Dynamic Workflow — durable multi-step execution
import { WorkflowEntrypoint } from "cloudflare:workers";
interface Env {
TENANT_WORKFLOW: WorkflowBinding;
}
interface Params {
tenantId: string;
customerLogic: string; // user-defined step function
payload: Record<string, unknown>;
}
export class TenantWorkflow extends WorkflowEntrypoint<
Env, Params
> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const { tenantId, customerLogic, payload } = event.payload;
// Step 1 — Validate (durable checkpoint)
const validated = await step.do("validate", async () => {
return validatePayload(payload);
});
// Step 2 — Execute customer-defined logic
const processed = await step.do("process", async () => {
const fn = new Function("data", customerLogic);
return fn(validated);
});
// Step 3 — Downstream effects with retries
const result = await step.do("webhook", async () => {
return fetch(processed.webhookUrl, {
method: "POST", body: JSON.stringify(processed.data),
});
}, { retries: { limit: 3, delay: "5 seconds" } });
return result;
}
}Interactive Simulation
Execute each layer of the multi-tenant stack and observe the runtime behavior.
Console Log
Runtime Metrics
Single-Digit Milliseconds
Every level of the trilogy operates in the 1-50ms range. Compare that to booting a Firecracker microVM for each tenant — which takes 150-500ms before any code even runs.
When to Use Each Level
Stateless Compute
Tenants run custom processing logic — webhooks, transforms, data enrichment. No long-lived state needed.
Request-response handlers, webhooks, serverless functions, data transformations.
Stateful Runtimes
Each tenant needs their own database with schema control. Consistent reads/writes with full ACID guarantees.
Multi-tenant SaaS backends, per-customer databases, session stores, configuration services.
Durable Pipelines
Multi-step business processes with human-in-the-loop pauses. Tenants inject custom step logic at runtime.
Approval workflows, order processing, data pipelines, onboarding automation, async ETL.
Built for Platform Engineers
VXBE composits the Cloudflare Multi-Tenant Trilogy to run edge-native agent platforms. Each level isolates tenant compute, state, and execution — without the overhead of containers or VMs.