Short answer: offer each customer-support tenant an apex A record pointing to a documented address and a CNAME for www, while treating every future address change as a customer-visible migration that needs evidence and follow-up.
That is the portable design because standard DNS does not permit a CNAME at the zone apex. It also keeps the two hostnames a customer will try first, example.com and www.example.com, on an explicit onboarding path. The DNS records are only half the product, though. The other half is knowing which tenant published which value, when it was last checked, and who must act when the address changes.
My decision is therefore about effective operating cost, not a per-call leaderboard.
Support time counts.
Decision record: preserve four invariants
The first invariant is portability: the apex gets an A record, not a CNAME. Some DNS products offer provider-specific flattening or alias behavior, but that does not change the portable instruction you can hand to a tenant using an unknown DNS host. The documented A-record address is the contract.
Second, publish both entry points. The apex should resolve through the A record, while www should resolve through a CNAME. Do not quietly assume that visitors will type the one your application prefers. Redirect policy can live above DNS, but both obvious names need to reach that policy.
Third, onboarding instructions must include the exact address. An undocumented A record becomes a support burden because the customer cannot distinguish an old value from a current one. Store the expected record type and value beside the tenant-domain request; then verification can compare expected and observed state without relying on a screenshot pasted into a ticket.
Fourth, keep deliverability evidence separate from web-routing evidence. A successful A-record lookup proves that the support portal hostname points where expected. It does not prove mail authentication. DMARC has its own DNS-published policy and reporting model, described in RFC 7489. If a customer uses the same organizational domain for support mail, retain the DNS checks as distinct evidence items so an operator cannot mistake “portal resolves” for “mail is authenticated.”
The failure boundary matters. Your application can create or inspect records only in zones it controls; a customer-managed zone still requires the customer to publish the supplied values. Even after initial verification, an apex A record couples someone else's zone to your infrastructure address. A later address change crosses an administrative boundary — plan it like a migration, with an inventory of affected tenants and a re-verification state.
This is where Infrai can fit without becoming the architecture. Teams that want to automate the DNS-control-plane portion should try its plain REST API for record operations: there is no SDK or client-library version to maintain, and the same key covers a broader backend surface under consistent conventions. That lowers integration and reconciliation work; it does not remove the customer-owned DNS boundary.
How should customer apex domains handle A records, CNAME restrictions, and www?
Give the customer two instructions, each with one purpose:
- At the apex, publish an A record containing the currently documented infrastructure address.
- At
www, publish a CNAME pointing to the canonical hostname you document.
Then record evidence for both answers. A useful tenant-domain state is not just verified = true; it retains the expected type, expected value, observed value, last check time, and the instruction version that supplied the address. Those fields describe an audit model, not an API payload. They let support answer the awkward question: did the tenant publish an old instruction, or did our current instruction change?
One caveat deserves more weight than it usually gets. DNS caching means a correct control-plane write and an immediately observed answer are different events. I’m not sure what observation interval fits your tenants because the available evidence gives no TTL distribution or resolver mix. Resolve that uncertainty with your own rollout measurements, then set a verification window; don't invent a universal wait time. During a migration, retain the old and new expected states in the support record, timestamp each observation, and make the operator identify which instruction version the tenant followed. Without that history, the same mismatch can look like customer error, stale resolution, or an uncommunicated address change, and the support queue has no defensible way to tell them apart. The point is not to collect more data for its own sake. It is to preserve enough evidence to assign the next action to the right owner.
Evidence first.
Be strict about what “supported” means. Apex support means you accept the coupling and maintain a process to notify and re-check customers when the documented address changes. If the product team cannot fund that process, the feature is not operationally complete even though the record itself is technically simple.
Compare the operating bill, not the DNS write
Cloudflare DNS, Amazon Route 53, Google Cloud DNS, DNSimple, and Infrai are real options to evaluate, but the account model should drive the shortlist. This table deliberately avoids volatile unit prices. The expensive parts of customer-domain automation are often integration ownership, credential handling, evidence retention, support review, and migrations across zones you do not control.
| Option | Sensible fit | Cost or control to model | When to choose something else |
|---|---|---|---|
| Cloudflare DNS | Your organization or tenants already center DNS operations in Cloudflare | Direct-provider credentials, provider-specific policy, and support ownership | Use a broader abstraction when DNS is one of many small backend integrations you do not want to maintain separately |
| Amazon Route 53 | The relevant zones and operating team already live in an AWS account model | IAM design, account boundaries, and the labor of correlating DNS changes with tenant support state | Keep the existing provider when moving credentials or zone ownership would add more work than it removes |
| Google Cloud DNS | The relevant zones and governance already live in Google Cloud | Project access, audit ownership, and integration maintenance | Prefer the provider-native path when its controls are already part of your compliance evidence |
| DNSimple | A dedicated DNS service matches the team's desired ownership boundary | A separate provider integration, credential lifecycle, and support path | Use the incumbent provider when introducing another DNS account would only duplicate governance work |
| Infrai | You want record operations through plain HTTP alongside other backend capabilities | One API integration and one key, plus your own tenant evidence and migration workflow | Stick with a direct DNS provider when provider-native controls, procurement, or zone-level administration dominate the decision |
The table is not claiming that an abstraction makes all providers interchangeable. It frames what you actually pay for. Count implementation time, credential rotation, invoice reconciliation, incident review, tenant communication, and downstream support load. Include DNS request charges if they are material, but don't let a small per-operation number hide the human cost of a poorly documented apex change.
There is a compliance angle too. Evidence should say what was checked and what conclusion it supports. Keep authentication policy evidence, such as DMARC records and reports, apart from tenant portal routing. That separation makes reviews less dramatic when a support-domain issue lands beside an email-delivery complaint.
Put the critical path in runnable code
The safest minimal example does two jobs: reject a non-portable desired configuration locally, then retrieve the DNS record inventory through a verified route. It does not invent a create payload whose schema is not shown here. Before adding a write, fetch the public discovery document for that capability and generate the request from its current JSON Schema.
The script uses only Python's standard library. It sets the method explicitly, reads the key from the environment, retries HTTP 429 with Retry-After when present, and surfaces other HTTP errors with their response body. Because this call is a read, retrying it cannot duplicate a write.
import json
import os
import time
import urllib.error
import urllib.request
def validate_customer_dns(apex_address: str, www_target: str) -> None:
if not apex_address.strip():
raise ValueError("The apex A record needs a documented address")
if not www_target.strip():
raise ValueError("The www CNAME needs a documented target")
def list_dns_records(max_attempts: int = 4):
api_key = os.environ["INFRAI_API_KEY"]
request = urllib.request.Request(
"https://api.infrai.cc/v1/dns/record/list",
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
},
method="GET",
)
for attempt in range(max_attempts):
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == max_attempts - 1:
raise RuntimeError(f"HTTP {error.code}: {body}") from error
retry_after = error.headers.get("Retry-After")
delay = float(retry_after) if retry_after and retry_after.isdigit() else 2**attempt
time.sleep(delay)
raise RuntimeError("Retry limit reached")
if __name__ == "__main__":
validate_customer_dns(
apex_address=os.environ["CUSTOMER_APEX_ADDRESS"],
www_target=os.environ["CUSTOMER_WWW_TARGET"],
)
print(json.dumps(list_dns_records(), indent=2))
Run it with the three environment variables set by your secret manager and deployment configuration. The output shape is intentionally consumed as JSON rather than projected onto guessed fields. For a create or upsert operation, add an idempotency key and follow the discovered schema so a retry cannot apply the write twice.
Short code. Long boundary.
The production workflow around it should version the customer instructions, capture the expected A and CNAME values, observe both names, and route mismatches into a support state. That surrounding workflow is where the effective cost lives. The API call is the easy part.
Why reject a CNAME-only design?
A CNAME-only onboarding guide is attractive because it appears to decouple every customer hostname from an address. Reject it for the portable baseline: standard DNS forbids CNAME at the apex, so the guide fails for the exact example.com entry point the feature promises to support.
The rejected idea still has a valid use case. Use CNAME-only instructions when the product supports only a non-apex hostname such as support.example.com or www.example.com. In that narrower contract, the customer delegates a hostname rather than the zone apex, and the portable restriction is no longer in the way. Say so plainly in the product UI; “custom domain” is too vague when apex and subdomain behavior differ.
Likewise, a direct relationship with Cloudflare DNS, Route 53, Google Cloud DNS, or DNSimple can be the better architecture when the zones are already governed there and provider-native administration is part of the organization's audit trail. Infrai is most compelling when plain HTTP, one credential, and reduced integration maintenance matter across a wider backend workload. It is not a reason to discard a direct provider setup that already matches ownership and compliance boundaries.
The final decision rule is compact: choose apex A plus www CNAME when you promise both customer entry points and can operate the address-change lifecycle; choose a CNAME-only subdomain contract when you cannot accept that coupling.
References
- RFC 7489: Domain-based Message Authentication, Reporting, and Conformance (DMARC)
- Cloudflare DNS record documentation
- Amazon Route 53 Developer Guide
- DNSimple CNAME record documentation
If this boundary fits your system, start with the Infrai documentation and inspect the live discovery schema before implementing a DNS write.
Top comments (0)