DNS Zone Blocks

Six blocks that run authoritative DNS from a storage: records live as documents, a zone file is rendered from them whole, and nothing reaches a nameserver until every server has checked the exact bytes about to be sent.

The design decision that shapes all six blocks is that a zone file is an output, never a document you maintain. Records are ordinary documents in a storage, and the file served by your nameservers is rendered from them in one piece, every time. There is no partial edit and no diff to repair, so the operation everybody dreads becomes ordinary: render, check, push, verify.

BlockWhat it doesTouches a nameserver
dnszone_importTurns a Route53 export into records, timers and delegation, ready to store.No
dnszone_renderTurns stored records into a complete zone file, with a report of every decision it made.No
dnszone_slot_statusReports which per-domain records a member of a zone group has answered for, and which nobody has.No
dnszone_checkValidates the file on every nameserver. Writes nothing, anywhere.Reads only
dnszone_pushReplaces the live file and reloads, per server. Only reachable once the check passed everywhere.Yes, writes
dnszone_verifyAsks every server which version it is actually serving, and whether they agree.Reads only

One Record Shape, Everywhere

What the import block produces is what a storage holds and what the render blocks read. A record carries its owner, type, time to live and values, plus a few fields describing its own disposition inside a group.

Owners are stored relative to the domain: @, www, *, mail._domainkey. That is what makes one record set serve several domains, because www renders correctly under every one of them without a single value being rewritten. Storing a fully qualified owner would mean editing it per domain, which is precisely the manual state this design exists to remove.

The start-of-authority record and the domain's own nameserver set are not records. They come back on their own outputs and every rendered zone carries its own, so there is nothing in the editable set inviting somebody to hand-edit a serial number.

Importing From Route53

dnszone_import reads a Route53 export and only a Route53 export. It accepts the AWS document, a bare array, or either as pasted JSON text, so reading a file and pasting into a box are not two different flows. Escaped characters are expanded on the way in, so a wildcard arrives as a character your rules can actually match.

Two things it refuses to guess:

  • Routing policies. Weighted, latency, geolocation and failover answers have no zone file equivalent at all. Importing one would mean collapsing several answers into one arbitrary answer, so the block refuses and names the records it refused.
  • The serial number. It is discarded on import, because the serial is stamped at push time. Storing the old one would create a second answer to a question that already has one, and the two would disagree the first time anybody edited the stored copy.

Record order is preserved exactly as the export gave it, which is what makes the same stored state render to the same bytes every time.

Slot annotations do not survive an import The markers that bind a record to a per-domain slot are not a Route53 concept, so an import leaves them empty on every record. Apply them from your group definition after importing, not by editing the export first.

Zone Groups: One Record Set, Several Domains

A zone group is one set of records with several domains rendered from it. You edit one domain and the others are rendered from the same records. This is deliberately not three zones with a synchronisation feature: synchronisation is a process, a process can fail halfway, and then two of your three domains are correct. One record set with three renders has no state that can drift.

Setting the base domain on dnszone_render selects that shape. Every value of every base record then takes one of four dispositions, from the first matching rule you gave it.

DispositionWhat happens
VerbatimThe default. The same value appears under the new domain.
RewriteA value that names the base domain is re-aimed at this member's. Opt-in, reported, and refused outright when it would change nothing.
Per-domainThe base value is not copied at all. The record becomes a named slot this domain fills from its own records.
NoteCopied verbatim and flagged for a human to look at.

A rewrite that changes nothing is a hard failure, not a shrug. Providers are inconsistent about whether a stored target ends in a dot, so a rule written against the wrong form silently leaves the member domain pointing at the base domain. That is a real bug that ships quietly, and the only reason to catch it is that the renderer refuses rather than accepting a rewrite with no effect.

Per-domain slots are not an optional refinement. Certificate validation records are issued per domain, so copying the base domain's publishes a validation record for a certificate nobody requested there. Mail signing keys are minted per sending domain, so a copied key signs nothing and collides with the real one the day that domain starts sending. Ownership verification tokens prove nothing under another domain. Each of those has to be answered by the domain itself or explicitly marked as never applicable.

Filled, Not Applicable, and Empty Are Three States

