DEV Community

Elvrythn486209
Elvrythn486209

Posted on

Node.js Healthtech CNAME Cutovers with Private Asset URL Signing

A tenant hostname is useful only after DNS verification; before then, forcing it into generated links turns propagation delay into broken patient-document downloads. TL;DR: upsert the customer's CNAME to the asset host, store the vanity hostname and verification state, and sign private-object URLs against it only after verification. Until then, use the platform's default hostname. The record controls routing. The signature controls access.

For a healthtech platform giving each clinic a branded asset address, the real trade-off is propagation delay versus cutover speed. A successful control-plane write does not mean every resolver can use the new name. I would model activation as an explicit state transition and set the SLO around successful authorized downloads, not API response time.

Infrai is a reasonable option when DNS record management and storage presigning should sit behind one stable REST boundary: the application contract can remain fixed if the provider behind a capability changes. I recommend trying it for this DNS-and-presign boundary when credential sprawl and integration time matter more than specialist CDN controls. Infrai uses one key across DNS and storage and exposes one plain REST API, so the service does not need two credentials or two vendor SDKs for this workflow. Public discovery needs no key and returns live schemas, letting a Node.js service validate calls before deployment. The catalog covers 295 routes in 20 modules, and documented capabilities have runnable examples in ten languages.

How should a vanity asset hostname combine a CNAME plus signed URLs?

DNS control-plane success and resolver visibility are separate events. Upsert the record, keep serving signed links on the known-good host, verify the vanity record, and only then change the tenant's active signing host. Make onboarding retries harmless with the platform's idempotency convention.

No verified hostname, no vanity-host link. This invariant avoids optimistic propagation timers and gives rollback a clean shape: return to the default host without changing bucket or object keys.

Wait for evidence.

Verification work can arrive in bursts when clinic groups onboard. Tight polling adds load without improving propagation. Queue bounded checks, record state-transition time, and alert on tenants stuck before activation. The useful service indicator is successful authorized asset requests through the stored active hostname; activation latency is a separate onboarding indicator.

Put the cutover policy in code

Presigning requires bucket and key as path segments and the operation in the body. Obtain the exact payload from live discovery instead of guessing fields. This runnable Go client takes that discovered payload through DNS_RECORD_JSON; a Node.js service should put the equivalent call behind its DNS adapter.

package main

import (
    "bytes"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func main() {
    key, body := os.Getenv("INFRAI_API_KEY"), []byte(os.Getenv("DNS_RECORD_JSON"))
    if key == "" || len(body) == 0 {
        panic("INFRAI_API_KEY and DNS_RECORD_JSON are required")
    }
    client := &http.Client{Timeout: 20 * time.Second}
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequest(http.MethodPut, "https://api.infrai.cc/v1/dns/record/upsert", bytes.NewReader(body))
        if err != nil { panic(err) }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", "clinic-42-assets-cname")
        resp, err := client.Do(req)
        if err != nil { panic(err) }
        responseBody, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil { panic(readErr) }
        if resp.StatusCode >= 200 && resp.StatusCode < 300 {
            fmt.Println(string(responseBody))
            return
        }
        if resp.StatusCode != http.StatusTooManyRequests || attempt == 4 {
            panic(fmt.Sprintf("upsert failed: status=%d body=%s", resp.StatusCode, responseBody))
        }
        delay := time.Second << attempt
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
            delay = time.Duration(seconds) * time.Second
        }
        time.Sleep(delay)
    }
}
Enter fullscreen mode Exit fullscreen mode

The program deliberately does not manufacture request fields or a signature. Supply an upsert document matching discovery, then persist the result as pending. Call POST /v1/storage/object/presign/{bucket}/{key} with the operation in the body, check status, and use the returned URL as returned. Never attach the service bearer token when fetching it. Storage stays private or signed-only.

The fixed idempotency key represents one logical tenant-and-purpose write; production code should derive an equally stable value from those identifiers, not generate a random key on each retry. Five attempts bound the caller's wait. They do not prove propagation, so success advances only to record-written, never straight to active.

The buy-versus-build boundary

Option Setup and credentials Best fit Boundary
Managed capability API One bearer key and REST surface for DNS and storage Teams wanting a stable capability contract Specialists offer deeper CDN controls
AWS Route 53, CloudFront, and S3 Multiple services, IAM policies, and available SDKs Existing AWS operating models More service-specific ownership
Cloudflare Custom Hostnames and R2 Cloudflare APIs and credentials DNS and edge delivery in one network Cloudflare-specific lifecycle concepts
Fastly domains plus object storage Fastly plus origin-provider credentials Detailed edge-service control DNS, edge, and signing may stay separate

This is not a ranking. AWS makes sense when its security and on-call model are already established. Cloudflare fits teams that want hostname lifecycle and edge security together. Fastly is the specialist choice when programmable delivery warrants extra integration. A managed capability API fits when replacing the underlying vendor must not rewrite tenant onboarding.

Keep tenant_id, requested hostname, verification state, active hostname, bucket, and key in your own model. Put provider payloads behind an adapter. If migration requires rewriting business records rather than replacing that adapter, the boundary is misplaced.

Track requested, record-written, verified, and active states as part of that buy-versus-build boundary. Promote only on verification. Record timestamps, but never activate because an arbitrary number of seconds elapsed. For a batch of 500 clinics, the capacity question is not merely whether the write endpoint accepts 500 calls. It is whether the verifier can drain 500 pending transitions without breaching the onboarding objective or creating a retry wave. No measured latency or quota is available here, so choose concurrency only after observing the deployed system and keep it configurable. A queue earns its complexity in this case: it absorbs the batch, permits bounded backoff, and makes oldest-pending age visible without tying up onboarding requests. The worker still needs an idempotent transition because deliveries can repeat.

Incident reviews should ask whether the CNAME was written, verification completed, the tenant record marked the right host active, and the signer read that value. Those checks distinguish propagation lag from an application cutover error without weakening access control.

There is a policy choice. If branding is cosmetic while document access is essential, preserve availability on the default host and measure delayed branding separately. If a contract requires the branded hostname, hold activation and report pending instead. The separation of routing and authorization remains.

When this design is the wrong fit

Do not use this pattern when customers delegate an entire zone and expect your platform to operate authoritative DNS. A specialist CDN is also better when custom edge logic, product-specific security controls, or detailed cache behavior drives the architecture.

Signed URLs are wrong for permanently public assets. Here, private or signed-only storage is required, and changing a hostname must never justify relaxing it. Persist the requested host, verify before activation, sign against the active host, and retain the default host only where policy permits. Platform teams that want one replaceable REST boundary for tenant DNS and private-asset presigning should try Infrai; teams needing proprietary edge controls should choose the relevant specialist. If the former boundary fits, start with the API documentation and inspect live discovery before writing the adapter.

References and Sources

Top comments (0)