One file. No runtime.

sproutboat build --standalone compiles your handler into a native Linux executable. Copy it to a Linux host, set PORT, and it serves. No Node, Bun or container on the target.

$ sproutboat build --standalone

Alpha. Built on Porffor alpha-17 (731f1b2), an ahead-of-time JavaScript compiler that is itself early. Node APIs (nodejs_compat) are the gap you are most likely to hit.

What ships in the file

Your code and the pieces it needs to run, compiled into one executable.

State lives beside it

On first run, the executable creates <name>.data/ for persistent state: one SQLite file for the shared binding tables, a separate file for each D1 database, and files for R2 object data. It uses the same directory layout as sproutboat dev.

Native handler
Porffor compiles your JavaScript handler to machine code.
Local bindings
KV, D1, R2, queues, Durable Objects and analytics use local storage backed by SQLite.
Embedded SQLite
The database engine is compiled into the executable.
Verified HTTPS
BearSSL and Mozilla root certificates are included for outbound fetch() requests.
Static assets
Your static files are embedded at build time, up to 8 MB in total.

Supported bindings

Every binding a deployed sprout has, except service bindings. Each example is a runnable project.

BindingWhat it isExample
Handlers export default { fetch }: one request in, one Response out hello
KV key-value store: get / put / delete / list kv
D1 SQL database: prepared statements, batch, transactions d1
R21 S3-style object storage with sha256 etags r2
Queues a producer handle plus a queue(batch) consumer, with retries queue
Durable Objects2 addressable single-threaded instances, each with its own storageIncludes alarms: setAlarm / getAlarm / deleteAlarm and the alarm() handler. durable-object
Cron triggers scheduled(event), fired on cron expressionsA standalone binary runs its own timers and refuses triggers arriving over the network. cron
Static assets3 files served from a directory via env.ASSETS.fetch() assets
Analytics Engine writeDataPoint() and query() analytics
Rate limiting4 env.NAME.limit({ key }) -> { success }, a fixed window per key ratelimit
Vars & secrets config values, and secrets that never enter the binaryA standalone binary reads secrets from the environment and throws the first time a handler touches one that's missing. vars-secrets
Outbound fetch fetch() to an allowlisted host, http and httpsTLS in a standalone binary is BearSSL with the Mozilla root set compiled in. outbound-fetch
Service bindingsNot supported env.PEER.fetch() to another deployment on the same nodeA service call routes through the node's edge, which a single binary does not have. -
ctx.waitUntil5 work that outlives the response, on fetch/scheduled/queue waituntil
Streams6 ReadableStream, WritableStream, TransformStream, and response bodies streamed as they are writtenServer-Sent Events and long responses work; a client that disconnects cancels the stream. -
HTMLRewriter7 Rewrite HTML responses with element, text, comment and document handlersOutput matches lol-html, the engine workerd uses. -
Compression streams CompressionStream and DecompressionStream: gzip, deflate, deflate-rawOutput decompresses with zlib, and zlib's output decompresses here. -
  1. An object is held whole in memory both ways. Keep them small for now; see the note in the docs.
  2. An alarm is not yet guaranteed to avoid running alongside a request to the same object.
  3. Compiled into a standalone binary, capped at 8 MB.
  4. One node uses a fixed window; requests near a window boundary can exceed the intended per-interval rate.
  5. In-process drain, capped at 25s.
  6. Request bodies are still read whole (up to 1 MiB).
  7. Rewrites the whole body at once rather than streaming it; no :nth-child-style pseudo-classes.

What differs from a deployed sprout

Each one follows from there being nothing else on the machine.

Inbound TLS is not its job

It serves plain HTTP on $PORT. Put Caddy, nginx or a tunnel in front to terminate TLS.

Outbound TLS is

fetch("https://…") verifies against the Mozilla root set through BearSSL, compiled in.

Triggers stay internal

Cron ticks, queue batches and alarms run on timers inside the process. Triggers arriving over the network are refused.

No service bindings

A worker-to-worker call routes through a node's edge, and a standalone binary has no node.