Custom Domains and Certificates¶
Azure Container Apps supports custom hostnames and TLS certificates so you can serve Express applications on your own domain instead of the default azurecontainerapps.io address.
Architecture¶
flowchart TD
U[Client] --> DNS[Public DNS zone]
DNS --> INGRESS[Container Apps ingress]
DNS --> TXT[TXT record: asuid.<host>]
TXT --> ACA[Container Apps domain validation]
ACA --> CERT[TLS certificate binding]
CERT --> APP[Express App] Prerequisites¶
- Existing Container App:
$APP_NAMEin$RG - Existing Container Apps environment:
$ENVIRONMENT_NAME - Public DNS zone you control for the target domain
- Azure CLI with the Container Apps extension
Step 1: Get the app and environment values¶
export ENV_STATIC_IP=$(az containerapp env show \
--name "$ENVIRONMENT_NAME" \
--resource-group "$RG" \
--query "properties.staticIp" \
--output tsv)
export APP_FQDN=$(az containerapp show \
--name "$APP_NAME" \
--resource-group "$RG" \
--query "properties.configuration.ingress.fqdn" \
--output tsv)
export CUSTOM_DOMAIN_VERIFICATION_ID=$(az containerapp show \
--name "$APP_NAME" \
--resource-group "$RG" \
--query "properties.customDomainVerificationId" \
--output tsv)
Step 2: Create DNS records¶
az network dns record-set cname set-record \
--resource-group "$DNS_RG" \
--zone-name "contoso.com" \
--record-set-name "www" \
--cname "$APP_FQDN"
az network dns record-set txt add-record \
--resource-group "$DNS_RG" \
--zone-name "contoso.com" \
--record-set-name "asuid.www" \
--value "$CUSTOM_DOMAIN_VERIFICATION_ID"
For apex domains, point an A record to $ENV_STATIC_IP instead of using a CNAME.
Step 3: Add the hostname to the Container App¶
az containerapp hostname add \
--name "$APP_NAME" \
--resource-group "$RG" \
--hostname "www.contoso.com"
Step 4: Bind a certificate¶
Use a managed certificate when public DNS validation is available:
az containerapp hostname bind \
--name "$APP_NAME" \
--resource-group "$RG" \
--hostname "www.contoso.com" \
--environment "$ENVIRONMENT_NAME" \
--validation-method CNAME
Use a bring-your-own certificate when you already manage certificate lifecycle outside Container Apps:
az containerapp env certificate upload \
--name "$ENVIRONMENT_NAME" \
--resource-group "$RG" \
--certificate-file "./www-contoso-com.pfx" \
--password "<pfx-password>"
az containerapp hostname bind \
--name "$APP_NAME" \
--resource-group "$RG" \
--hostname "www.contoso.com" \
--environment "$ENVIRONMENT_NAME" \
--certificate "www-contoso-com"
Step 5: Configure Express for forwarded headers¶
Container Apps terminates TLS before traffic reaches Express. Trust the upstream proxy so Express generates the correct scheme and host information.
const express = require("express");
const app = express();
app.set("trust proxy", true);
app.get("/debug/host", (req, res) => {
res.json({
protocol: req.protocol,
host: req.get("host"),
forwardedProto: req.get("x-forwarded-proto"),
});
});
Verification¶
az containerapp hostname list \
--name "$APP_NAME" \
--resource-group "$RG" \
--output table
az containerapp env certificate list \
--name "$ENVIRONMENT_NAME" \
--resource-group "$RG" \
--output table
curl --verbose https://www.contoso.com/