DNS zones and records
The DNS page (Infrastructure, DNS) manages whole zones at the DNS provider you already connected for certificates: Cloudflare or Amazon Route53. It covers creating a zone, moving a domain's name servers to it, importing what the domain serves today, editing records, and checking that the world sees your changes. The CLI has the same surface under levelrail-cli dns, and the API lives under /api/v1/dns.
Nothing here is a DNS server of its own. Zones and records live at the provider; the control plane calls its API with the credentials stored under Domains, DNS provider.
Connect a provider
Zone management reuses the DNS-01 credentials from Domains and ingress. Zone level work needs a little more access than DNS-01 alone.
When both providers are connected, Cloudflare is the default and --provider route53 (or ?provider=route53) picks the other one. Tokens are envelope encrypted at rest, never returned by any API, and never written to logs or audit rows.
Bring a domain onto a zone
The Add zone wizard has four steps, and levelrail-cli dns zones ... does the same from a terminal.
- Domain. Enter the registrable domain (
example.com, notwww.example.com). The zone is created at the provider.bashlevelrail-cli dns zones create example.com - Name servers. The provider assigns name servers to the zone. Set exactly these at your registrar, replacing the current ones; a mix of old and new answers inconsistently.bash
levelrail-cli dns zones nameservers example.com - Import records. Before switching, copy what the domain serves today. Zone transfers (AXFR) are almost always refused, so the wizard looks up a list of common names (
@,www,mail,api, DKIM selectors,_dmarc, common SRV names and more) at the domain's current name servers and shows what it found. Untick anything you do not want and add any other names you use. Only the sets you keep are imported. - Verify. The system resolver,
1.1.1.1and8.8.8.8are asked for the domain's NS records and compared with the zone's assigned servers.bashlevelrail-cli dns zones verify example.com
| State | Meaning |
|---|---|
delegated | Every resolver returns only this zone's name servers. |
partially_delegated | Some resolvers return this zone's servers, others still return old ones, or one answer mixes both. Wait for caches, or remove stray servers at the registrar. |
delegated_elsewhere | Resolvers return other name servers only. The list is shown so you can see where the domain still points. |
not_delegated | No resolver returned name servers. The domain may be unregistered or have no name servers set. |
A resolver that returns a subset of the assigned servers counts as a match: a registrar given two of Route53's four servers still delegates correctly. Registries usually publish a name server change within an hour, but resolvers may keep the old answer for up to two days.
Records
The zone page lists record sets: all values of one name and type together. Search matches names, values and set identifiers; the type filter narrows to one type. Lists longer than 50 sets are virtualized.
Supported types are A, AAAA, CNAME, TXT, MX, CAA, SRV and NS (for delegating a subdomain; apex NS and SOA are provider managed and read only). Each type has its own fields in the record dialog, and every value is checked twice: in the browser for instant feedback and on the server, which parses each value with miekg/dns before anything reaches the provider.
levelrail-cli dns records list example.com --type MX
levelrail-cli dns records add example.com --name www --type A --value 203.0.113.10 --value 203.0.113.11
levelrail-cli dns records add example.com --name @ --type MX --value "10 mail.example.com"
levelrail-cli dns records update example.com --name www --type A --value 203.0.113.12 --ttl 600
levelrail-cli dns records delete example.com --name old --type CNAMERules the server enforces:
- A CNAME cannot share a name with any other record, in either direction.
- A CNAME at the apex is refused on Route53 (use an A record or a Route53 alias) and allowed with a warning on Cloudflare, which flattens it.
- A CNAME set holds exactly one value.
proxiedapplies to A, AAAA and CNAME on Cloudflare only. TTL1means automatic and is Cloudflare only.- TTL is otherwise between 30 seconds and 7 days. The default is
APP_DNS_DEFAULT_TTL, or 300 seconds. - TXT values are the raw text; splitting into 255 byte strings and quoting is handled for you.
Route53 alias records are shown read only; edit them in the AWS console.
Routing policies (Route53)
On Route53, a record set can use a routing policy. Several sets then share a name and type and are told apart by a set identifier.
| Policy | Fields | Use |
|---|---|---|
| Weighted | weight 0 to 255 | split traffic, canary a new server |
| Failover | role PRIMARY or SECONDARY, health check | send traffic to a standby when the primary fails its health check |
| Multivalue answer | optional health check | return up to eight healthy addresses |
levelrail-cli dns health-checks create --type HTTPS --fqdn app.example.com --path /healthz
levelrail-cli dns records add example.com --name app --type A --value 203.0.113.10 \
--routing failover --set-id primary --failover PRIMARY --health-check <id>
levelrail-cli dns records add example.com --name app --type A --value 198.51.100.20 \
--routing failover --set-id standby --failover SECONDARYA name cannot mix simple and routed sets of the same type. Cloudflare has no equivalent in plain DNS (its load balancing is a separate paid product), so routing policies are refused there with a clear error.
Import and export
Import accepts a BIND zone file or the JSON this page exports, as a file or pasted text. It always produces a plan first: what will be created, updated, deleted or left alone, with conflicts flagged. Nothing changes until you apply a plan without conflicts.
By default an import never deletes anything, so records the file does not mention stay put. Replace deletes record sets missing from the file (never apex NS, SOA or alias sets) and requires typing the zone name.
levelrail-cli dns records export example.com --format bind --out example.com.zone
levelrail-cli dns records import example.com --file example.com.zone # preview
levelrail-cli dns records import example.com --file example.com.zone --apply
levelrail-cli dns records import example.com --file full.zone --replace --confirm example.com --applyRouted and alias sets have no zone file form; a BIND export lists them as comments, and the JSON export keeps them.
Templates
Templates add common setups as a plan you review before applying:
| Template | Records |
|---|---|
email | MX to your mail host, SPF, DMARC |
dkim | a DKIM public key under a selector |
verification | a verification TXT token |
www-to-apex | www CNAME to the apex |
google-workspace | Google's MX, SPF include, DMARC |
microsoft-365 | Exchange Online MX, SPF include, autodiscover, DMARC |
caa-letsencrypt | CAA issue and issuewild for Let's Encrypt |
TXT values already at the same name are kept (verification tokens survive), except a second SPF or DMARC record, which would break mail authentication.
levelrail-cli dns records template example.com google-workspace --param policy=quarantine --applyHealth and propagation
The zone overview shows record counts by type, the delegation state, and the last change made through this control plane (who and which action), falling back to the provider's own modified time.
The Propagation tab, or levelrail-cli dns check, asks one name and type of the system resolver, 1.1.1.1, 8.8.8.8, and, when the zone is known, each of the zone's own name servers directly. Each answer shows its values and remaining TTL, whether all answers agree, and whether each matches what the zone holds. A resolver that still differs from the authoritative answer is waiting out its TTL.
levelrail-cli dns check www.example.com --type ASet APP_DNS_PUBLIC_RESOLVERS=off to ask only the system resolver, for example on a network that blocks outbound DNS.
Wildcard subdomains
An app can serve every name under a domain: add *.example.com (or *.apps.example.com) as one of its domains.
- Only a single leading
*.label is allowed.*.com,*.*.example.comanda.*.example.comare refused. - An exact domain always wins: if another app owns
api.example.com, requests for it go to that app, and every other name under*.example.comgoes to the wildcard app. A wildcard matches exactly one label, so*.example.comdoes not matcha.b.example.com. - The certificate must come from DNS-01, because HTTP-01 cannot issue wildcard certificates. Adding a wildcard domain is refused until Cloudflare or Route53 is connected; the error links to the Domains page and names the CLI command.
- When the provider manages the domain's zone, a
*A record pointing at this server is created for you. An existing*record is never overwritten. - The domain check resolves a random name under the wildcard, so it tests the wildcard record rather than a cached exact name. The Domains table marks these domains Wildcard.
Per environment and preview environment wildcards are not part of this yet.
Permissions and audit
Reads (zones, records, delegation, checks, exports) need the read ability. Every write against the provider (zones, records, imports, templates, health checks) needs root, because it changes live infrastructure outside this server. Each write is audited with an action name (dns_zone.create, dns_zone.delete, dns_record.create, dns_record.update, dns_record.delete, dns_record.import, dns_record.template, dns_health_check.create, dns_health_check.delete); filter the audit log by dns_record or dns_zone.
Deleting a zone requires typing its name, and a zone that still has records beyond NS and SOA also needs force (--force). Export it first.
Limits
- Discovery before delegation is a best effort lookup of common names; names nobody guessed are not found. Add them in the wizard.
- Cloudflare zone listings do not include a record count; Route53 does.
- Route53 private hosted zones are listed but have no delegation to verify.
- Each change is applied as it is planned, one record set at a time; a provider error part way through an import stops it and reports how many changes landed.
Roadmap
How the Route53 feature set maps onto this page:
| Route53 feature | Status | Why |
|---|---|---|
| Public hosted zones | Supported | Create, list, delete, name servers. |
| Record types and multi value sets | Supported | A, AAAA, CNAME, TXT, MX, CAA, SRV, NS. |
| Weighted, failover, multivalue routing | Supported | Same plumbing: set identifier plus one field. |
| Health checks | Supported | HTTP, HTTPS and TCP checks for failover and multivalue sets. |
| Alias records | Read only | Alias targets are AWS resources this server does not manage. |
| Latency and geolocation routing | Later | Needs region and location pickers; shown read only today. |
| Private hosted zones | Later | Needs VPC association; listed, not created. |
| DNSSEC signing | Later | Needs KMS keys and DS records at the registrar. |
| Reusable delegation sets | Later | Useful for white label name servers across many zones. |
| Query logging | Later | Writes to CloudWatch Logs, outside this platform's log store. |
| Traffic flow policies | Not planned | Priced per policy record and overlaps the routing above. |
| Domain registration and transfer | Not planned | Stays with your registrar. |
Also later: ownership TXT markers (as Kubernetes external-dns does) so imports can tell records this platform created from foreign ones, and Cloudflare load balancing pools.