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.
Your source stays on your machine. Deploy uploads the compiled binary. Read the deploy lifecycle

Install in four steps

A Linux box, a domain pointed at it, and the CLI on your laptop.

  1. 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. 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. 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. 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.

BindingWhat it isExample
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. -
  1. An object is held whole in memory both ways. Keep them small for now; see the note in the docs.
  2. An alarm is not yet guaranteed to avoid running alongside a request to the same object.
  3. One node uses a fixed window; requests near a window boundary can exceed the intended per-interval rate.
  4. In-process drain, capped at 25s.
  5. Request bodies are still read whole (up to 1 MiB).
  6. Rewrites the whole body at once rather than streaming it; no :nth-child-style pseudo-classes.