Architecture 01 Safety property 02 Trust model 03 The little backend 04 Create with AI 05 Runtime 06 Non-goals
00.
Architecture brief

One small service,
safe by construction.

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.

simple-host.app · public playground · companion: /llms.txt · /openapi.yaml
01.

The core safety property

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.

1.1

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.

1.2

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.

1.3

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.

1.4

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.

Fig 01 · Upload, validation & serve pipeline Platform Accepted Rejected
People & agents
Coding agent
holds the user's API key
uploads .tar.gz / .zip · or JSON files
Viewer's browser
GET https://sites.simple-host.app/<handle>/<site>/
public on the internet
1 · upload  ·  GET site path
Platform · one Go service
Upload API
X-API-Key → authenticated owner
2 · validate ↓
Path-safety check
jailed to one site's current/
safe path only ↓
Validation gate
size caps · extension allowlist
path-traversal blocked
Static file serve
bytes + content type only
no SSR · no exec · no autoindex
.sh .php .env … → rejected at upload · nothing written to disk
3 · extract vN · flip current  ·  read-only bytes from current/
Storage · versioned disk
Versioned site tree
v1 … vN · immutable per upload
current → vN symlink · atomic flip
Postgres
users · sites · versions
per-site state & collections

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.

02.

Trust model

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.

T1Deploy & manage sitesPer-user API key, issued by email magic-link registration. The key authenticates the owner of every upload, rollback, and delete.
T2AdminA single admin key held in the service's environment file (/etc/simple-host.env, root-owned) — never in code, config, or the repo.
T3Viewing sitesPublic 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.
T4Per-site stateReads 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.
T5Create with AISign-in-gated and rate-limited, per user and per IP. Runs off-box; the platform stores no model credentials on served pages.
T6DB & secretsPostgres 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.

03.

The little backend

Static hosting is commoditized. The difference here is that every site gets just enough backend — with no database to set up.

3.1

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.

3.2

Append-only collections. Growing lists (/collections/{name}) for signups, RSVPs, and form submissions — cheap appends, paginated reads.

3.3

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.

3.4

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.

04.

Create with AI

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.

05.

Runtime & deployment

Where this actually runs: one small VM, one process, no orchestration.

R1

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.

R2

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.

R3

Small by design. 1 CPU, ~1 GB RAM, 25 GB disk. Static serving and pass-through proxying — no heavy compute in-process.

R4

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.

R5

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.

Fig 02 · Runtime — one box, one process Platform Traffic
Edge
Caddy
TLS · content host by path + dashboard
on-demand certs for custom domains
HTTPS
One VM · systemd · non-root
simple-host · Go
static serve · REST API · admin UI
/healthz · /readyz · embedded skill
Probes & sandbox
ProtectSystem=strict · non-root
writes only /srv/simple-host/sites
reads / writes
Local state
Postgres
users · sites · versions
state · collections
Versioned disk
/srv/simple-host/sites
v1 … vN · current symlink
06.

What it deliberately is not

N1Not general computeNo user-code execution, by construction. No SSR, no backends, no PHP, no processes per site.
N2Static-only servingHTML/CSS/JS/images/fonts served as bytes. Framework output is welcome; framework servers are not.
N3Not a secret storePer-site state is readable by the site's audience and documented as such. Skills tell agents never to put credentials in uploads or state.
N4Not bypassable by scalePer-site size and state caps are enforced server-side; AI create is rate-limited per user and IP.