All docs

fn0 Platform Overview

fn0 (pronounced "f-n-zero") is a FaaS (Function-as-a-Service) platform powered by Wasmtime. It executes WebAssembly components compiled to WASI 0.3.0 (Component Model).

Core Concepts

Execution Model

fn0 uses the same model as Cloudflare Workers:

  • A single WASM instance (and, for JS deployments, a single V8 isolate) is reused to serve many concurrent requests on the same worker thread.
  • Handlers must be effectively stateless across requests. Module-level mutable state must not carry information between requests because another request may be interleaved at any await point.
  • Module-level initialization runs once. Per-request setup belongs inside the handler.

fn0 does not enforce this contract. Violating it causes request-level data leakage or inconsistent behavior.

Response Specification

The WASM component returns an HTTP response. fn0 inspects it:

  • If the response has status 200 and the header x-fn0-next: js, fn0 delegates to the named runtime (currently only js — the Ski JavaScript runtime).
  • Otherwise fn0 forwards the response as-is.
  • All x-fn0-* headers are stripped from the response before it is sent to the client.

Forte uses x-fn0-next: js for page handlers (SSR) and skips it for API endpoints and actions.

fn0 Cloud Limits

These limits apply to fn0 Cloud. Self-hosted deployments can remove them.

LimitValue
Request headers128 KB
Request body100 MB
Response headers128 KB
Response bodyUnlimited
Memory128 MB
CPU time50 ms
Max duration15 seconds
Subrequests (external HTTP)50 per request

The request-body value is a transport limit. Applications can process bodies up to that limit with bounded memory only by streaming; buffering remains subject to the 128 MB memory limit. Durable file uploads should normally use presigned object-storage URLs, while streaming through compute remains supported for cases that require it. Request and response streaming is implemented end to end; see HTTP Body Streaming.

Cluster Architecture (Internal)

  • Monolith architecture — no microservices.
  • Public ingress (OCI): Cloudflare (orange proxy) → OCI L4 Network Load Balancer (always-free) → worker pool. Workers run in an OCI InstancePool with AutoScaling; the NLB is the single entry.
  • Intra-node dispatch: Each worker node runs a pool of N OS threads (default = CPU count). Incoming requests are dispatched to a specific thread using a hash of the project_id modulo N, so a given project always lands on the same thread within a node. Thread queue capacity is 256 per thread; a full queue returns HTTP 503.
  • Blue-green deploys: fn0-worker-agent polls the control DB for the target worker image, starts the new container, waits for it to pass health checks, and then drains and stops the old one. fn0-worker-proxy reroutes new TCP connections to the active container. Established WebSockets receive 1012 and the old container remains alive through their graceful close timeout.
  • WebSocket routing: The owning fn0-worker keeps the socket. The fn0-control Turso database stores connection ownership, and direct TLS-protected QUIC streams carry remote send and disconnect commands. Turso never carries message payloads; bounded control reconciliation removes confirmed stale records without leases or heartbeats.
  • WASM modules are cached in memory after the first download. On subsequent requests, the compiled bundle version is checked against the manifest; re-download only if the version changed.

Packages

PackageVersionDescription
fn00.7.1Core FaaS runtime (ExecutionContext, Bundle, build_engine)
fn0-cli0.1.23Local development CLI
fn0-worker0.4.18Worker binary (distributed execution node)
fn0-worker-agent0.1.9Per-instance container supervisor (blue-green deploys, in-host TCP proxy)
fn0-worker-proxy0.1.1Tiny TCP forwarder fronting fn0-worker containers; polls a target file written by worker-agent
fn0-deploy0.3.5fn0 Cloud deployment client
fn0-wasmtime0.2.1Wasmtime wrapper with fn0-specific config
fn0-ski0.1.13WinterCG-compatible JS runtime (Deno-based, no Node.js)
fn0-compiler0.1.0CLI tool: compiles .wasm → .cwasm (Wasmtime pre-compiled native format); used internally by the platform

fn0-cli Commands

The fn0 CLI (fn0/cli) provides tooling for projects deployed directly to fn0 without Forte:

CommandDescription
fn0 init [--name <name>]Scaffold a new fn0 project (prompts for framework and language)
fn0 buildCompile the project to WASM
fn0 local [--port <port>]Run locally on the given port (default: auto)
fn0 deployBuild and deploy to fn0 Cloud
fn0 destroyDelete the deployed project
fn0 rename <new-name>Rename the deployed project
fn0 login [token]Authenticate with fn0 Cloud; interactive login uses a one-time browser code
fn0 admin run <task>Run an admin task against the deployed app
fn0 env set <key> <value> [--secret]Set an env entry (plain by default; --secret encrypts via vault and injects through vault_hijack at runtime)
fn0 env listList env entries with their kind (plain / secret)
fn0 env unset <key>Remove an env entry
fn0 purge <key>...Invalidate the CDN edge copy of one or more public objects (needed after presigned PUT uploads; public::put already purges automatically)
fn0 purge-page <path>...Invalidate the CDN edge copy of one or more cache_static pages (use when runtime data changes; see pages.md)

