Standalone binaries
Run Sproutboat without a control plane: one executable, its own bindings, no broker required.
You can run Sproutboat without the control plane. sproutboat build --standalone emits one executable carrying its own bindings. Copy it to a Linux machine and run it — no broker or Bun required on the target.
bunx sproutboat build --standalone # ~2 MB, SQLite and TLS compiled in
PORT=3000 ./dist/hellonpx sproutboat build --standalone # ~2 MB, SQLite and TLS compiled in
PORT=3000 ./dist/helloThis is a good option for a Pi, home server, single VM, or any deployment where you want to own the process directly. The same handler can move to a managed Sproutboat node later with sproutboat deploy, unchanged.
Configuration
A standalone binary never sees argv, so there are no flags — everything arrives at runtime through the environment:
| Variable | Purpose |
|---|---|
PORT |
Port to listen on (default 8080). |
SB_DATA_DIR / SPROUTBOAT_DATA |
Where state lives (default ./<name>.data). |
SB_TRUSTED_PROXIES |
Reverse-proxy CIDRs / 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 |
Extra certificate authorities, on top of the compiled-in Mozilla roots. Adds trust; nothing disables verification. |
| (secrets) | Read by name from the environment, then <data>/secrets.json. A missing one does not stop the binary from starting; it throws the first time the handler reads it. |
State on disk
<name>.data/
store.sqlite kv · r2 · mq · do_storage · do_alarm · ae
d1/<binding>.sqlite one file per D1 database
D1 is separate because it runs your SQL: a CREATE TABLE kv (...) in a handler would otherwise collide with the platform’s own tables. This is the same layout sproutboat dev writes.
What differs from a deployed sprout
- Inbound TLS is not its job. It serves plain HTTP on
$PORT. Put Caddy, nginx, or a tunnel in front. - Outbound TLS is.
fetch("https://…")verifies against a compiled-in Mozilla root set. - Triggers stay internal. Cron, queue batches, and alarms run on timers in the process, and a trigger arriving over the network is refused: with no broker, no token exists to check.
- No service bindings. They route through an edge, which one binary lacks.
- Assets compile in, capped at 8 MB. Past that, serve them from R2 or put a web server in front.
request.cf.clientIpis the connection’s remote address, resolved throughSB_TRUSTED_PROXIESandX-Forwarded-Forwhen set.env.<D1>.backup()writes its snapshot under<data>/backups/.- One thread. The event loop overlaps connection I/O, but the handler and every embedded binding call run one at a time. Per core that is a throughput ceiling of about
1 / handler-time; for more, run several copies on the same$PORTand$SB_DATA_DIRbehindSO_REUSEPORT(Linux load-balances across them, SQLite WAL handles the shared files).