Short answer: For a logistics signup, use a platform-owned tenant subdomain when you need an automatic cutover; write the tenant and DNS intent together, publish after commit, and roll back with a saved previous value.
Create the tenant row and its DNS intent in one database transaction, publish the CNAME only after that commit, and make rollback a deliberate state transition rather than a best-effort delete.
The bill is usually not the DNS lookup. It is the retention of operational state: old targets, ownership evidence, audit events, and the queue of changes waiting for an authoritative provider. Keeping every historical payload forever makes incident work easier, but increases storage, review, and privacy obligations. Keep the current intent and a bounded rollback record; archive older audit data under the same retention policy as shipment metadata.
How should a signup transaction provision a tenant subdomain?
Treat signup as an intent boundary. The transaction owns the tenant identifier, the canonical hostname, the requested target, and an idempotency key. It does not own propagation time. DNS providers and recursive resolvers can publish at different times, so a committed row means “this is the desired record,” not “every driver can resolve it now.”
For a platform-owned zone such as tenant.example.com, the service can choose a collision-resistant label and control the parent zone. A customer-owned zone is different: the customer must delegate or create the record, and your service should wait for proof before declaring the hostname active. Mixing these flows is a common source of half-onboarded carrier portals.
I keep the state machine small: pending, published, verified, rollback_requested, and rolled_back. Every transition has an event id. A retry with the same idempotency key returns the existing decision instead of creating another tenant or another CNAME.
How should a logistics signup provision a tenant subdomain, upsert a CNAME, and roll back?
The write path below uses a generic DNS adapter. It is intentionally not a provider SDK example; the adapter is where a customer-owned API, an internal authoritative service, or a platform zone implementation belongs. The important details are the compare-and-set version, the saved prior value, and the outbox event emitted in the database transaction.
from dataclasses import dataclass
from typing import Optional
@dataclass
class DnsRecord:
name: str
target: str
version: str
class DnsAdapter:
def upsert_cname(self, name: str, target: str, expected_version: Optional[str]) -> DnsRecord:
"""Provider-specific implementation with conditional write semantics."""
raise NotImplementedError
def delete_cname(self, name: str, expected_version: str) -> None:
raise NotImplementedError
def provision_signup(db, dns: DnsAdapter, signup_id: str, tenant_id: str,
hostname: str, target: str) -> DnsRecord:
with db.transaction() as tx:
existing = tx.one("select * from dns_intent where signup_id = ?", (signup_id,))
if existing:
return DnsRecord(existing["hostname"], existing["target"], existing["version"])
previous = tx.one("select * from dns_intent where hostname = ?", (hostname,))
version = tx.new_id()
tx.execute(
"insert into tenants(id, signup_id, status) values (?, ?, 'pending')",
(tenant_id, signup_id),
)
tx.execute(
"insert into dns_intent(signup_id, hostname, target, previous_target, version) "
"values (?, ?, ?, ?, ?)",
(signup_id, hostname, target, previous["target"] if previous else None, version),
)
tx.execute("insert into outbox(kind, aggregate_id) values ('dns.publish', ?)", (signup_id,))
# A worker performs this after commit. The signup request never holds a DB lock
# while waiting for an external DNS authority.
return DnsRecord(hostname, target, version)
def rollback(db, dns: DnsAdapter, signup_id: str) -> None:
with db.transaction() as tx:
intent = tx.one("select * from dns_intent where signup_id = ? for update", (signup_id,))
if not intent or intent["status"] == "rolled_back":
return
tx.execute("update dns_intent set status = 'rollback_requested' where signup_id = ?", (signup_id,))
tx.execute("insert into outbox(kind, aggregate_id) values ('dns.rollback', ?)", (signup_id,))
# The worker restores the saved target, or removes the record when no target existed.
# It then marks rolled_back only after the conditional DNS write succeeds.
The adapter must make an upsert idempotent. If the provider has no conditional operation, store a lease or serialize writes per hostname so a late retry cannot overwrite a newer cutover. Never infer success from an HTTP response alone; record the provider change identifier and verify the authoritative answer separately.
Which zone should a logistics tenant use for a reversible CNAME cutover?
| Decision | What you control | Typical failure to design for | Rollback posture |
|---|---|---|---|
| Platform-owned zone | Label allocation, record write, verification | A stale worker retries an old target | Conditional upsert using the saved version |
| Customer-owned zone | Intent and evidence, not the customer DNS console | Delegation exists but CNAME is absent or points elsewhere | Revoke activation; do not overwrite customer records |
The customer-owned path is not suitable when onboarding must finish without a human or customer DNS change. Stick with a platform-owned zone for that workflow, then offer custom domains as a later state transition. The platform-owned path is a poor fit when the carrier requires its own DNS policy, DNSSEC custody, or a strict separation of administrative authority.
How do retention and verification protect the rollback path?
Rollback data is useful only while it is trustworthy. Store the old target, who requested the change, the observed authoritative answer, and an expiry for the rollback record. After expiry, keep an audit digest rather than silently pretending that restoration is still possible. That is the cost of bounded retention, and it is easier to explain during an incident review.
Verification should query the authoritative nameserver first, then test from more than one recursive resolver. A green application health check is not proof that DNS has converged. For mail subdomains, publish authentication records deliberately and review DMARC reporting requirements; a hostname cutover can change the alignment surface even when the web endpoint is healthy.
I have learned to log the exact old and new values, not just “updated.” A single missing dot or an unexpected trailing label can send a depot portal to the wrong edge. Your mileage may vary with provider propagation and negative caching, so expose the observed state and timestamp to support staff instead of promising an instant switch.
Keep it boring.
Use one transaction for business identity and DNS intent, an outbox for external publication, and a versioned record for rollback. Keep the synchronous response boring: accepted, pending verification, or rejected because ownership evidence is missing. That makes retries safe and keeps DNS latency out of the signup lock path.
The approach is intentionally conservative. It will not give a customer-owned domain an invisible override, and it will not retain unlimited snapshots. Those are boundaries, not defects. They protect tenant isolation and make the failure mode visible when a cutover has to be reversed during a busy shipping window. A two-phase outbox worker also gives operations a place to pause publication during a carrier migration: the database records intent first, and a second process can check authorization, zone ownership, and the latest version before touching DNS. That extra check adds a small amount of queue latency, but it prevents a stale signup request from winning a race against an approved hostname change. Support can then answer three concrete questions from one event trail: what was requested, what the authoritative server returned, and which version is eligible for restoration. Without those fields, “rollback” is just a ticket asking someone to guess the previous target.
References
- https://datatracker.ietf.org/doc/html/rfc7489
- https://www.rfc-editor.org/rfc/rfc1034
- https://www.rfc-editor.org/rfc/rfc1035
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
Top comments (0)