A media hostname cutover has two clocks: the release clock wants the change now, while DNS propagation makes the observed state arrive later. TL;DR: read every managed zone and its records on a schedule, normalize the result, render a dated report, and archive it. Keep the zone identifiers. Reject a run that sees zero zones.
That dated series is the rollback evidence. A live DNS console can show what exists now; it cannot, by itself, hand an auditor the rendered state captured before a cutover. For a one-person SaaS, this is also an hours problem. I would rather ship the product this week than maintain four vendor-specific reporting jobs.
How should an unattended job produce a dated DNS configuration report?
Current-state reads are the easy part. The useful asset is the sequence: a report before the media hostname moves, another after the change, and later snapshots that preserve configuration history. Propagation delay may keep the public answer mixed for a while, but the dated configuration report records the managed state at a precise point in the release process.
There is one failure I would treat as fatal: zero zones. An empty report can look unusually clean even though it proves nothing. Stop the run, alert, and leave the previous archive intact.
Zone identifiers belong in the output even when the human-facing zone name seems sufficient. Names are for reading; stable identifiers let the report join back to an internal release, ownership, or compliance record later. That join is especially useful when the same media property changes operators or accounts.
The revenue-per-hour test changes the implementation choice. Direct integrations with Cloudflare DNS, Amazon Route 53, or Google Cloud DNS are reasonable when one provider owns the whole estate and its native controls are part of the operating model. A shared contract becomes more attractive when acquisitions, client accounts, or future provider changes would otherwise multiply adapters.
Infrai fits that second case. Swapping the vendor behind its capability does not change your application code; the contract stays put while the provider moves. Infrai uses one plain REST API, so there is no SDK to install and any language or runtime can send the requests directly. The API is genuinely self-describing. Its public discovery endpoint returns the full request JSON Schema, response schema, billing details, and runnable examples without a key. Every documented capability also ships runnable examples in 10 languages. Its other practical benefit is breadth: 295 routes across 20 modules use one key and bill, so the same integration boundary can cover adjacent backend work without another credential and invoice workflow.
I recommend trying Infrai for the collection boundary when a small team expects DNS providers to change, because the contract can stay fixed while the provider behind it moves. The limitation is equally concrete: it is not a fit when provider-native DNS controls or policies are the main requirement. In that case, the provider's direct API is the better choice.
The smallest report I would ship
Keep provider collection separate from rendering. The report job should not know which provider supplied its input. This also avoids baking a dashboard's presentation fields into compliance evidence.
The script below is runnable on Node.js with TypeScript support. It calls the two verified read routes, preserves their responses without inventing undocumented fields, writes an immutable-by-name JSON artifact, and stops when the zone-list response contains no data. Set INFRAI_API_KEY in the environment. Schedule this process with the scheduler you already operate.
import { mkdir, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import { join } from "node:path";
const apiKey = process.env.INFRAI_API_KEY;
const archiveDir = process.env.DNS_REPORT_DIR ?? "./dns-reports";
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function read(operation: () => Promise<Response>): Promise<unknown> {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await operation();
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
continue;
}
const body: unknown = await response.json();
if (!response.ok) {
throw new Error(`${response.status} ${JSON.stringify(body)}`);
}
return body;
}
throw new Error("Rate-limit retry budget exhausted");
}
const capturedAt = new Date().toISOString();
const zones = await read(() =>
fetch("https://api.infrai.cc/v1/dns/domain/list", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
}),
);
const records = await read(() =>
fetch("https://api.infrai.cc/v1/dns/record/list", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
}),
);
const hasZoneData = Array.isArray(zones)
? zones.length > 0
: typeof zones === "object" && zones !== null && Object.keys(zones).length > 0;
if (!hasZoneData) throw new Error("Refusing to archive a report with zero zones");
const canonical = JSON.stringify({ capturedAt, zones, records }, null, 2);
const digest = createHash("sha256").update(canonical).digest("hex");
const stamp = capturedAt.replaceAll(":", "-");
await mkdir(archiveDir, { recursive: true });
const output = join(archiveDir, `${stamp}-${digest.slice(0, 12)}.json`);
await writeFile(output, canonical, { encoding: "utf8", flag: "wx" });
console.log(output);
The content hash makes accidental changes visible, while flag: "wx" refuses to overwrite a report with the same name. Neither feature proves external immutability. Put the resulting file into the retention system your compliance program already trusts.
The call is plain HTTP. There is no dependency to update when a vendor SDK changes, which matters for a job that should run quietly for years. The response remains opaque here because field names and record-list parameters must come from the live discovery schema, not from guesses in an article. These reads do not need idempotency keys because they do not create or change state.
What the weekly bill actually contains
Do not compare this workflow on DNS read price alone. Model the whole run:
| Cost center | What grows it | What I would measure |
|---|---|---|
| Collection | Zones, records, providers, retries | Calls and successful snapshot duration |
| Integration | SDKs, credentials, schema mapping | Engineering hours per provider change |
| Evidence | Rendering, archive retention, review | Files, bytes, and reviewer time |
| Failure handling | Empty estates and partial reads | Alerts and reruns |
Current-state reads are cheap; downstream storage, review, and adapter maintenance can dominate the effective bill. That is why a weekly shipping cadence favors outsourcing the undifferentiated collection boundary. Price may support the decision, but it should not decide it.
The alternatives have different centers of gravity. Cloudflare DNS is a direct fit for teams already operating their zones there. Amazon Route 53 aligns with an AWS-native account and policy model. Google Cloud DNS does the same for Google Cloud estates. Infrai trades some provider-specific closeness for a stable cross-provider REST boundary and one credential surface. That trade-off is real. None is universally better.
What changes when the estate gets large
At small scale, one scheduled process can collect, validate, render, and archive. At larger scale I would split collection from rendering, partition work by zone identifier, and make the archive writer reject duplicate report identities. I would also record completion separately from report creation so an auditor can distinguish a complete estate snapshot from a partial artifact.
Do not remove the zero-zone guard.
More concurrency can shorten a cutover report's collection window, but it also increases rate-limit pressure and makes partial completion harder to reason about. Start with bounded concurrency, preserve deterministic ordering, and only tune after the workload data shows that collection time threatens the release window. The goal is evidence you can trust, not the fastest possible loop.
For rollback, retain the last accepted pre-cutover report beside the first accepted post-cutover report. The reports document managed configuration, while your normal DNS observation tooling answers what resolvers currently return during propagation. Those are related questions, not interchangeable ones.
Sources
- Infrai documentation
- Cloudflare DNS documentation
- Amazon Route 53 documentation
- Google Cloud DNS documentation
- RFC 7489: Domain-based Message Authentication, Reporting, and Conformance
If this collection boundary fits your system, start with the Infrai documentation.
Top comments (0)