Skip to main content

1.6 DNS and TLS

Outcome​

All required DNS records and TLS certificates are in place so traffic flows correctly. The platform supports six white-label brands (RCM_02 shipped); each brand has its own tenant root domain and login UI, but they all share the same rcm-core backend.

HostRoutes toNotes
admin.<env>.medsuiteone.aiadmin-app SWA (/*) + admin-service App Service (/api/*)Platform console. One host, two origins — see below
api.rcm.medsuite.comrcm-core App ServiceTenant API. Platform admin no longer lives here
edi.medsuite.comedi-app SWAEDI operator console
edi-api.medsuite.comedi-gateway App ServiceX12 + SFTP gateway
*.rcm.medsuite.com (and per-brand)rcm-app SWATenant subdomain per brand
admin.docs.medsuiteone.aidocs-admin SWAThis site
tenant.docs.medsuiteone.aidocs-tenant SWATenant manual
edi.docs.medsuiteone.aidocs-edi SWAEDI manual
billing.docs.medsuiteone.aidocs-billing SWABilling-services manual
The four manuals sit behind a docs. label

Each manual is <site>.docs[.<env>].medsuiteone.ai — on the operator root, not a brand root, and not a bare label on it. That keeps tenant, admin, edi and billing usable as tenant slugs: a Static Web App custom domain at the same depth as a tenant subdomain would silently shadow that slug for every customer.

The platform console moved off admin.rcm.medsuite.com

Platform administration used to be a /platform/* route prefix inside the tenant app, served from an "admin variant" build at admin.rcm.medsuite.com. It is now its own application pair and that host is gone:

  • admin.<env>.medsuiteone.ai — dev / stg, production dropping the label (admin.medsuiteone.ai). One custom domain, two Front Door origins: /api/* → admin-service, /platform/.well-known/* → admin-service (the platform JWKS document), and everything else /* → the admin-app SWA.
  • The API paths did not change. They are still /platform/*; a browser reaches them as /api/platform/* on this same host.
  • Do not add a bare /platform/* Front Door route here. Only /platform/.well-known/* is routed to the API; every other /platform/… URL is an SPA route and must fall through to the SWA.

Background: docs/architecture/adr/006-platform-admin-application.md and docs/operations/runbook-platform.md §154 (standing the surface up) in the repo.

The .rcm.medsuite.com hosts in this chapter are stale

.medsuite.com has been retired as a customer domain — tenants live at <slug>.<surface>.<brand-root> on the .ai brand roots listed below. This pass repointed the platform console only; the api., edi. and tenant rows in the table above — and the examples further down that use them — still carry the retired names and are a separate correction.

Brand domains​

Seven brand roots ship out of the box (RCM_02, plus susqsoft from #132). Each brand has its own apex (and optional *.<brand> wildcard) routed to the same SWA + Front Door origin:

Brand keyApexTenant pattern
millinmillinmedical.ai<slug>.<surface>.millinmedical.ai
medsuitemedsuiteone.aioperator surfaces only — no tenant slug
emrbearemrbear.ai<slug>.<surface>.emrbear.ai
accumedicaccumedic.ai<slug>.<surface>.accumedic.ai
brittcobrittco.ai<slug>.<surface>.brittco.ai
electrobillselectrobills.ai<slug>.<surface>.electrobills.ai
susqsoftsusqsoft.ai<slug>.<surface>.susqsoft.ai

susqsoft shares Brittco's palette and artwork for now — its own theme is still to come — but it is a separate brand key, so a SusqSoft customer must be provisioned with whitelabel_key = 'susqsoft', never brittco. Picking brittco "because it looks the same" means every one of those tenants stops being able to log in the day the real theme lands.

<surface> is rcm, emr or cm. The apexes above are the values stored in each brand's domainRoots; brand resolution is a suffix match, so it works at any depth (customer1.rcm.dev.millinmedical.ai → millin). medsuite.com is retained for email only — no app host is bound to it any more.

The brand is resolved from the request host at first paint; on localhost a whiteLabelKey localStorage override is honored. Login refuses to authenticate a tenant whose configured brand does not match the host.

Prerequisites​

Topology​

Steps​

  1. Provision a wildcard certificate

    Two options:

    • Azure-managed cert (recommended for SWA): each Static Web App can be issued a managed cert per custom domain — no manual cert handling. Wildcards are not supported here, so each tenant subdomain needs its own cert.
    • Bring-your-own wildcard: a *.rcm.medsuite.com cert from your CA, uploaded to Azure Front Door. Use this if you want one cert covering all tenant subdomains.

    Most installations use Front Door + a wildcard cert because tenant subdomains are provisioned dynamically.

  2. Add the canonical CNAME / A records

    In your DNS provider:

    HostnameTypeTarget
    api.rcm.medsuite.comCNAMEFront Door endpoint (origin = rcm-core App Service)
    edi-api.medsuite.comCNAMEFront Door endpoint (origin = edi-gateway App Service)
    edi.medsuite.comCNAME<edi-app-swa>.azurestaticapps.net
    admin.<env>.medsuiteone.aiCNAMEFront Door endpoint (origins = admin-app SWA + admin-service App Service)
    *.rcm.medsuite.comCNAMEFront Door endpoint
    admin.docs.medsuiteone.aiCNAME<rcmdocs-platform>.azurestaticapps.net
    tenant.docs.medsuiteone.aiCNAME<rcmdocs-tenant>.azurestaticapps.net
    edi.docs.medsuiteone.aiCNAME<rcmdocs-edi>.azurestaticapps.net
    billing.docs.medsuiteone.aiCNAME<rcmdocs-billing>.azurestaticapps.net
  3. Bind custom domains to Static Web Apps

    declare -A DOCS_HOSTNAMES=(
    [rcmdocs-platform]=admin.docs.medsuiteone.ai
    [rcmdocs-tenant]=tenant.docs.medsuiteone.ai
    [rcmdocs-edi]=edi.docs.medsuiteone.ai
    [rcmdocs-billing]=billing.docs.medsuiteone.ai
    )
    for swa in "${!DOCS_HOSTNAMES[@]}"; do
    az staticwebapp hostname set \
    --resource-group rg-rcm-prod-eastus2 \
    --name "$swa" \
    --hostname "${DOCS_HOSTNAMES[$swa]}"
    done
  4. Configure Front Door routing for *.rcm.medsuite.com

    The wildcard route inspects the Host header and forwards to:

    • api.rcm.* → backend container app
    • *.rcm.* (any other) → tenant SWA bundle (the same apps/rcm-app build serves all tenants; the app reads window.location.host to pick the tenant slug).
    az afd route create \
    --resource-group rg-rcm-prod-eastus2 \
    --profile-name fd-rcm-prod \
    --endpoint-name rcm-prod \
    --route-name tenant-wildcard \
    --custom-domains "*.rcm.medsuite.com" \
    --origin-group og-rcm-app \
    --supported-protocols Https \
    --link-to-default-domain Disabled \
    --https-redirect Enabled
  5. Validate certificates

    # Platform console (operator root). Prod drops the env label.
    for h in admin.dev.medsuiteone.ai; do
    echo | openssl s_client -connect "$h:443" -servername "$h" 2>/dev/null \
    | openssl x509 -noout -subject -enddate
    done

    # App / API hosts (medsuite.com)
    for h in api edi; do
    echo | openssl s_client -connect "$h.medsuite.com:443" -servername "$h.medsuite.com" 2>/dev/null \
    | openssl x509 -noout -subject -enddate
    done

    # Docs hosts (docs.medsuiteone.ai)
    for h in admin tenant edi billing; do
    echo | openssl s_client -connect "$h.docs.medsuiteone.ai:443" -servername "$h.docs.medsuiteone.ai" 2>/dev/null \
    | openssl x509 -noout -subject -enddate
    done

    Each line should show a non-expired cert with the right subject CN/SAN.

Validation​

CheckExpected
dig admin.<env>.medsuiteone.aiResolves to the Front Door endpoint
dig api.rcm.medsuite.comResolves to Container App hostname
dig +short '*.rcm.medsuite.com'Resolves to Front Door
curl -fsSL https://admin.<env>.medsuiteone.aiReturns the console SPA login page
curl -fsSL https://admin.docs.medsuiteone.aiReturns this docs site

Troubleshooting​

SymptomLikely causeFix
Cert error in browserCustom domain still validatingWait up to 30 min after binding; check az staticwebapp hostname show.
*.rcm resolves but returns 404Front Door route missing or origin not addedRe-check step 4.
api.rcm returns 502Container app probe failingCheck az containerapp logs show for the backend.

Next​

1.7 — Smoke tests