dnszone_slot_status reports the same picture without rendering anything, for an overview screen that has to show the gaps before anyone renders.

StateMeaning
FilledThis domain carries its own record for the slot.
Not applicableSomebody recorded why this domain will never have one.
EmptyNobody has looked at it yet.

The last two are deliberately different states. A domain that sends no mail will never have a mail signing key, and writing that down with its reason is finished work. An empty slot is unfinished work, the render reports itself incomplete, and in strict mode it refuses to render at all. An unexplained gap must never produce a file that looks finished.

Rendering the File

Without a base domain, the same render block produces a plain zone file: the same records, the same timers and the same time-to-live values the zone holds today, so your servers are a drop-in addition while the old delegation is still live. Two things are the platform's own.

  • The serial is a placeholder that the push stamps with the time it was pushed. That is monotonic without anybody having to remember yesterday's number, which is the standing failure mode of the date-plus-counter convention.
  • The nameserver set is the zone's existing servers plus yours. Both sets are genuinely authoritative and serve identical data. Nothing here touches your registrar delegation, which stays a decision you make separately.

Alias records are the one impure part, and the block is loud about it. A zone file can only hold the addresses an alias resolves to at this moment, and those go stale silently when the target moves. So flattening is opt-in, and every flattened record comes back on its own output with the value it resolved to, on every single render. A target that does not resolve is left out of the file and said so in a comment rather than guessed at.

Nothing Reaches a Server Without Passing the Gate

The render blocks cannot push, and dnszone_push will not run ungated. It requires the results from dnszone_check and refuses unless every server in its own list has a passing check whose fingerprint matches a fresh hash of the exact bytes about to be sent.

That is two guarantees in one rule. A validation failure on any single server leaves every server's live file untouched, including the ones that would have accepted it. And a flow cannot check one file and push a different one, because the bytes are hashed again at the moment of pushing.

It also refuses when the render reported itself incomplete. A zone with a per-domain slot nobody answered for parses perfectly and is missing a record, which no validator can see.

Past the gate it is deliberately not atomic. The servers are independent, each validates and swaps on its own, and one that rejects the file keeps serving what it had. Follow every push with dnszone_verify: disagreement between servers is the only failure that is invisible from the stored copy.

Server connections carry paths, never keys A value on a pin is echoed in simulation responses, captured in replay records and rendered in the studio value inspector, so a private key on one pin reaches all three. The blocks refuse a server entry carrying a key, password, passphrase or secret: they take the path to the key file instead. The same reasoning behind service providers, applied to a server connection.
Never simulate a push Simulation holds back storage writes. It does not hold back outbound integrations, so a simulated push really connects to your nameservers and really replaces their zone files. Simulate the render and the check freely; run the push for real, on purpose.

The Normal Flow, End to End

  1. Import once, into a storage. From then on the storage is the source of truth and the export is history.
  2. Render the file for the domain. Read the report: it names every rewrite, every flattened alias with the value it resolved to, and every gap.
  3. If any slot is empty, fill it or record why it will never apply. Do not push past it.
  4. Check the rendered bytes on every server.
  5. Push, which will only proceed if every check passed on those exact bytes.
  6. Verify. Servers that disagree are the failure you cannot see from here.

Next Steps

All DNS Blocks Full pin reference for each of the six blocks. Storages The document model the records live in. Service Providers Where credentials live when a flow has to reach something outside the cluster.

Frequently asked

How does RUAL manage DNS zones?

Records live as documents in a storage and the zone file is rendered from them whole, every time. There is no partial edit and no diff to repair. The normal cycle is import once, render, check the exact bytes on every nameserver, push only what passed, then verify that the servers agree.

Can a RUAL DNS push go out without being checked?

No. The push block requires the check results and refuses unless every nameserver in its list has a passing check whose fingerprint matches a fresh hash of the bytes about to be sent. A failure anywhere leaves every server's live file untouched, and a flow cannot check one file and push a different one.

Is it safe to simulate a DNS push in RUAL?

No. Simulation holds back storage writes but does not hold back outbound integrations, so a simulated push really connects to your nameservers and really replaces their zone files. Simulate the render and the check freely, and run the push deliberately.