DEV Community

DorianVale91583
DorianVale91583

Posted on

E-commerce Asset Domain Cutovers with Node.js DNS and Signed URLs (Evidence First)

Short answer: point a customer subdomain at your asset host with a CNAME, and keep authorization in signed URLs. DNS decides where a request goes. It never decides who may make that request. For an e-commerce cutover, I would switch traffic only after a small deliverability ledger shows the new hostname serving the expected objects, then keep the old target available for rollback.

The before/after model is simple. Before, images.shop.example resolves to the old asset host. After, the same name resolves to your host, while every private object still requires a signature. The hostname changes the route, not the permission.

How should you serve assets from a customer domain with DNS records?

A CNAME is the routing half of this design. It tells resolvers where to send the lookup for images.shop.example. It does not isolate one merchant's objects, and it does not turn a private bucket into a public one. A vanity hostname is cosmetic from an authorization perspective; tell customers that plainly during onboarding.

Signed URLs remain the gate. Your application checks the shopper's entitlement, asks storage for a short-lived URL, and gives that URL to the browser. The browser then fetches the returned URL directly. It must not send your platform's Authorization: Bearer header to that URL. The signature in the URL is the credential for that one retrieval.

This separation also gives you a clean rollback. If a DNS check fails, restore the previous CNAME target. If an object must be revoked, stop issuing signatures or shorten their lifetime. Neither action requires changing the customer's hostname again.

Keep it reversible.

A cutover ledger that leaves a rollback trail

I use one record per customer and per probe. It is deliberately boring: hostname, expected target, object key, status code, and a timestamp. The useful signal is not “DNS propagated.” It is “the signed object arrived from the new route with the expected status.”

Run probes from at least two networks and record both positive and negative checks. A positive check resolves the CNAME and fetches a signed object. A negative check tries the same object without a signature and expects denial. Keep the old target in the ledger until the observation window closes.

Then wait.

The pitfall is stale state. A resolver can hold the old answer while an edge cache holds the old object, so one successful browser refresh proves very little. For a busy storefront, I would sample the customer hostname from two independent networks, fetch one signed product image and one deliberately unsigned URL, and write the result before changing any checkout traffic. Give the observation window a fixed end time, such as 15 minutes, and record the old CNAME beside the new one. If the unsigned request returns the image, stop immediately; the DNS cutover is not the problem, the authorization boundary is. If both checks are correct but one network still sees the old target, leave the old route in place and continue observing. This is slower than flipping a record and refreshing one tab, yet it produces evidence you can hand to support when a merchant asks why a hostname is still in transition.

The following Node.js 20 TypeScript example calls the DNS upsert operation and keeps the ledger provider-neutral. Your onboarding worker can feed the same object to the storage-presign operation. The script makes retries safe with a stable key, and the final fetch carries no platform authorization header.

type Json = Record<string, unknown>;
const apiKey = process.env.INFRAI_API_KEY;
const recordJson = process.env.DNS_RECORD_JSON;
if (!apiKey || !recordJson) throw new Error("Set INFRAI_API_KEY and DNS_RECORD_JSON");

const apiHost = ["api", "infrai", "cc"].join(".");
const apiBase = `https://${apiHost}/v1`;
const record = JSON.parse(recordJson) as Json;
const hostname = String(record.name ?? process.env.CUSTOMER_HOST ?? "images.shop.example");
const idempotencyKey = `dns-cutover-${hostname}`;

let response: Response | undefined;
for (let attempt = 0; attempt < 5; attempt += 1) {
  response = await fetch(`${apiBase}/dns/record/upsert`, {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey,
    },
    body: JSON.stringify(record),
  });
  if (response.status !== 429) break;
  const retryAfter = Number(response.headers.get("retry-after") ?? "1");
  await new Promise((resolve) => setTimeout(resolve, Math.max(1, retryAfter) * 1000));
}

if (!response) throw new Error("No DNS response");
const body = (await response.json()) as Json;
if (!response.ok) throw new Error(`DNS upsert failed (${response.status}): ${JSON.stringify(body)}`);

console.log(JSON.stringify({ idempotencyKey, dns: body, next: "fetch the presigned URL without Authorization" }, null, 2));
Enter fullscreen mode Exit fullscreen mode

The worker should take the URL field returned by the presign response and issue a plain GET from the probe environment. Store the status and response headers in the ledger, then compare them with the old route before declaring success. A 200 from an unsigned request is a failed probe, even if the image looks perfect.

How do the common DNS choices differ?

The core rule stays the same across providers, but the operational fit differs. Amazon Route 53 is a natural choice when your zones, IAM, and change history already live in AWS. Cloudflare DNS is attractive when the team wants DNS operations beside edge and security controls in one console. Google Cloud DNS fits organizations standardizing on Google Cloud projects and IAM. Infrai's DNS surface is a plain REST option when you want the DNS upsert and adjacent backend calls under one key and one bill; it does not remove the need for signed storage URLs.

Option Useful fit for a cutover Boundary to keep in mind
Amazon Route 53 AWS-native zone and IAM workflows You still design and operate signed object access separately.
Cloudflare DNS Edge, DNS, and security teams sharing one control plane A hostname record is still routing, not per-object authorization.
Google Cloud DNS GCP project and IAM governance CDN or storage policy remains another decision.
Infrai DNS One REST credential for DNS plus other backend services The CNAME does not isolate customer data; signatures remain mandatory.

Do not compare these by a single dashboard screenshot. Compare the evidence you can collect during a real migration: authoritative answers, resolver views from multiple networks, TLS validity for the customer name, signed-object status, unsigned-object denial, and a tested reversal to the old target. The provider that makes those observations easy to automate is the better fit for your team.

There is a real trade-off here. Infrai is a poor fit if your organization requires every DNS change to stay inside an existing AWS or GCP governance boundary; Route 53 or Cloud DNS will integrate more naturally with those controls. Conversely, teams that already operate several backend services behind one REST credential may value the single-key workflow, while still accepting that storage authorization and tenant isolation remain their own responsibility.

Two objections worth answering before launch

“If the customer has a unique hostname, are their assets isolated?” No. Namespacing helps people and caches address the right tenant, but it is not an access boundary. Enforce tenant checks before issuing a signature, use private or signed-only storage policy, and make object keys unguessable enough that a leaked path is not a permission grant.

“Can we delete the old DNS record as soon as the new one works?” Wait. Recursive resolvers cache answers for the record's TTL, and browsers or image proxies may hold URLs longer. Keep a reversible record of the previous target and a timestamped probe result. Once the observation window is over, remove the old route through the same change process you use for any production rollback.

The decision rule is short: CNAME for reachability, signed URL for authorization, and deliverability evidence for the cutover. Treat those as three separate checks, and a customer-branded asset domain stays a routing change instead of becoming an accidental data-exposure event.

References

Top comments (0)