Skip to content

Custom domains

Without a domain, the site is served from the distribution’s *.cloudfront.net hostname. Configure a domain and bootstrap requests a TLS certificate, waits for you to validate it via DNS, and attaches the domain to the CloudFront distribution as its alias. Creating the DNS record that points your domain at the distribution is the one step left to you — blogwright never touches the main site’s DNS, so the domain can be hosted at any registrar or DNS provider.

Set domain in the environment’s config file:

config/production.jsonc
{
"region": "us-east-1",
"siteName": "example",
"domain": "example.com",
}

Or pass it on the command line at bootstrap:

Terminal window
blogwright bootstrap --domain example.com

--domain overrides the config value for that invocation only. Prefer the config file: the domain is read on every command, and deploy uses it to compute the canonical site URL — a domain passed only as a one-off flag at bootstrap is not remembered by later deploys. The domain is per-environment, so config/staging.jsonc can carry a different one (or none).

A configured domain adds two things to the infrastructure graph:

  • An ACM certificate, requested in us-east-1 — CloudFront only accepts certificates from that region, regardless of your primary region — covering exactly the configured domain, validated via DNS.
  • A CloudFront alias: the domain is attached to the distribution, with the certificate as its viewer certificate.

ACM proves you control the domain through DNS records. Bootstrap prints the validation CNAMEs it needs:

Terminal window
Add these DNS records at your registrar to validate the certificate:
CNAME <name> -> <value>

Create those records at your DNS host, exactly as printed. Bootstrap then polls the certificate until it is issued, checking every 15 seconds for up to 30 minutes. You can Ctrl-C out of the wait — ACM keeps validating in the background — and re-run bootstrap later. If the wait expires before DNS propagates, bootstrap fails with a message telling you to re-run it once it has.

Bootstrap is a reconcile, and re-running it is always safe. The certificate’s ARN is saved to state before the long issuance wait, so a re-run picks up the same pending certificate — re-printing the validation records if it is still not issued — rather than requesting a new one. Once the certificate is issued, subsequent runs see it and move on. A certificate deleted out-of-band (with a stale ARN left in state) is detected by the status check and treated as missing, not as an error.

When the distribution is created, bootstrap prints its hostname and what to do with it:

Terminal window
CloudFront domain: dxxxxxxxxxxxxx.cloudfront.net
point example.com (CNAME/ALIAS) at dxxxxxxxxxxxxx.cloudfront.net

Create that record at your DNS host: a CNAME for a subdomain, or an ALIAS/ANAME-style record if the domain is a zone apex and your provider supports one. This is the only DNS record the site needs beyond the certificate validation records.

A domain added (or changed) after the first bootstrap reaches the live distribution the same way: set domain in the config and re-run blogwright bootstrap. The certificate node requests and validates the new certificate, and the distribution reconcile attaches the alias and viewer certificate to the existing distribution — bootstrap reports attached example.com to the distribution, or aliases up to date when nothing changed.

Every deploy computes the site’s base URL: the custom domain if one is configured, otherwise the distribution’s CloudFront hostname. That URL is passed into the build — it is where robots.txt and sitemap.xml URLs point — and it appears as the site row in the deploy summary.

This is why the domain belongs in the config file: with domain set, every deploy bakes https://example.com into the generated SEO files; without it, they fall back to the raw CloudFront URL. When bootstrap completes it prints the CloudFront hostname (Site will be served at https://….cloudfront.net) — that is the fallback origin, superseded for canonical URLs as soon as a domain is configured.