Routing
Every app gets its own subdomain of one of the instance's domains: notes.mycooldomain.com is the app named notes. You never create a DNS record or issue a certificate per app; a wildcard covers all of them.
Domains
An instance answers on one or more domains, managed in Settings → Domains. One is the primary: the canonical domain used for outbound links and background tasks. Each domain is one of:
- Public (HTTPS): a real domain served over TLS. The usual case.
- Local HTTP: a plain-HTTP domain such as
lvh.me:8080, served by the router directly. Used before an instance is taken public.
Host matching is longest-suffix, so overlapping domains resolve to the most specific one.
DNS
For a public domain, you delegate the whole zone to the machine rather than pointing an A record at it (see Exposing a server with a static IP). The instance then runs CoreDNS as the authoritative nameserver for the zone and serves:
- an
Arecord for the domain itself, - a wildcard
Arecord for*.domain, so every app subdomain resolves, TXTrecords under_acme-challenge.domain, written only while a certificate is being issued.
CoreDNS reloads automatically when the zone file changes.
Delegating the zone is what makes both the wildcard and DNS-01 certificate validation work without you doing anything per app. If you would rather manage DNS yourself, you can: point domain and *.domain at the machine at your provider, disable CoreDNS, and supply certificates yourself; the home server page shows this with acme.sh.
TLS certificates
Each TLS domain gets one certificate covering both domain and *.domain, validated over ACME DNS-01: the instance writes the challenge TXT record into its own CoreDNS zone, the CA reads it, and the record is removed afterwards.
There are two ways to reach a CA:
- Your own ACME account: configure
acme_account_key_path,acme_email, andacme_directory_url(Let's Encrypt by default). The instance talks to the CA directly. - The Imbue certificate broker: used by managed spaces. The instance generates its keypair and CSR locally and sends only the CSR; the private key never leaves the machine.
Certificates are cached on disk and reused across restarts. A background task checks every 12 hours and renews anything expiring within 7 days, then reloads Caddy. A domain whose certificate has not landed yet is served with Caddy's self-signed certificate, so it is reachable, with a browser warning, while acquisition is in flight.
The proxy path
Caddy sits in front and does nothing but TLS:
:443: terminates TLS and reverse-proxies to the router on loopback:8080.:80: permanently redirects to HTTPS.
Caddy's config is generated by the router at startup; there is no Caddyfile to edit. An HTTP-only install runs with start_caddy = false and no Caddy at all; the router serves :8080 directly. Configuring a TLS domain without Caddy is refused at startup, since nothing would terminate TLS.
The router then matches the Host header to a domain, extracts the app name from the subdomain, and proxies to that app's container port. Requests for the bare domain are handled by the router itself: the dashboard, the API, and the docs you are reading.
What the app sees
The router forwards HTTP and WebSocket traffic alike, and sets a few headers on the way in:
X-Forwarded-For: the real client IP, taken from Caddy.X-OpenHost-Is-Owner: trueis set only when the request carries a valid owner session.
Any X-OpenHost-* header from the client is stripped first; the router is the only thing allowed to set them.
Apps that speak something other than HTTP can bind extra host ports with [[ports]] in the manifest; those bypass the router and are exposed directly on the machine. See the manifest spec.
This page lives in the
Cloud in a Bottle repo.
Suggest an edit. Prefer the raw markdown?
Add .md to the URL, or grab the whole manual at
all.md.