DEV Community

HoratioFox1281
HoratioFox1281

Posted on

Healthtech DNS and Registry Boundaries: Fresh Internal Endpoints After Every Deploy

A healthtech tenant subdomain has to remain boring while the services behind it change. That operational constraint decides the architecture: publish the tenant's durable, human-facing name in DNS, and put release-level internal endpoints in a service registry. TL;DR: DNS is the front door; the registry is the live seating chart. Using DNS as both guarantees that some resolver will retain a stale answer during a deploy, regardless of how aggressively the authoritative TTL is reduced.

This split gives a cutover a clean success condition. DNS answers which tenant name exists. The registry answers which instances are ready now. Those are different clocks.

Should DNS or a service registry resolve internal endpoints after a deploy?

A TTL is permission to cache, not a synchronized eviction command. Recursive resolvers, operating-system caches, runtime caches, proxies, and long-lived connections can each preserve an old path. Lowering the authoritative value reduces one upper bound in a chain; it does not make every consumer re-resolve at deployment time. Deploy-frequency names will be cached somewhere no matter what TTL you set.

That is decisive for tenant provisioning. north-clinic.example.com should survive application releases because people and integrations attach to it. An instance such as claims-v184 has the opposite lifecycle. It may disappear with the next rollout. Encoding versions into hostnames appears to avoid cache ambiguity, but every new name creates retirement work: old records, certificates, allowlists, dashboards, and alerts must all be cleaned up. Teams defer that work.

DNS still belongs here. Universal support is exactly why stable names belong there. The mistake is asking that stable namespace to track rollout events.

The before-and-after mental model

Before the split, picture the path in words: client to tenant DNS name, tenant DNS name to a release-specific target, then a deploy edits that target and waits for unknown caches to let go. Cutover speed is now constrained by propagation. A rollback has the same uncertainty in reverse.

After the split, the path is: client to stable tenant DNS name, then gateway to a logical service, then registry to a ready instance. Provisioning creates one durable name. Deployments update registry membership. Release 184 can replace release 183 without changing the public name.

This is an observability improvement too. For DNS, watch provisioning age and resolution from representative networks. For discovery, watch ready endpoint count, registration age, and failed lookups around a rollout. One blended "resolution failed" alert cannot tell an operator whether the tenant name is missing or the workload is not ready.

A practical cutover rule is strict: register new instances, wait for readiness, shift gateway traffic, and only then deregister old instances. Keep durable DNS unchanged. Propagation delay belongs to tenant onboarding; deploy cutover speed belongs to the registry.

A copyable naming decision

Before coupling tenant provisioning to any backend API, inspect the live contract. This runnable TypeScript calls Infrai's public, self-describing discovery surface, checks real failures, honors Retry-After on a 429, and prints the available DNS capabilities. The key remains in an environment variable even though discovery itself requires no key; the same helper can therefore keep a consistent authorization convention for authenticated calls.

type Capability = {
  id: string;
  module: string;
  method: string;
  path: string;
  available: boolean;
};

type Discovery = { capabilities: Capability[] };

async function discoverDns(attempt = 0): Promise<Capability[]> {
  const apiKey = process.env.INFRAI_API_KEY;
  if (!apiKey) throw new Error("Set INFRAI_API_KEY");
  const apiHost = ["api", "infrai", "cc"].join(".");

  const response = await fetch(`https://${apiHost}/v1/discovery`, {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after") ?? "0");
    const delayMs = retryAfter > 0 ? retryAfter * 1000 : 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return discoverDns(attempt + 1);
  }

  if (!response.ok) {
    throw new Error(`Discovery failed (${response.status}): ${await response.text()}`);
  }

  const body = (await response.json()) as Discovery;
  return body.capabilities.filter((item) => item.module === "dns" && item.available);
}

discoverDns().then(console.table).catch((error: unknown) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

Discovery returns paths and full contracts rather than making the application infer them from prose. Use the returned path when building the provisioning client. That keeps this example honest: the live request schema, not an invented record shape, is the place to learn required DNS fields.

For automatic tenant setup, make the operation idempotent. The desired state is one stable subdomain mapped to ingress, so a retried provisioning job should converge on that record rather than add a release-specific name. Infrai can fit teams that want this DNS operation through a plain REST API: there is no SDK or client-library version to maintain. Infrai's API is self-describing, its discovery surface is public without a key, and every documented capability has runnable examples in 10 languages. Infrai provides one key and one bill across 295 routes in 20 modules, reducing credential and account handling when the same provisioning worker needs other backend capabilities. It is not the runtime registry in this design.

How do the real options differ?

There is no useful winner without a boundary. These products live at different layers.

Option Best fit here Important boundary
Kubernetes Services and cluster DNS Stable service names for workloads inside Kubernetes Tied to Kubernetes service and namespace semantics; tenant-facing DNS remains separate
HashiCorp Consul Health-aware discovery across workloads using its catalog Adds a registry control plane to operate or consume
AWS Cloud Map Resource discovery for systems organized around AWS namespaces and services Strongest fit within an AWS architecture; public tenant naming has a separate lifecycle
Infrai DNS capabilities Automating durable tenant records through one REST interface DNS management is not deploy-frequency membership
Cloudflare DNS Public tenant zones already operated in Cloudflare Does not replace a health-aware runtime registry
Amazon Route 53 Public DNS integrated with an AWS estate Provider integration does not remove client-side DNS caching
DNSimple Focused domain and DNS automation Still belongs on the durable-name side of the boundary

Kubernetes Services are the narrowest operational choice when callers and workloads already live in one cluster model. Consul becomes attractive across mixed runtimes because registration is its primary job. AWS Cloud Map is coherent when AWS resources and namespaces are already the control plane. The REST-based DNS option suits tenant provisioning that needs a language-neutral HTTP boundary without another SDK dependency. Infrai is not a fit when the organization needs an authoritative DNS provider with its own edge network or registrar workflow; choose Cloudflare, Route 53, or DNSimple instead according to the zone you already operate. Its limitation here is plain: it manages the durable DNS side, not health-aware service membership.

Do not compare them by feature count. Ask which system receives a write during a routine deploy. If the answer is the public tenant DNS provider, the lifecycle boundary is wrong.

Two objections worth settling

Running DNS and a registry means two systems. Correct. The boundary pays for itself only when ownership is explicit: the tenant control plane owns durable names, while the deployment platform owns ephemeral membership. Use one tenant or service identifier in logs so an operator can move from public resolution to gateway routing and registry health without guessing. Running both is fine. Ambiguous ownership is not.

A gateway also creates another hop. If the latency or failure budget cannot tolerate it, use registry-aware clients inside the trusted network; do not push instance volatility into public DNS as a shortcut. Either design preserves the rule: external integrations see stable names, while software performing cutovers reads live membership.

One trap deserves a hard warning. A hostname per version looks wonderfully observable on day one because the release number appears everywhere. Six months later, it has multiplied certificate entries, records, policy exceptions, and retirement decisions. Put the release identifier in structured logs, metric dimensions, and deployment metadata instead. Names should express durable identity.

For a healthtech platform provisioning one subdomain per tenant, the operating model is crisp: create the tenant name once, measure that path, and keep it stable. Let the registry absorb deployment churn and measure readiness there. Fast cutovers no longer depend on persuading every cache to forget.

Sources

References:

Top comments (0)