Appearance
Private DNS Zones
Azure Private DNS Zones enable DNS resolution of PaaS service private endpoints within each VNet. Without private DNS zones, services would resolve to the public IP of Azure PaaS resources — bypassing private endpoints entirely.
How Private DNS Works with Private Endpoints
Container App calls: mic-erp-be-dev-redis.redis.cache.windows.net
│
Azure DNS resolver
│
Private DNS Zone: privatelink.redis.cache.windows.net
│
Returns 10.0.2.5 (private IP)
│
Traffic stays within VNetWithout the private DNS zone, DNS would return the public IP X.X.X.X and traffic would exit the VNet — which is blocked by the NSG denying outbound internet from private-apps.
12 Private DNS Zones
All 12 zones are provisioned per-environment and linked to the environment's VNet.
Group 1: Core Azure Services
| # | DNS Zone | Service | Used By |
|---|---|---|---|
| 1 | privatelink.azurecr.io | Azure Container Registry | All services pulling images |
| 2 | privatelink.redis.cache.windows.net | Azure Cache for Redis | All services using distributed cache |
| 3 | privatelink.servicebus.windows.net | Azure Service Bus | Message-bus consumers and producers |
| 4 | privatelink.blob.core.windows.net | Azure Blob Storage | Attachment service, reports |
| 5 | privatelink.vaultcore.azure.net | Azure Key Vault | All services reading secrets |
| 6 | privatelink.database.windows.net | Azure SQL / SQL Server | All services with relational data |
Group 2: Cosmos DB (Production)
| # | DNS Zone | Service | Used By |
|---|---|---|---|
| 7 | privatelink.mongo.cosmos.azure.com | Cosmos DB (Mongo API) | MongoDB-based services |
| 8 | privatelink.table.cosmos.azure.com | Cosmos DB (Table API) | Reserved for future use |
Group 3: Container Apps Environments
Azure Container Apps Environments create their own internal DNS zones for service-to-service communication. These are automatically managed by the CAE and should not be manually modified:
| # | DNS Zone | Purpose |
|---|---|---|
| 9 | {cae-name}.{region}.azurecontainerapps.io | Public CAE FQDN |
| 10 | {cae-name}.internal.{region}.azurecontainerapps.io | Private CAE internal FQDNs |
Group 4: Supporting Services
| # | DNS Zone | Service | Used By |
|---|---|---|---|
| 11 | privatelink.azurewebsites.net | Azure Static Web Apps / Web Apps | Frontend static apps |
| 12 | privatelink.monitor.azure.com | Azure Monitor (OTLP endpoint) | OpenTelemetry exporters |
Bicep Provisioning
Each private DNS zone is provisioned with a VNet link in Devops/azure/infrastructure/modules/privateDns.bicep:
bicep
// Example: Redis private DNS zone
resource redisPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.redis.cache.windows.net'
location: 'global'
}
resource redisVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: redisPrivateDnsZone
name: '${environmentName}-redis-dns-link'
location: 'global'
properties: {
virtualNetwork: {
id: vnet.id
}
registrationEnabled: false // Only resolves, does not auto-register
}
}
// A-record created by the private endpoint itself
resource redisPrivateEndpoint 'Microsoft.Network/privateEndpoints@2023-05-01' = {
name: 'mic-erp-be-${environment}-redis-pe'
location: location
properties: {
subnet: {
id: privateEndpointsSubnet.id
}
privateLinkServiceConnections: [
{
name: 'redis-connection'
properties: {
privateLinkServiceId: redisCache.id
groupIds: ['redisCache']
}
}
]
}
}
resource redisDnsGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
parent: redisPrivateEndpoint
name: 'default'
properties: {
privateDnsZoneConfigs: [
{
name: 'config'
properties: {
privateDnsZoneId: redisPrivateDnsZone.id
}
}
]
}
}DNS Resolution Troubleshooting
Verify DNS resolution from inside a Container App
bash
# Use a debug container or exec into a running container
az containerapp exec \
--name mic-erp-accounting \
--resource-group mic-erp-be-dev-apps-rg \
--command nslookup
# Inside the container:
nslookup mic-erp-be-dev-redis.redis.cache.windows.net
# Expected: returns 10.0.2.5 (private IP)
# Wrong: returns 20.X.X.X (public IP) → private DNS zone not linkedVerify DNS Zone VNet Link
bash
az network private-dns link vnet list \
--resource-group mic-erp-be-dev-network-rg \
--zone-name privatelink.redis.cache.windows.net \
--output tableExpected output:
Name VirtualNetworkName RegistrationEnabled ProvisioningState
---------------------------- --------------------- --------------------- ---------------
dev-redis-dns-link mic-erp-be-dev-vnet False SucceededCommon DNS Issues
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Service resolves to public IP | DNS zone not linked to VNet | Add VNet link in Bicep and redeploy |
DNS NXDOMAIN for .redis.cache.windows.net | Private endpoint not created | Provision private endpoint and DNS zone group |
| Intermittent DNS failures | DNS zone link in Failed state | Delete and recreate the VNet link |
| Private endpoint A-record missing | DNS zone group not attached to PE | Re-create the privateDnsZoneGroups resource |
DNS Zone Scope
Private DNS zones are provisioned in each environment's network resource group:
- dev:
mic-erp-be-dev-network-rg - stage:
mic-erp-be-stage-network-rg(note:stgmay appear in some names) - preprod:
mic-erp-be-preprod-network-rg - uat:
mic-erp-be-uat-network-rg - production:
mic-erp-be-production-network-rg
Note: The orphaned RG
mic-erp-bk-dev-network-rgexists from the oldbknaming convention. It is safe to ignore but should not be used for new resources.
Auto-Registration vs Manual A-Records
registrationEnabled: falseon all VNet links — resources in the VNet are not automatically registered in private DNS zones.- Private endpoint DNS records (A-records) are created and managed by the
privateDnsZoneGroupsresource attached to each private endpoint. - Do not manually create A-records — they are managed by Azure when private endpoints are created or updated.
ACR DNS Note
ACR has two private endpoint types, both requiring DNS zones:
privatelink.azurecr.io — Registry data plane
privatelink.azurecr.io — Registry control plane (same zone, different sub-resource)Both are handled by a single DNS zone. Ensure the private endpoint specifies both groupIds:
bicep
groupIds: ['registry', 'registry_data_{region}']
// e.g.: 'registry_data_uksouth'