Appearance
Cloudflare DNS Records — heritageva.app ​
AB#3680 — DNS and domain-validation records to create in Cloudflare at deploy time. See ADR 0026 for the domain strategy decision and ADR 0028 for the per-portal subdomain decision.
TLS model: Cloudflare Universal SSL (free, covers
heritageva.app+*.heritageva.appfirst-level wildcard) terminates TLS for all portal subdomains at the Cloudflare edge. Azure SWA provides its own managed certificate for the apex domain only. Azure-managed certificates are not used for portal subdomains.
Prerequisites ​
- Run
az deployment group createwithcustomDomain=heritageva.app - Capture the stack outputs:
swaDefaultHostnameandswaDomainValidationToken - Capture the Container App FQDN from
containerAppFqdnoutput - Confirm Cloudflare Universal SSL is active for
heritageva.appin the Cloudflare dashboard (SSL/TLS → Overview → should show "Universal" with green certificate status)
Step 1 — Apex and API records (Azure-managed certs) ​
Web app root domain → Static Web App ​
| Type | Name | Value | Proxy | TTL |
|---|---|---|---|---|
| CNAME | @ | <swaDefaultHostname> (e.g. swa-heritageva-prod-eus2.azurestaticapps.net) | DNS only | Auto |
| TXT | @ | <swaDomainValidationToken> (from stack output) | n/a | Auto |
Note: The CNAME is set to DNS only (grey cloud) during initial SWA cert provisioning so Azure's HTTP challenge can reach the SWA directly. After the Azure-managed cert for
heritageva.appis issued (5–10 min), you may optionally switch to Proxied. The Cloudflare Universal SSL cert covers the apex regardless.
API subdomain → Container App (DNS only, no CF proxy) ​
| Type | Name | Value | Proxy | TTL |
|---|---|---|---|---|
| CNAME | api | <containerAppFqdn> (from stack output) | DNS only | Auto |
| TXT | asuid.api | Container App domain validation token | n/a | Auto |
Retrieve the Container App validation token:
bash
az containerapp hostname add \
--name ca-heritageva-api-prod-eus2 \
--resource-group rg-heritageva-prod-eus2 \
--hostname api.heritageva.app \
--query properties.customDomainVerificationId -o tsv
apistays DNS only (grey cloud). Azure Container Apps managed-cert validation requires the record to be unproxied during the DNS challenge. Leave it grey-cloud permanently.
Step 2 — www redirect ​
| Type | Name | Value | Proxy | TTL |
|---|---|---|---|---|
| CNAME | www | <swaDefaultHostname> | Proxied | Auto |
Then add a Cloudflare Redirect Rule (Rules → Redirect Rules → Create rule):
- Name:
www → apex - When incoming requests match:
http.host eq "www.heritageva.app" - Then: Redirect to
https://heritageva.app${http.request.uri.path}— 301 (Permanent)
Step 3 — Portal subdomain CNAMEs (Cloudflare-proxied, CF Universal SSL) ​
All portal subdomains are Proxied (orange cloud). Cloudflare Universal SSL covers *.heritageva.app; no per-subdomain cert provisioning is needed.
| Type | Name | Value | Proxy | Purpose |
|---|---|---|---|---|
| CNAME | profile | <swaDefaultHostname> | Proxied | Member profile portal |
| CNAME | family | <swaDefaultHostname> | Proxied | Family portal |
| CNAME | homeschool | <swaDefaultHostname> | Proxied | Homeschool Education Portal |
| CNAME | marketplace | <swaDefaultHostname> | Proxied | Community Marketplace |
| CNAME | pony-express | <swaDefaultHostname> | Proxied | Pony Express delivery network |
| CNAME | listen | <swaDefaultHostname> | Proxied | Sermons & Music |
| CNAME | calendar | <swaDefaultHostname> | Proxied | Calendar & events |
| CNAME | groups | <swaDefaultHostname> | Proxied | Small Groups & Ministries |
| CNAME | members | <swaDefaultHostname> | Proxied | Member directory |
| CNAME | rideshare | <swaDefaultHostname> | Proxied | Ride share & travel |
| CNAME | communities | <swaDefaultHostname> | Proxied | Sister Communities |
| CNAME | announcements | <swaDefaultHostname> | Proxied | Announcements |
Step 4 — Cloudflare Origin Rule (Host header rewrite) ​
Azure SWA only serves responses for hostnames it has been explicitly configured with (the apex). The Origin Rule rewrites the Host header so SWA accepts requests for portal subdomains.
Create a Cloudflare Origin Rule (Rules → Origin Rules → Create rule):
- Name:
Portal subdomains → SWA - When incoming requests match:
(http.host matches ".*\.heritageva\.app") - Then — Host Header rewrite:
<swaDefaultHostname>(e.g.swa-heritageva-prod-eus2.azurestaticapps.net)
This rule fires for all *.heritageva.app requests that reach Cloudflare. The SPA then reads window.location.hostname on the client side to determine which portal to mount (see apps/web/src/lib/portal-router.ts).
Step 5 — Email (already configured — do not change) ​
| Type | Name | Value | Notes |
|---|---|---|---|
| MX | @ | Cloudflare routing MX | Already live — do not change |
Email forward: any @heritageva.app → kris@hybridsolutions.cloud
Verification checklist ​
Apex and API ​
- [ ]
dig heritageva.appresolves (A or CNAME via CF) - [ ]
curl -I https://heritageva.app→ HTTP 200 or app redirect - [ ] TLS cert for
heritageva.appshows valid (Azure-managed or Cloudflare Universal) - [ ]
dig api.heritageva.appresolves to Container App IP (grey cloud, direct) - [ ]
curl https://api.heritageva.app/healthz→{"status":"ok"} - [ ] Email forwarding still works after DNS changes
Portal subdomains ​
- [ ]
curl -I https://calendar.heritageva.app→ HTTP 200 (SPA served via CF proxy) - [ ] TLS cert for
calendar.heritageva.appis Cloudflare Universal SSL (not Azure) - [ ]
curl -I https://listen.heritageva.app→ HTTP 200 - [ ]
curl -I https://homeschool.heritageva.app→ HTTP 200 - [ ] Browser: visit
https://profile.heritageva.app— confirm SPA loads and hostname router mounts the Profile portal (not the root shell)
www redirect ​
- [ ]
curl -IL https://www.heritageva.app→ 301 →https://heritageva.app