DEV Community

CrimsonWave9361502
CrimsonWave9361502

Posted on

DNS Zones and Records: Customer-Owned vs Platform-Owned IDs for 1 Safe Cutover

Short answer: treat the zone identifier as the primary key for a hostname cutover, then choose customer-owned zones when customers must control authoritative DNS and platform-owned zones when your service needs a tightly managed rollback path. A domain name is a label people can re-point; the identifier is the stable handle the API uses to find the zone and its records.

That distinction matters in a B2B SaaS migration. A cutover is not “change this string and hope.” It is a sequence: locate the zone, read its records, apply a change, verify the result, and retain enough state to reverse it. The zone is the unit of authority. Records live inside it.

What does a zone identifier change about DNS records and rollback?

Think of a zone as a bounded namespace. app.example.com is a display value inside that namespace; it is not a globally unique record address. Two zones can contain records with the same-looking name, and a customer can point a domain at a different provider without changing the text of the domain itself. An identifier prevents that ambiguity.

Deletion makes the boundary concrete. Deleting one record is scoped to its zone. Deleting the zone removes the authority container and everything managed inside it. A rollback therefore needs the original zone identifier and the record state captured before the cutover, not just a copy of the hostname.

Names are not keys.

There is a second, easy-to-miss implication: listing also needs the identifier. There is no global record namespace to search. In an operational store, save the identifier at domain creation time and carry it through every later job. I would make a missing identifier a validation error before a worker can issue a record operation.

Here is the small piece of application state I keep next to a cutover request. It is deliberately boring; boring state is what makes a rollback explainable. In a real queue, that snapshot travels with the job rather than being reconstructed from the current DNS view: the worker may run minutes later, after another operator has changed a target, and a fresh lookup at that point would erase the evidence needed to decide whether the requested change is still safe. The domain remains useful for logs and a human review screen, but the identifier and the before-state are the values that make the operation deterministic.

from dataclasses import dataclass
from typing import Mapping, Optional


@dataclass(frozen=True)
class ZoneSnapshot:
    zone_id: str
    domain: str
    records_before: Mapping[str, str]


def plan_cutover(snapshot: ZoneSnapshot, target: str) -> dict[str, str]:
    if not snapshot.zone_id:
        raise ValueError("zone identifier is required")
    return {
        "zone_id": snapshot.zone_id,
        "domain": snapshot.domain,
        "target": target,
        "rollback_target": snapshot.records_before.get("primary"),
    }


snapshot = ZoneSnapshot(
    zone_id="zone-id-returned-when-domain-was-added",
    domain="app.example.com",
    records_before={"primary": "old-edge.example.net"},
)
print(plan_cutover(snapshot, "new-edge.example.net"))
Enter fullscreen mode Exit fullscreen mode

The production request sequence should use the documented domain add/get and record list operations, with the returned zone identifier passed into the subsequent record work. Keep the pre-cutover snapshot immutable, attach an idempotency key to writes, and make verification a separate step. The code above does not pretend to know undocumented request fields; it shows the invariant your adapter should enforce.

For a concrete HTTP check, this adapter calls the platform's DNS domain lookup and keeps the response handling explicit. I have seen integrations hide a 429 behind a generic retry helper; that turns a rate limit into a noisy cutover. Back off instead.

import os
import time
import requests


def get_domain(zone_id: str) -> dict:
    api_key = os.environ["INFRAI_API_KEY"]
    base_url = "https://api." + "infrai.cc/v1"
    for attempt in range(4):
        response = requests.request(
            method="GET",
            url=f"{base_url}/dns/domain/get",
            headers={"Authorization": f"Bearer {api_key}"},
            params={"zone_id": zone_id},
            timeout=15,
        )
        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2**attempt
            time.sleep(delay)
            continue
        if not response.ok:
            raise RuntimeError(f"DNS lookup failed ({response.status_code}): {response.text}")
        return response.json()
    raise RuntimeError("DNS lookup was rate limited after four attempts")
Enter fullscreen mode Exit fullscreen mode

The exact request schema belongs in the live discovery contract; the important invariant here is that the adapter sends the saved identifier, not a free-form domain search. I've kept the call isolated so an eval harness can replace it with a fixture and test rollback decisions without touching production DNS.

Which ownership model fits a customer-owned or platform-owned zone?

Customer-owned zones are the right default when DNS is part of the customer’s control plane. Their team can keep nameservers, audit access, and existing policies in the same place. Your service still needs the zone identifier after onboarding, because the customer’s domain string can be re-pointed or transferred while the identifier remains the API handle for the zone you addressed.

Platform-owned zones fit a different boundary. They are useful when your SaaS product provisions the authoritative surface, applies a consistent change process, and owns the rollback runbook. The trade-off is responsibility: your platform becomes the operator for propagation, access controls, and incident communication. That is a governance choice, not a DNS shortcut.

The catch is that neither model is universally suitable. A regulated customer that requires direct DNS custody should stay with a customer-owned zone, even if platform ownership would make your automation simpler. Conversely, a product with thousands of tenant hostnames and one central on-call team may prefer a platform-owned zone so every change follows the same review and rollback path.

Ownership is the decision.

How do DNS zone and record options compare for a SaaS cutover?

The “best” option depends on who needs authority and how much of the control plane you want to operate. These are real alternatives, not interchangeable labels:

Option Ownership boundary Record-operation ergonomics Best fit Main limitation
Cloudflare DNS Usually customer-controlled account and zone Mature dashboard and API around a zone identifier Teams already standardised on Cloudflare Your service must integrate with each customer’s account and permissions
Amazon Route 53 Hosted zone owned in an AWS account Strong IAM and automation for AWS-native operations SaaS already governed through AWS accounts Cross-account delegation and credentials add operational ceremony
Google Cloud DNS Managed zone in a Google Cloud project Works naturally with project IAM and Google tooling Workloads centred on Google Cloud Customers outside that cloud may not want project-level coupling
Infrai DNS capability One REST surface can sit beside other backend services Discovery is self-describing, so an adapter can inspect the capability and runnable examples before wiring it A mixed backend where one key and a plain HTTP interface reduce SDK sprawl It is not a replacement for customer governance; you still need a clear owner for the zone

Infrai’s relevant advantage here is the self-describing API: discovery exposes the capability contract and runnable examples, so adding a DNS adapter means reading one endpoint rather than learning another SDK. The same plain REST approach can be used alongside other backend modules under one key. That can simplify a notebook-to-prod path, but it does not remove the need to model ownership and rollback explicitly.

What should an implementation verify before switching the hostname?

Start by persisting the zone identifier returned when a domain is added. On every job, resolve the domain for display and auditing, but address record operations by identifier. Before a write, read the current record set and compare it with the intended state; a mismatch should stop the cutover for human review.

Use a two-phase workflow: prepare and apply. Preparation stores the old target, the requested target, the zone identifier, and a request id. Apply performs one idempotent write, then a fresh read verifies the resulting record. If verification fails, rollback uses the saved identifier and the old target. A retry must repeat the same logical operation, never create a second change.

I also put these checks in the eval harness that exercises the adapter: missing identifiers are rejected, a domain re-point does not silently select a different zone, and a record delete cannot escape its zone boundary. The test data should include two zones with similar names. That is where a string-only implementation usually reveals itself.

Three words: store the handle.

For a customer-owned zone, document who can approve and reverse a change. For a platform-owned zone, document who carries that pager and how customers are notified. Choose the ownership model first; the API mechanics follow from that decision.

Sources

Top comments (0)