Domains
Your product answers at its own domains. Your platform's DNS zones (the cluster file's [dns] zones) are your product's too: your API at api.shop.example and your homepage at shop.example share
shop.example, and a name there is your services' as soon as a deploy claims it, its record published
by your platform (Names in your platform's zones). A domain of another
zone is your services' once you prove it with DNS records you create at your DNS provider; your platform
never writes records in that zone. Browsers see Cloudflare's certificate for your names; nothing per
name is issued, renewed or waited for.
Names in your platform's zones
A name of your platform's zones, the zone itself or one label below it (what Cloudflare's free certificate and your platform's own cover), may be a service's, your status page's or a public bucket's:
[env.prod.services.web]
domains = ["shop.example", "www.shop.example"] # beside your API at api.shop.example
Nothing to create or prove: the deploy claims the name, your platform publishes its record (a proxied
CNAME to its entry, written by its ExternalDNS) and the name answers once the deploy is live.
shop-ops domains lists it as verified, with no records. A static service on Cloudflare may answer in
these zones too (Cloudflare makes its record). Your platform's own names are never a service's: its API,
dashboard, console, status page and outside watcher (public_api, dashboard, console,
status_page, watcher), and the names it makes in its first zone (entry, s3, s3-direct,
acme); a deploy naming one is refused, and so is a sync giving your platform a name a service has.
Names two labels below a zone (www.app.shop.example) are refused: no free certificate covers them.
Names in other zones
List a service's names in scaledbox.toml, per environment:
[env.prod.services.web]
domains = ["shop.example", "www.shop.example"]
[env.staging.services.web]
domains = ["staging.shop.example"]
The next deploy claims them. shop-ops domains lists each name, its
status and the records it needs:
_scaledbox-challenge.shop.example TXT "<the value shown>"
shop.example CNAME shop--product.<targets>
staging.shop.example CNAME shop.example
<targets> is your platform's targets zone (the cluster file's targets_zone: a domain of its own on
Hetzner's DNS, off Cloudflare, so a Cloudflare zone can proxy a CNAME to it); shop-ops domains shows
the full names.
- The TXT record proves the domain is yours. Once
shop.exampleis proven, the names below it that your product declares (staging.shop.example) need only their CNAME. - The CNAME points the name at your product's target. At a zone's apex Cloudflare flattens it by itself. Never an A record to an address: addresses change; the target follows your product wherever it runs.
- One record of your zone points at your platform, its anchor: the apex if it is one of your
names, else the shortest. Your zone's other names point at the anchor (
staging.shop.example CNAME shop.example), asshop-ops domainsshows. Should your targets zone ever change, you change that one record, when it suits you: the former one keeps serving meanwhile. - In Cloudflare, make the CNAME proxied (orange cloud): your names get Cloudflare's CDN and DDoS protection on its free plan.
Your platform checks the records every few minutes. A proxied CNAME is hidden from DNS, so it checks
it by fetching http://<your name>/.well-known/scaledbox-check/<token> through it (as Let's Encrypt
checks a name): leave that path on plain HTTP, out of any redirect or WAF rule of yours. A name
answers, over HTTPS, once both hold.
Certificates
Visitors get Cloudflare's certificate for your names (its free Universal SSL covers your zone's apex and one level below it). Behind it, your platform's edge presents a certificate for your target's name, which Cloudflare accepts in any SSL/TLS mode, Full (strict) included: so no name of yours needs a certificate, a record or a wait of its own, and your CAA records only concern Cloudflare's.
When a name points away
A name whose records stop pointing at your product stops answering after its checks have failed for ten minutes, and is released seven days later unless it points at your product again: it answers again then, with no TXT record needed (it was proven once). Each name has one owner across your services and your tenants: a name a tenant has claimed can't be your services', nor theirs once yours.
Your tenants' domain
In saas mode, your tenants' apps (Tenants) answer under a domain of yours: one of your proven domains, a name below one, or a name one label below a zone of your platform's:
[tenants]
domain = "apps.shop.example" # a tenant's app answers at <subdomain>.apps.shop.example
In your platform's zones, your platform publishes its wildcard itself. Elsewhere it needs one record,
listed by shop-ops domains (and the domain's TXT proof, if neither it nor a domain of yours above it
is proven yet):
*.shopapps.example CNAME tenants.shop.<targets> (proxied)
Proxied, it puts every tenant's app behind Cloudflare's proxy at no cost per name: each app and each of
its environments is one level below your domain (blog--acme.shopapps.example,
staging--blog--acme.shopapps.example). Cloudflare's free certificate covers that when your tenant
domain is a zone's apex (shopapps.example); below a zone (apps.shop.example), its apps are two
labels below it, which only an advanced certificate for *.apps.shop.example covers (Cloudflare's
Advanced Certificate Manager, on that zone): your choice, billed by Cloudflare. A zone of its own
(shopapps.example) costs nothing. A zone of your platform's itself is never your tenant domain: its
wildcard leads to your platform. Until it holds,
your tenants' apps have no names, and their deploys say so. Submitting the domain to the
Public Suffix List makes browsers keep your tenants' cookies apart; that is
yours to do, and it takes weeks: start early.
Your previews ([previews] domain) may answer under your tenant domain only while your tenants run on
the same cluster as your environments (that wildcard leads to your tenants'); otherwise a preview's
deploy is refused, saying so: give previews another of your domains.
A tenant's own domain, for its app, is added through the API and proven by records its owner creates (see Tenants).
Your status page
[status] domain is one of your names too, proven the same way and listed as (status page) (see
Status pages).
A public bucket's domain
An environment's public bucket ([bucket] public = true) may answer on a name of yours,
[bucket] domain = "files.example.com": listed as (bucket), proven the same way (its CNAME proxied,
orange cloud), then GET https://files.example.com/<key> reads the object <key> under its
BUCKET_PREFIX, through Cloudflare's CDN. Objects without a Cache-Control of their own are served
with public, max-age=3600; set one at upload to change it. Cloudflare caches by file extension
unless your zone's Cache Rules say otherwise. Nothing but reading goes there: writes need the bucket's
keys, at its S3 endpoint.