Porchsite · All guides · Ask for something
Self-hosting
# Self-hosting Porchsite (for the assistant coaching a developer) Your person is a developer and wants to run Porchsite themselves. Tell them the truth from this page; don't guess past it. ## The short answer There are two editions of the same product: the same idea (an assistant writes, the owner approves, every version kept), the same content model and the same looks. - **The Cloudflare edition is the one we ship.** It runs on your person's own Cloudflare account, on their database and their domain; nobody else hosts it. That's real self-hosting. Follow the [install guide](https://get.porchsite.dev/install.md), or they can run the same API calls themselves. It fits Cloudflare's free plan for a personal or small business site. - **The server edition installs with Docker.** Python (FastAPI) and PostgreSQL on your person's own server, behind Caddy, which gets the HTTPS certificate by itself. It is the same product as the Cloudflare edition: passkey sign-in, the same safety rules and MCP tools, the same looks (pages render per request, byte for byte as the Cloudflare edition renders them), the theme catalog, analytics. One supported setup: Docker Compose with three containers (the app, PostgreSQL 17, Caddy). Its guide is `deploy/compose/README.md` in the repository. - **The source** of both editions, the looks and the spec is at [https://github.com/iXanadu/porchsite](https://github.com/iXanadu/porchsite), under the MIT licence (the spec under CC BY 4.0). The Cloudflare bundle in [latest.json](https://get.porchsite.dev/latest.json) is built from it (one JavaScript module plus SQL files, checked by sha256). ## What a Cloudflare-edition site needs So a developer can judge it: - **Runtime:** a Cloudflare Workers-compatible runtime (the bundle is an ES module Worker, `compatibility_date` 2026-08-15, flags `nodejs_compat` and `global_fetch_strictly_public`). - **Database:** Cloudflare D1 (SQLite). All content, every version of every item, the owner's passkeys and sessions, and **images** (stored in the database in chunks, up to 8 MB each) live here. The layout is in the `migrations/*.sql` files; apply new ones on update. - **Key-value store:** Workers KV, for sign-in (OAuth) state. - **Secrets:** `SETUP_CODE`, used once to claim the site, then deleted. The owner never types it: whoever installs sends them `https://<site>/setup?code=<code>`, which fills it in. (A one-time code with an expiry can instead be stored in D1's `setup_codes` table, as the sha256 of the code in unpadded base64url with an ISO `expires_at`; the spec, §5, has the rules.) - **HTTPS and a stable address:** passkeys only work over HTTPS and only at the address where they were made. - **Outbound requests:** the site fetches images from links an assistant gives it, and reads the theme catalog at get.porchsite.dev. ## Running it on Cloudflare by hand The [install guide](https://get.porchsite.dev/install.md) is written for an assistant, but every step is one Cloudflare API call a developer can run with `curl` and an API token (Workers Scripts, D1 and Workers KV edit; for a custom domain also Zone, DNS and Workers Routes edit). Wrangler works too: a `wrangler.jsonc` with the bundle as `main`, `no_bundle: true`, a D1 binding `DB`, a KV binding `OAUTH_KV`, and the `SETUP_CODE` secret. ## What a server-edition site needs - **A server with Docker** and the Compose plugin. A small VPS is enough. - **A domain pointed at it:** DNS records for the domain and its `www.` name. The site lives at the bare domain; `www.` redirects there. - **Ports 80 and 443** reachable from the internet, so Caddy can get and renew the certificate. Passkeys only work over HTTPS and only at the address where they were made. - **Settings** in a `.env` file: the domain, the database password, the release. - **Storage:** everything (content, every version, images, passkeys, connected assistants) is in PostgreSQL, in one Docker volume. A backup is a `pg_dump` of that database. - **Outbound requests**, as for the Cloudflare edition. Once it's up, whoever installed it runs the `setup-link` admin command and sends the owner the link it prints (`https://<domain>/setup?code=…`, good for 24 hours; a new one replaces it). The owner opens it and saves a passkey: owners never need a terminal, a settings file or a code to type. The assistant connects at `https://<domain>/mcp`. A `check` admin command reports whether the database, schema, looks and settings are sound. ## Update policy No site updates itself; the owner decides. The site checks latest.json once a day and only tells: `site_state.update` for the assistant, a line on the owner's review page. Every release stays downloadable, schema changes only ever add, and each release is tested with the previous one running on its schema, so a rollback is putting the previous release back with no database step. A release with `security: true` fixes something an attacker could use: say so, and suggest updating soon. ## Updating the Cloudflare edition On the owner's yes: apply every file in latest.json's `all_migrations` that the site's `_sitekeeper_migrations` table doesn't list, in order (not just `migrations`, which names one release's new files), upload the new bundle with the same bindings, delete the `SETUP_CODE` secret if it is still there, and check the `X-Porchsite-Release` header (retry for up to 30 seconds). Roll back by re-uploading the previous bundle from `v/<release>/` (hashes in `v/<release>/files.json`), with no schema step. Step by step: "Updating" in the install guide. ## Updating the server edition A release id is `<date>-<commit>` and names a commit; latest.json gives the full `commit`, and each release is tagged `release/<release id>`. Releases made before the repository went public (up to 2026.10.12) were built from an earlier private history: their files and hashes stay on get.porchsite.dev, but their commits and tags aren't in the public repository. On the owner's yes, in `deploy/compose/`: note the running release (the `X-Porchsite-Release` header), dump the database, `git fetch --tags` and `git checkout release/<release id>` (or `git checkout <commit>` if the tag isn't there), set `PORCHSITE_RELEASE=<release id>` in `.env`, run `docker compose up -d --build`, then the `check` admin command; the header names the new release. The schema migrates itself at start, and only ever adds. Roll back: check out the previous release's tag (or commit), set `PORCHSITE_RELEASE` back, `docker compose up -d --build`; no schema step. Reconnect the assistant if `reconnect` was true. The full procedure is "Update" in `deploy/compose/README.md`. ## Limits to say out loud - Looks and fixes are welcome as pull requests on GitHub; a running site can also share a look with `theme_submit`, and anyone can ask for a feature or a fix at https://porchsite.dev/ask. - The server edition has one supported setup (Docker Compose on one machine). Other ways of running it (a bare Python install, another web server, a managed database) can work, but aren't documented or supported. - One owner per site; approvals need that owner's passkey.