People tell AI agents what to build; the agents build, deploy, and manage static sites here. The platform's whole job is to make that safe — by construction, not by vigilance.
Simple Host is one Go service, one Postgres database, and one directory on disk, behind a single reverse proxy that fronts the content host sites.simple-host.app (sites by path) and the dashboard origin. No CDN, no per-site infrastructure, no third-party hosting. Uploads are static data that can never execute on the server; every version is immutable, attributable, and instantly revertible; and the whole thing runs on a box with 1 CPU and ~1 GB of RAM. This page is that argument, visualized.
The defining constraint: user uploads are data, never code — they can never execute on the server. That is what makes agent-generated ("vibe-coded") sites safe to host.
No execution path exists. The server has no interpreter, no SSR runtime, no plugin loader, no per-site processes. Uploaded files are written to disk and later returned as bytes with a content type — a hostile upload has no more power than an email attachment sitting in a folder.
Archives are validated before extraction. Path traversal is blocked (no entry may escape its site directory), archive and file sizes are capped, and extensions are restricted to a static-content allowlist — scripts, binaries, and server configs (.sh, .php, .env …) are rejected at upload time, before anything touches disk.
Serving is jailed per site. Requests for site content are path-checked and rooted at that one site's current/ directory. Directory listings are disabled; a directory without an index serves nothing. One site can't read another's files through the server.
Everything is versioned and revertible. Every upload is a new immutable version tied to an authenticated owner and timestamped. Bad content rolls back instantly — the current symlink flips atomically — and can be deleted by the owner or an admin.
Top to bottom: an agent authenticates with the user's key and uploads; the validation gate enforces size caps, the static-extension allowlist, and traversal protection — scripts and binaries die there, before anything touches disk. Accepted content extracts to a new immutable vN/, the current symlink flips atomically. Serving is a separate read-only path: path-safety check, then static bytes. No user code ever runs server-side.
Every surface is gated; the gates differ in strength. Sites are public on the internet — the protection is that they're static, owned, and revertible.
| T1 | Deploy & manage sites | Per-user API key, issued by email magic-link registration. The key authenticates the owner of every upload, rollback, and delete. |
| T2 | Admin | A single admin key held in the service's environment file (/etc/simple-host.env, root-owned) — never in code, config, or the repo. |
| T3 | Viewing sites | Public by default. Privacy (view-lock password) is available on a connected custom domain: a login page sets a signed cookie that gates the page and its data. |
| T4 | Per-site state | Reads Origin-checked; writes need a visitor Google/GitHub session (X-SH-CSRF) or the owner's API key. Attribution-grade for reads, not a secret store. |
| T5 | Create with AI | Sign-in-gated and rate-limited, per user and per IP. Runs off-box; the platform stores no model credentials on served pages. |
| T6 | DB & secrets | Postgres credentials and keys live in the root-owned env file, read at boot by the non-root service user. Not reachable from any served page. |
Known, accepted limitation: the origin check on state reads is attribution-grade, not authentication-grade. Writes go through a visitor session or owner key. Secrets never belong in state.
Static hosting is commoditized. The difference here is that every site gets just enough backend — with no database to set up.
Per-site JSON state. One shared document per site (GET/PUT/PATCH /v1/sites/{site}/state), with atomic ops (set, inc, append, remove) and ETags for cheap polling — counters, votes, saved app state, guestbooks.
Append-only collections. Growing lists (/collections/{name}) for signups, RSVPs, and form submissions — cheap appends, paginated reads.
Drop-in widgets & templates. Threaded comments and feedback pins as one script tag each, password-locked private pages on a connected custom domain, and starter templates served from /v1/templates.
Server-side visitor analytics. Per-site page views and unique visitors with a 30-day trend, computed from access logs — privacy-preserving (IPs hashed with a daily-rotating salt, never stored raw), no tracking script needed.
All of it is backed by the same single Postgres — no per-site database, no separate service. The store is public to the site's own audience by design; skills tell agents never to put secrets in it.
The homepage builds a site for you through a chat. The model is called server-side, so the provider key stays on the server and never reaches a served page.
POST /v1/generate keeps the sign-in gate and rate limit on the edge, then runs the turn as a background job and answers with a job id you poll. A full build takes a minute or more — far longer than a browser will hold an idle connection open — so nothing depends on one long-lived request. The model only ever returns HTML; it is given no file or shell access. You preview in a sandboxed opaque-origin iframe, then publish through the same authenticated files API.
You can speak instead of typing — captions appear as you talk and the text stays editable before you send it. Speech is transcribed on this server, so recordings are never handed to a third party. You can also attach a screenshot, PDF or notes and have the page built from them.
Each job is bound to the user who started it and is not visible to anyone else. Exactly one model provider is configured — the builder runs on Grok — and there is no fallback: if it is down, the build reports an error instead of quietly switching to a different provider.
Where this actually runs: one small VM, one process, no orchestration.
One process. A single Go binary under systemd — deliberately not a cluster. A Caddy edge terminates TLS, serves the content host by path and the dashboard, and issues on-demand certs for connected custom domains.
Hardened & non-root. Runs as a dedicated simplehost user with NoNewPrivileges, ProtectSystem=strict, and write access limited to the sites directory. Liveness (/healthz) and readiness (/readyz, DB-connected) gate traffic.
Small by design. 1 CPU, ~1 GB RAM, 25 GB disk. Static serving and pass-through proxying — no heavy compute in-process.
Hot-swap releases. Build, copy the new binary in, atomic rename over the old one, restart — site data and versioned files untouched. Old binaries kept for instant rollback.
Local state. Postgres on the same box (users, sites, versions, per-site state); the versioned site tree on a local disk. No external object store — small enough not to need one.
| N1 | Not general compute | No user-code execution, by construction. No SSR, no backends, no PHP, no processes per site. |
| N2 | Static-only serving | HTML/CSS/JS/images/fonts served as bytes. Framework output is welcome; framework servers are not. |
| N3 | Not a secret store | Per-site state is readable by the site's audience and documented as such. Skills tell agents never to put credentials in uploads or state. |
| N4 | Not bypassable by scale | Per-site size and state caps are enforced server-side; AI create is rate-limited per user and IP. |