Standalone binaries
Run Sproutboat without a platform: one executable with its bindings compiled in.
sproutboat build --standalone emits one executable with its bindings, SQLite and TLS compiled in. Copy it to a Linux x86-64 machine and run it. The target needs no broker, Node, Bun or container.
bunx sproutboat build --standalone # a few MB
PORT=3000 ./dist/hellonpx sproutboat build --standalone # a few MB
PORT=3000 ./dist/helloThis suits a Raspberry Pi-class home server, a single VM, or any setup where you want to own the process directly. The same handler deploys to a platform later with sproutboat deploy, unchanged.
Add --target host to build for the machine you are on instead, to run it locally.
Configuration
A standalone binary reads no command-line arguments. Everything arrives through the environment:
| Variable | Purpose |
|---|---|
PORT |
Port to listen on. Default 8080. |
SB_DATA_DIR or SPROUTBOAT_DATA |
Where state lives. Default ./<name>.data. |
SB_TRUSTED_PROXIES |
Comma-separated reverse-proxy CIDRs or IPs. When the peer is one of them, request.cf.clientIp resolves from X-Forwarded-For (right-most untrusted entry); otherwise the header is ignored. |
SB_CA_BUNDLE |
A PEM file of extra certificate authorities, trusted on top of the compiled-in Mozilla roots. |
SB_REQUEST_BODY_MAX |
Largest inbound request body, in bytes. Default 1 MiB. |
SB_FETCH_MAX_BYTES |
Largest outbound fetch() response body, in bytes. Default 32 MiB. |
SB_EGRESS_ALLOW |
Private addresses fetch() may reach, such as a database on the same host: * or a comma-separated list of exact addresses. See Outbound fetch. |
| (secrets) | Each secret is read from the environment variable of the same name. A missing one does not stop the binary from starting; reading it throws. |
State on disk
<name>.data/
store.sqlite KV, R2 metadata, queues, Durable Object storage and alarms, Analytics Engine, rate limits, cache
r2-blobs/ R2 object bytes, one file per object
d1/<binding>.sqlite one file per D1 database
backups/ D1 backup() snapshots
Each D1 database is its own file because it runs your SQL: a CREATE TABLE kv (...) in a handler would otherwise collide with Sproutboat’s own tables. sproutboat dev uses the same layout.
Differences from a platform deployment
- Plain HTTP inbound. It serves HTTP on
$PORT. Put Caddy, nginx or a tunnel in front for HTTPS. - Verified TLS outbound.
fetch("https://…")verifies against the compiled-in Mozilla roots plusSB_CA_BUNDLE. - Internal triggers. Cron, queue batches and alarms run on timers inside the process. Trigger requests from the network are refused.
- No service bindings. They route through the platform’s edge.
- Assets compile in, up to 8 MB in total. For more, store files in R2 or put a web server in front.
- One thread. The event loop overlaps connection I/O, but the handler and every binding call run one at a time, so throughput per process is about
1 / handler time. For more, run several copies with the same$PORTand$SB_DATA_DIR: they share the port throughSO_REUSEPORT, and SQLite’s WAL mode handles the shared files.