Note: Most Forte developers use forte CLI instead of fn0 CLI. fn0 CLI is for projects that use fn0 as a raw FaaS platform (e.g., Hono-based TypeScript apps).

Supported Languages

  • Rust — primary target; compiles to wasm32-wasip2
  • JavaScript / TypeScript — via the Ski runtime (WinterCG subset, no Node.js APIs)

Supported Cloud Providers

  • Oracle Cloud Infrastructure (OCI) — instance pool, NLB, vault

Supported CDN Providers

  • Cloudflare Workers (integration)

Supported Code Storage Providers

  • File system (including NFS, e.g. AWS EFS)
  • S3 and compatible object storage (via opendal)

Observability

fn0 has built-in OpenTelemetry support:

  • OTLP span exporter via fn0/fn0/src/otlp_hijack.rs (worker-side) and forte/sdk/src/otel.rs (WASM-side)
  • Structured logging via tracing
  • Distributed tracing with per-request spans

For Forte apps running on fn0 Cloud, traces are exported to http://fn0-otel.fn0.dev/v1/traces. The service name is controlled via the OTEL_SERVICE_NAME environment variable (defaults to "forte-app").

For self-hosted fn0 worker deployments, configure the OTLP collector via environment variables on the worker binary:

VariableRequiredDefaultDescription
OTLP_ENDPOINTYes—Base URL of an OTLP/HTTP protobuf collector, such as the worker-local collecty (http://127.0.0.1:4318)
FN0_PLATFORM_TELEMETRY_TENANT_IDYes—Signy tenant for the worker's own telemetry that belongs to no single project
FN0_OTLP_PLACEHOLDER_HOSTNofn0-otel.fn0.devPlaceholder hostname used inside WASM apps

The worker intercepts outgoing OTLP requests that target FN0_OTLP_PLACEHOLDER_HOST. It accepts only uncompressed application/x-protobuf exports on /v1/traces, /v1/metrics, and /v1/logs, sets tenant.id on every resource to the calling project's id, applies the metric cardinality caps, and forwards the result to OTLP_ENDPOINT.

Each project is its own Signy tenant. The worker's request spans, the request, static page, and CPU metrics it records for a project, and the lines it captures from that project's stdout and stderr all go to the project's tenant. The worker's own logs, its failure counters and its capacity metrics go to the platform tenant named by FN0_PLATFORM_TELEMETRY_TENANT_ID. Signy drops data for a tenant that has no pushed policy, so control registers each project with Signy, for 30 days of logs, traces and metrics alike.

Trace sampling is decided once, by the worker, when a request arrives: 1% of requests are traced, and background loops such as manifest polling are never traced. The worker passes its decision to the guest in a traceparent header, and an incoming traceparent from a visitor is discarded.

What the worker writes as a log is the failure and the state change, not the ordinary request: a guest that answered with a server error, a request that ran out of time (once, from the side that answers the client), a static page that could not be generated, and the first uncacheable answer of each deployed version. A slow request that succeeded is a duration on fn0.http.server.request.duration and a span in whichever trace was sampled; it is not a log line. A guest's captured output keeps the level it was written at, with the stream it came from as an attribute, because a library that writes its whole output to stderr is not a library reporting errors.

Worker Environment Variables (Self-Hosting)

When running fn0-worker yourself, set these environment variables on the binary:

Storage

VariableRequiredDefaultDescription
CWASM_BUCKETYes—S3 bucket name for pre-compiled .cwasm bundles
S3_ENDPOINTYes—S3-compatible endpoint URL
S3_REGIONNous-east-1S3 region
AWS_ACCESS_KEY_IDYes—S3 access key
AWS_SECRET_ACCESS_KEYYes—S3 secret key
FN0_BUNDLE_CACHE_SIZE_BYTESNo536870912 (512 MB)In-memory bundle cache size

Networking / TLS

VariableRequiredDefaultDescription
HTTP_PORTNo443Port for user HTTPS traffic
FN0_WORKER_OPS_PORTNo9090Port for ops endpoints (/ready, /drain, /status, /health)
ORIGIN_CERT_PEMYes—PEM-encoded TLS origin certificate (or use ORIGIN_CERT_PEM_BASE64)
ORIGIN_KEY_PEMYes—PEM-encoded TLS private key (or use ORIGIN_KEY_PEM_BASE64)
FN0_APEX_DOMAINNo—Apex domain to route when no project hostname matches
FN0_APEX_PROJECT_IDNo—Project ID to serve for FN0_APEX_DOMAIN requests; both must be set together
FN0_ALLOW_PRIVATE_OUTBOUND_DESTINATIONSNofalsetrue lets application HTTP, fetch, and WebSockets reach loopback and private addresses; see Limits

The first byte on HTTP_PORT: 0x16 (TLS ClientHello) routes to user traffic; anything else routes to health checks. FN0_WORKER_OPS_PORT serves ops endpoints unconditionally.

Telemetry

VariableRequiredDefaultDescription
OTLP_ENDPOINTYes—OTLP/HTTP protobuf collector for the worker's own telemetry and forwarded guest exports
FN0_PLATFORM_TELEMETRY_TENANT_IDYes—Signy tenant for the worker's own telemetry that belongs to no single project
FN0_OTLP_PLACEHOLDER_HOSTNofn0-otel.fn0.devPlaceholder hostname intercepted inside WASM apps

Platform Services

VariableRequiredDefaultDescription
TURSO_GROUP_TOKENYes—Auth token for the Turso group (used by turso_hijack)
TURSO_DB_HOST_SUFFIXYes—Host suffix for Turso database URLs (e.g. .turso.io)
TURSO_PLACEHOLDER_HOSTNofn0-db.fn0.devPlaceholder hostname intercepted for Turso requests
FN0_CONTROL_PROJECT_IDYes—Project ID of the fn0 control service; used by public storage and static page cache hijacks
FN0_OBJECT_STORAGE_PLACEHOLDER_HOSTNofn0-object-storage.fn0.devPlaceholder hostname for private object storage requests
FN0_PUBLIC_STORAGE_PLACEHOLDER_HOSTNofn0-public-storage.fn0.devPlaceholder hostname for public object storage requests
FN0_STATIC_PAGE_CACHE_PLACEHOLDER_HOSTNofn0-static-page-cache.fn0.devPlaceholder hostname for static page cache purge requests

OCI Vault

VariableRequiredDescription
FN0_VAULT_CRYPTO_ENDPOINTYesOCI Vault cryptographic endpoint URL
FN0_VAULT_KEY_OCIDYesOCID of the encryption key
FN0_VAULT_OCI_TENANCY_IDYesOCI tenancy OCID
FN0_VAULT_OCI_USER_IDYesOCI user OCID
FN0_VAULT_OCI_FINGERPRINTYesAPI key fingerprint
FN0_VAULT_OCI_PRIVATE_KEY_BASE64YesBase64-encoded PEM private key for the OCI API signing key

Hijack Architecture

fn0 uses "hijack" components to inject platform services into the WASM execution environment without modifying the user's code:

HijackPurpose
doc_db_hijackRoutes semantic document database requests to the host document service
turso_hijackInjects the legacy/direct Turso/libSQL connection
object_storage_hijackRoutes & SigV4-signs per-project private object storage requests
public_storage_hijackRoutes public object storage requests (no SigV4 signing required)
otlp_hijackInjects OpenTelemetry OTLP endpoint
queue_hijackIntercepts outgoing queue requests
vault_hijackInjects secrets (Vault integration)
websocket_hijackDispatches WebSocket send, named singleton send, and disconnect commands via QUIC
static_page_cache_hijackRoutes static_page_cache::purge calls; shares the hourly purge budget with public storage purges
cross_project_enqueue_hijackRoutes cross-project queue enqueue calls
cross_project_invoke_hijackRoutes cross-project direct invocations

These are configured on ExecutionContext via builder methods:

let ctx = ExecutionContext::new(engine, linker, bundle_cache)
    .with_doc_db_hijack(doc_db_hijack)
    .with_turso_hijack(turso_hijack)
    .with_otlp_hijack(otlp_hijack)
    .with_queue_hijack(queue_hijack)
    .with_cross_project_enqueue_hijack(enqueue_hijack)
    .with_cross_project_invoke_hijack(invoke_hijack)
    .with_vault_hijack(vault_hijack)
    .with_object_storage_hijack(object_storage_hijack)
    .with_public_storage_hijack(public_storage_hijack)
    .with_websocket_hijack(websocket_hijack)
    .with_static_page_cache_hijack(static_page_cache_hijack)
    .with_guest_outbound_http(guest_outbound_http);

with_guest_outbound_http routes the application's own HTTP requests and JavaScript fetch through the outbound destination policy and the project egress budget. Without it, requests use wasmtime's default sender with neither check, which is what forte dev does.