DNS Naming Convention & ExternalDNS
Map page β the summary + the reference table live here; the full decision record lives with the infrastructure code. Full ADR:
minicloud-gitops/docs/dns-naming-and-externaldns.md.
The platform has one enterprise-wide DNS convention and automates org DNS through ExternalDNS. The
organisation (ktayl-solution IS) is namespaced under ktayl.devandre.sbs so it never collides
with the owner's portfolio (www / apex devandre.sbs) or with Retrieva (retrieva.online,
its own domain β the two-layer model). Access control is identity-first: public via Cloudflare Tunnel +
WAF + Authentik SSO; internal via Tailscale.
The convention (four planes)β
| Plane | Convention | Reached via | Examples |
|---|---|---|---|
| Personal (not org) | devandre.sbs, www.devandre.sbs | Cloudflare | the portfolio β off-limits to org automation |
| Retrieva (cert product) | retrieva.online | Cloudflare | separate layer |
| Public β org | <app>.ktayl.devandre.sbs (prod) Β· <app>.<env>.ktayl.devandre.sbs (non-prod) | Cloudflare Tunnel + WAF + SSO | broker.ktayl.devandre.sbs, broker.dev.ktayl.devandre.sbs |
| Corp β internal apps | <app>.10.0.0.200.nip.io (today) β target <app>.corp.ktayl.devandre.sbs | Tailscale only | underwriting.10.0.0.200.nip.io |
| Platform β ops tooling | <tool>.10.0.0.200.nip.io (Tailscale-only, default) | Tailscale | argocd, grafana, vault |
| Kubernetes-internal | <svc>.<ns>.svc.cluster.local | in-cluster | stalwart.mail.svc.cluster.local |
Rules: environment is a subdomain prefix (broker.dev.β¦), prod is the clean name; microservices
are not hostnames (internal on svc.cluster.local behind default-deny egress); platform tooling
defaults to Tailscale-only.
ExternalDNS (Cloudflare) β automated, fail-safeβ
An org app's Ingress creates its own DNS record (the GitOps Ingress β DNS flow). Because the public
plane rides Cloudflare Tunnel, ExternalDNS publishes CNAMEs to the tunnel, not A-records. It is
scoped so it cannot misfire: --domain-filter=ktayl.devandre.sbs (can't touch the portfolio or
retrieva.online) and policy=upsert-only (never deletes). The hostname is the opt-in β an app
is managed only when its Ingress host is under ktayl.devandre.sbs (a deliberate act; internal apps use
nip.io), plus the external-dns.alpha.kubernetes.io/target (tunnel) annotation to make it a CNAME. No
custom label is used (the shared library ingress emits only standard labels). It is idle until an org app
adopts a ktayl.devandre.sbs host β the scaffold (services/_template-helm) carries the ready block.
Companion (deferred to the first public org app): fully zero-touch public onboarding also needs a
wildcard *.ktayl.devandre.sbs cloudflared rule (free, controller-side) and a Cloudflare edge
cert β and free Universal SSL only covers devandre.sbs + *.devandre.sbs (one level), so a 2-level
*.ktayl.devandre.sbs needs Advanced Certificate Manager (~$10/mo) or a one-level name. No public org
app exists yet, so this is deferred (need-first). ExternalDNS makes the DNS record regardless. Full
design, guard table, and phased-migration plan are in the ADR linked above.
Relatedβ
- Delivery / wrapper-chart golden path: Delivery Workflow
- Mail auth on the same zone (custom MAIL FROM): Amazon SES