Agent control plane Structured SSH · Git receipts · Human inspection
← All updates

Custom domains: from claim to CDN-safe TLS

How Mechanics custom domains evolved before 0.1.57: CNAME proof, CDN-safe TLS, domain attach, diagnostics, and safe detach.

Custom domains first landed in Mechanics 0.1.51, then tightened over the next few releases. By 0.1.56 the model had settled into a safer shape: domain ownership is explicit, DNS proves control, routes only consume verified claims, and removing a domain can keep the app online.

This custom-domain recap covers the releases immediately before 0.1.57. It follows the path from first-write-wins claims to CDN-safe TLS, domain attach, read-only checks, and dry-run detach plans.

Where it started

Mechanics 0.1.51 introduced the domain command group: domain check, domain add, domain list, and domain remove. A custom domain became a globally unique claim owned by one Mechanics user. Claims are first-write-wins, and platform-owned names under the Mechanics base domain stay reserved.

That separation still matters: a domain claim is not a route. The domain command owns the hostname claim; route create --host only uses a claim that the current SSH identity already owns and has verified. Route creation never claims or verifies hostnames implicitly.

The proof moved to one CNAME

The early implementation evolved quickly. 0.1.52 replaced DNS-pointing-as-proof with an explicit DNS challenge, and 0.1.53 moved that proof to the current CNAME model:

  • domain add www.example.com creates or resumes a claim
  • Mechanics prints _acme-challenge.www.example.com CNAME <token>.acme.<base-domain>
  • the domain owner publishes that CNAME in their DNS zone
  • a later domain add www.example.com verifies the claim once the record is visible

Exit code 2 means the challenge is still pending; exit code 0 means the claim is verified. Publishing DNS remains a user action. Agents should show the record and delegate the DNS change to the user, not go looking for DNS credentials. On Cloudflare DNS, the challenge CNAME should be set to DNS only, not proxied.

Why the CNAME stays published

The same _acme-challenge CNAME does two jobs. First, it proves control of the hostname. Second, it permanently delegates ACME DNS-01 challenges to Mechanics' embedded acme-dns-compatible service. cert-manager can then issue and renew Let's Encrypt certificates for the origin without depending on where the traffic DNS points.

That is what made CDN-backed domains viable in 0.1.53. A hostname can point directly at Mechanics, or it can stay behind a CDN such as Cloudflare or Fastly. In the proxied case, the CDN origin should be Mechanics and TLS mode should be Full (strict), because the Mechanics origin presents a real Let's Encrypt certificate for the custom hostname.

Attach is the shortcut

0.1.54 added domain attach for the common workflow. Instead of running domain verification and route creation as separate steps, users can run domain attach www.example.com --app myapp.

If the CNAME is missing, domain attach prints the DNS action, exits 2, and performs no route mutation. Once a later run sees the published CNAME, it verifies the claim and creates or updates the app route to use the custom host. The same command supports --dry-run, so the route plan can be reviewed before the GitOps write.

Checks report ownership, DNS, and TLS

domain check is the read-only diagnostic path. It reports whether the hostname is unclaimed, pending, or verified; whether the expected challenge CNAME is published; whether traffic DNS points directly at Mechanics or at a known CDN; and, for the owner of a verified claim, certificate readiness.

The verification lookup also avoids a common DNS trap: Mechanics checks the owning zone's authoritative nameservers for the challenge CNAME, rather than trusting a recursive resolver that may have cached a negative answer before the record existed.

Removal is explicit and app-safe

Releasing a claim never silently deletes an app route. Without --detach, domain remove www.example.com refuses while any route still serves that hostname and prints the commands needed to repoint those routes first.

With --detach, Mechanics repoints each route using the custom domain back to its default <route>.<user>.<base-domain> hostname, removes the Gateway listener for the custom host, and then releases the claim. By 0.1.56 this flow had dry-run support and an all-or-nothing preflight, so Mechanics can show the detach/release plan before changing GitOps files, Gateway listeners, or claim state.

The safety contract

  • one custom hostname belongs to one Mechanics user at a time
  • one hostname serves one route; host conflicts are rejected before mutation
  • custom domains must be verified before routes can use them
  • the challenge CNAME must remain published for TLS renewal
  • route cleanup and repo deletion do not release custom domain claims
  • admin transfers reset verification, so the new owner must prove DNS control
Custom domains are deliberately separate from routes: claim the name, prove it with DNS, then let routes use the verified host.