---
title: Architecture
description: "How the CLI builds a native sprout and how the platform routes requests, bindings, and triggers."
---

Sproutboat has two main paths: the CLI builds a native executable on your machine, and the platform runs that artifact. [Porffor](https://porffor.dev) compiles the bundled JavaScript. Sproutboat adds bundling, capability checks, the handler prelude, HTTP hosting, bindings, and deployment. These layers together determine [runtime compatibility](/concepts/compatibility).

## CLI build path

The CLI reads your handler, its static imports, and `sproutboat.jsonc`. It runs Bun's bundler first, so the capability check sees dependency code too. The wrapper then adds the Sproutboat prelude and selects a binding transport before Porffor compiles the program.

<ComarkArchitecture kind="cli" />

Porffor turns the wrapped handler into C and links it with the native HTTP host. For a deployable `linux-x86_64` build, Zig produces a static musl executable. The CLI uses a pinned, hash-verified Porffor source with Sproutboat's local patches, and seeds Porffor's uWebSockets dependency from a prebuilt archive. A host-target build instead uses the local C toolchain; `sproutboat dev` uses that path for fast local rebuilds.

The output is content-addressed. Its manifest records the target, capability profile, compiler version, hashes, and binary size. Managed builds carry binding names in `bindings.json`; static assets are sidecars. Standalone builds compile SQLite, TLS support, and small assets into the binary. `deploy` uploads the artifact, not your source or `node_modules`.

## Platform path

The control API validates and stores immutable artifacts, records deployment versions, and publishes the active hostname routes. A rollback changes the active route without rebuilding the sprout. Caddy handles public TLS; the edge reads the route snapshot and either serves a matching static asset or forwards the request to the active sprout.

<ComarkArchitecture kind="platform" />

The supervisor assigns a loopback port to each active sprout. It starts the broker first, then starts the sprout under `bubblewrap` with a read-only artifact directory, private namespaces, and resource limits. The edge sends handler requests over loopback HTTP. HTTP-only sprouts can be evicted after idling and restarted on the next request; routes with cron, queues, or alarms stay available for timed work.

The sprout calls `env.*` through a token-gated loopback connection to its broker. The broker handles storage, secrets, outbound `fetch`, service bindings, and trigger delivery. KV, D1, R2 and queue data belong to account-level resources, not to a deployment version, so it survives a redeploy. See [Bindings](/bindings) for the API exposed to handlers.

## Standalone path

`sproutboat build --standalone` selects the embedded binding transport. The executable listens on `$PORT` and stores local binding state under `$SB_DATA_DIR`; no control plane or broker runs beside it. Public TLS needs a reverse proxy or tunnel, and service bindings need the platform's edge. See [Standalone binaries](/get-started/standalone) for the full differences.

Both modes run code compiled by Porffor. Test packages in the native sprout, and test binding behavior in the mode you intend to deploy.
