How a request reaches your site
boatramp serves a site at a root mountpoint — the site’s files answer at /,
/assets/app.js, /api, exactly as they were authored. This page explains every
way a request is matched to a site, in the order you meet them: the local
single-site default, host/domain routing in production, the zero-DNS
<site>.localhost convenience, and the explicit by-name admin route.
The routing itself is one pure function shared by every deployment target, so a request resolves the same way on a single node, a cluster, or Cloudflare Containers. What differs is only which host names resolve to which site.
The single-site default (local first run)
When a server serves exactly one site, that site answers at the root of the
listener. Run boatramp serve, publish one site, and it is there:
curl http://127.0.0.1:8080/
No host header, no domain, no path prefix. This is the first-run experience in
Publish your first site: the site you just
published is the site at /. Publish a second site and the default turns off
(the server can no longer guess which one you mean) — then you address sites by
host, below.
Host / domain routing (production)
In production a site answers on a hostname you attach to it. The Host header of
each request selects the site; the request path is served at that host’s root. A
site can hold a primary hostname, exact aliases, and wildcards — see the
domains config.
boatramp domain add app.example.com --method dns
boatramp domain verify app.example.com
boatramp routes a host only after you prove you control it, so attaching is a
verify-then-route task — see Attach a custom domain.
Because selection rides the Host header, it behaves identically on every
topology; a domain is registered once and every node resolves it. A host that
matches no attached domain returns 404, unless a
default site or an explicit
--default-site catch-all is set.
<site>.localhost (zero-DNS local multi-site)
To work on several sites locally without editing DNS or /etc/hosts, address a
site by putting its name in the first host label. blog.localhost resolves to the
site named blog, served at root:
curl -H 'Host: blog.localhost' http://127.0.0.1:8080/
# or, so the browser/curl resolves it to loopback:
curl --resolve blog.localhost:8080:127.0.0.1 http://blog.localhost:8080/
Most resolvers (macOS, systemd-resolved) send *.localhost to loopback already,
so a browser can just visit http://blog.localhost:8080/. On systems that do not
(bare Windows, some musl setups), use --resolve or an explicit Host header —
that is a client resolver gap, not a difference in how boatramp behaves.
First-label routing never overrides a registered domain: an attached host always wins over a same-named label.
Note: the single-site default and
<site>.localhostrouting are conveniences for local and single-operator use. They are on for a loopback bind, and under thesingle-tenantanddevsecurity postures; they are off under the default strictmulti-tenantposture on a public address, where an unmatched host resolves only to an explicit--default-siteor404. This keeps a public multi-tenant server from ever resolvingHost: <sitename>.attacker.exampleto one of your sites by name.
/_sites/<name> (explicit by-name, admin/testing)
Every site is also reachable by name at /_sites/<name>/…, regardless of host.
This is an admin and testing affordance — a quick way to hit a specific site
without attaching a host:
curl http://127.0.0.1:8080/_sites/blog/
It is not a hosting model. Because the site’s content is served under a path
prefix, a site authored for root — with absolute references like /assets/app.js
or fetch('/api') — breaks here: those URLs resolve against the origin root, not
the /_sites/blog/ prefix. Use host routing (or the single-site default) to serve
such a site; reach for /_sites/<name> only for by-name inspection.
Deprecated: the older
/sites/<name>/…prefix is a deprecated alias for/_sites/<name>/…and still works for now. Prefer/_sites/.
Sub-path mounts
Serving a site under a deliberate sub-path (for a site built with a matching
base path, e.g. a framework’s base / basePath) is not available yet. Absolute
URLs authored for root cannot be rewritten server-side in the general case, so the
supported model is a root mountpoint via host routing. See
Maturity, validation & support for status.
Choosing
| You want | Use |
|---|---|
| A quick local first run | The single-site default — publish one site, hit /. |
| Several sites locally, no DNS | <site>.localhost (first-label routing). |
| Production on your own hostname | Attach a domain; the site answers at its host’s root. |
| To inspect a specific site by name | /_sites/<name>/ (admin/testing). |