From fetch handler to native binary.
Sproutboat takes a Cloudflare Workers handler, compiles it ahead of time with Porffor, and runs the result as a native process. On Sproutboat Cloud the compile happens on our build servers. Here is each step, what runs where, and what is supported.
- 01
Write a handler
The Workers module shape:
export default { fetch }, plusscheduledandqueueif you need them. Bindings are declared insproutboat.jsonc.{ "name": "notes", "main": "src/index.js", "compatibility_date": "2026-09-07", "kv_namespaces": ["NOTES"] } - 02
Bundle and upload
npx sproutboat deploybundles your handler and uploads the bundle, usually a few kilobytes. Bindings without an id get their own storage on the first deploy, and the id is written back tosproutboat.jsonc.✓ provisioned kv notes-notes → kv_4f1c9a20b7e3 ✓ bundled notes · 1.2 KB ✓ uploaded the bundle to Sproutboat Cloud
- 03
Build on Sproutboat
Our build servers compile the bundle ahead of time with Porffor into native code: no JIT, no engine to start. Each result is cached by a hash of its input, so the same input is never compiled twice.
Community and standalone builds run the same compiler on your own machine.
✓ built on Sproutboat input 7c1e9a04: cache miss, compiled # deploy the same input again: input 7c1e9a04: cache hit, reused
- 04
Serve
On Cloud the app answers at
<app>.<account>.sproutboat.app, and custom domains are free. Every deploy is a new version you can roll back to.🌱 Deployed notes https://notes.acme.sproutboat.app version 3f9c2a71 - 05
Run it three ways
Cloud runs your apps on sproutd, our own runtime: each app in its own sandbox, asleep when idle and awake again on the next request. Community runs your apps on one machine you own. A standalone binary carries its bindings, SQLite and TLS inside one executable.
sproutboat deploy # Cloud or Community sproutboat build --standalone # one executable
What happens to a request.
Every app on Sproutboat Cloud is its own sandboxed process. An idle one sleeps, so a quiet app costs almost nothing, and the next request wakes it.
Bindings, and their limits.
Cloud and Community run the same deployments, so they share a column. Every row has an example the CLI builds and calls, except service bindings, which need two deployments.
| Binding | Cloud and Community | Standalone binary |
|---|---|---|
| Handlersexport default { fetch }: one request in, one Response out | Supported | Supported |
| KVkey-value store: get / put / delete / list | Supported | Supported |
| D1SQL database: prepared statements, batch, transactions | Supported | Supported |
| R2 1S3-style object storage with sha256 etags | Supported | Supported |
| Queuesa producer handle plus a queue(batch) consumer, with retries | Supported | Supported |
| Durable Objects 2addressable single-threaded instances, each with its own storage | Supported | Supported |
| Cron triggersscheduled(event), fired on cron expressions | Supported | Supported |
| Static assets 3files served from a directory via env.ASSETS.fetch() | Supported | Supported |
| Analytics EnginewriteDataPoint() and query() | Supported | Supported |
| Rate limiting 4env.NAME.limit({ key }) -> { success }, a fixed window per key | Supported | Supported |
| Vars & secretsconfig values, and secrets that never enter the binary | Supported | Supported |
| Outbound fetchfetch() to an allowlisted host, http and https | Supported | Supported |
| Service bindingsenv.PEER.fetch() to another deployment on the same node | Supported | Not supported |
| ctx.waitUntil 5work that outlives the response, on fetch/scheduled/queue | Supported | Supported |
| Streams 6ReadableStream, WritableStream, TransformStream, and response bodies streamed as they are written | Supported | Supported |
| HTMLRewriter 7Rewrite HTML responses with element, text, comment and document handlers | Supported | Supported |
| Compression streamsCompressionStream and DecompressionStream: gzip, deflate, deflate-raw | Supported | Supported |
- 1. R2: An object is held whole in memory both ways. Keep them small for now; see the note in the docs. Read the docs
- 2. Durable Objects: An alarm is not yet guaranteed to avoid running alongside a request to the same object. Read the docs
- 3. Static assets: Compiled into a standalone binary, capped at 8 MB. Read the docs
- 4. Rate limiting: One node uses a fixed window; requests near a window boundary can exceed the intended per-interval rate. Read the docs
- 5. ctx.waitUntil: In-process drain, capped at 25s.
- 6. Streams: Request bodies are still read whole (up to 1 MiB).
- 7. HTMLRewriter: Rewrites the whole body at once rather than streaming it; no :nth-child-style pseudo-classes.
Examples live in sproutboat-cli/examples.
The same Worker on three runtimes.
A standalone sprout next to workerd and Node, with the raw samples published.
| Node | workerd | Sproutboat | |
|---|---|---|---|
| Idle memory (PSS) | 52.3 MiB | 46.5 MiB | 7.44 MiB |
| Start to first correct response | 247.7 ms | 53.6 ms | 12.0 ms |
| p95 latency, JSON orders | 5.51 ms | 5.29 ms | 2.58 ms |
| p95 latency, 10% wait on a 50 ms upstream | 55.2 ms | 53.65 ms | 56.81 ms |
| CPU time per 1,000 requests | 2,585 ms | 1,654 ms | 1,159 ms |
The same Worker-style app on Node, workerd and a Sproutboat v0.18.0 executable on one Linux host, medians of 3 rounds at 50 requests/s, 2026-10-20 (raw samples and method). On CPU-heavy code, workerd’s JIT is still about 5× faster in our early sproutd tests.
Sproutboat Cloud is in closed alpha.
Join the waitlist and we’ll send one email when your invite is ready. Nothing else.
We’ll email you once, when your invite is ready.