Levelrail
Skip to content

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.

  1. Domain. Enter the registrable domain (example.com, not www.example.com). The zone is created at the provider.
    bash
    levelrail-cli dns zones create example.com
  2. 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
  3. 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.
  4. Verify. The system resolver, 1.1.1.1 and 8.8.8.8 are asked for the domain's NS records and compared with the zone's assigned servers.
    bash
    levelrail-cli dns zones verify example.com
StateMeaning
delegatedEvery resolver returns only this zone's name servers.
partially_delegatedSome 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_elsewhereResolvers return other name servers only. The list is shown so you can see where the domain still points.
not_delegatedNo 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.

bash
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 CNAME

Rules 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.
  • proxied applies to A, AAAA and CNAME on Cloudflare only. TTL 1 means 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.

PolicyFieldsUse
Weightedweight 0 to 255split traffic, canary a new server
Failoverrole PRIMARY or SECONDARY, health checksend traffic to a standby when the primary fails its health check
Multivalue answeroptional health checkreturn up to eight healthy addresses
bash
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 SECONDARY

A 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.

bash
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 --apply

Routed 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:

TemplateRecords
emailMX to your mail host, SPF, DMARC
dkima DKIM public key under a selector
verificationa verification TXT token
www-to-apexwww CNAME to the apex
google-workspaceGoogle's MX, SPF include, DMARC
microsoft-365Exchange Online MX, SPF include, autodiscover, DMARC
caa-letsencryptCAA 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.

bash
levelrail-cli dns records template example.com google-workspace --param policy=quarantine --apply

Health 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.

bash
levelrail-cli dns check www.example.com --type A

Set 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.com and a.*.example.com are 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.com goes to the wildcard app. A wildcard matches exactly one label, so *.example.com does not match a.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 featureStatusWhy
Public hosted zonesSupportedCreate, list, delete, name servers.
Record types and multi value setsSupportedA, AAAA, CNAME, TXT, MX, CAA, SRV, NS.
Weighted, failover, multivalue routingSupportedSame plumbing: set identifier plus one field.
Health checksSupportedHTTP, HTTPS and TCP checks for failover and multivalue sets.
Alias recordsRead onlyAlias targets are AWS resources this server does not manage.
Latency and geolocation routingLaterNeeds region and location pickers; shown read only today.
Private hosted zonesLaterNeeds VPC association; listed, not created.
DNSSEC signingLaterNeeds KMS keys and DS records at the registrar.
Reusable delegation setsLaterUseful for white label name servers across many zones.
Query loggingLaterWrites to CloudWatch Logs, outside this platform's log store.
Traffic flow policiesNot plannedPriced per policy record and overlaps the routing above.
Domain registration and transferNot plannedStays 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.

Released under the Apache 2.0 License.