Your box. Your control plane.
One script on a Linux machine installs an edge with automatic TLS, a sandbox, a control plane, a dashboard and the runtime. Your source stays on your laptop; only the compiled binary is uploaded.
Alpha. It runs this website and holds real deployments, but the storage format and the CLI contract can still change between minor versions.
One box. Room to grow.
- Apps run side by side
- Each app has its own compiled sprout. Deploy a new version or roll back to a previous one.
- Data stays put
- KV, D1, R2 and queue resources outlive app versions. A redeploy keeps their data.
- You control the node
- The control plane handles deployments, rollbacks and logs, with a dashboard on your host.
Install in four steps
A Linux box, a domain pointed at it, and the CLI on your laptop.
-
1
Get a Linux box
A Linux VPS or home server with a domain you control. Check the installation requirements before choosing a machine.
-
2
Run the installer
It provisions Caddy with automatic TLS, a bubblewrap sandbox, a firewall, one admin identity, and the control, edge and dashboard services. It pauses once so you can add a DNS record.
$ curl -fsSL https://sproutboat.com/install.sh | sudo bash -
3
Point the CLI at your endpoint
A device-code browser flow, or the admin token the installer printed. The CLI keeps credentials per endpoint, so you can hold several.
$ sproutboat login --api-url https://control.your-domain.com -
4
Deploy
Porffor compiles your handler on your machine. The CLI uploads only the binary; your node runs it behind Caddy. From here on it is just sproutboat deploy.
$ sproutboat deploy
Supported bindings
What a deployment gets on a self-hosted node. Each example is a runnable project.
| Binding | What it is | Example |
|---|---|---|
| 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 expressions | cron |
| Static assets | files served from a directory via env.ASSETS.fetch() | assets |
| Analytics Engine | writeDataPoint() and query() | analytics |
| Rate limiting3 | env.NAME.limit({ key }) -> { success }, a fixed window per key | ratelimit |
| Vars & secrets | config values, and secrets that never enter the binary | vars-secrets |
| Outbound fetch | fetch() to an allowlisted host, http and https | outbound-fetch |
| Service bindings | 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. | service-bindings |
| ctx.waitUntil4 | work that outlives the response, on fetch/scheduled/queue | waituntil |
| Streams5 | ReadableStream, WritableStream, TransformStream, and response bodies streamed as they are writtenServer-Sent Events and long responses work; a client that disconnects cancels the stream. | - |
| HTMLRewriter6 | 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. | - |
- An object is held whole in memory both ways. Keep them small for now; see the note in the docs.
- An alarm is not yet guaranteed to avoid running alongside a request to the same object.
- One node uses a fixed window; requests near a window boundary can exceed the intended per-interval rate.
- In-process drain, capped at 25s.
- Request bodies are still read whole (up to 1 MiB).
- Rewrites the whole body at once rather than streaming it; no :nth-child-style pseudo-classes.