Skip to content
sproutboat
Esc
↑↓navigate↵open⌘Jpreview
On this page

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/hello
npx sproutboat build --standalone      # a few MB
PORT=3000 ./dist/hello

This 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 plus SB_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 $PORT and $SB_DATA_DIR: they share the port through SO_REUSEPORT, and SQLite’s WAL mode handles the shared files.

Was this page helpful?