AWS Fargate to Azure Container Instances - Fixing Networking Issues During Migration
Fixing networking issues when migrating from AWS Fargate to Azure Container Instances: DNS resolution, service discovery, NSG vs Security Groups, private endpoints.
This runbook covers resolving common networking issues when migrating containers from AWS Fargate to Azure. For a detailed comparison of containerisation strategies on both platforms, see our article AWS vs Azure - a detailed comparison of container deployment strategies. Mapping AWS container services to their Azure equivalents is covered in our container services mapping runbook. If you are planning a container infrastructure migration to Azure - book a consultation.
Symptom
After deploying containers in Azure (ACI with VNet integration or ACA), network communication errors appear:
# Typical error messages:
# "Name or service not known" - DNS does not resolve service names
# "Connection refused" - port is open, but traffic blocked by NSG
# "Connection timed out" - no routing to private resources
# "no healthy upstream" - load balancer cannot see backends
# Diagnose from inside ACI container (exec into container)
az container exec \
--resource-group rg-app-prod \
--name my-api-container \
--exec-command "/bin/sh"
# Inside the container - test DNS
nslookup my-backend-service
# Expected on AWS (ECS Service Connect): resolves to 127.255.0.x or task private IP
# Actual on Azure ACI: "server can't find my-backend-service: NXDOMAIN"
# Test connection to database
nc -zv my-database.postgres.database.azure.com 5432
# "Connection timed out" = NSG or missing VNet integration
# Check ACI network configuration
az container show \
--resource-group rg-app-prod \
--name my-api-container \
--query '{ip:ipAddress.ip, ports:ipAddress.ports, subnet:subnetIds, networkProfile:networkProfile}' -o json
# Check effective NSG rules on subnet
az network nsg show \
--resource-group rg-app-prod \
--name nsg-aci-subnet \
--query 'securityRules[].{name:name, direction:direction, access:access, destPort:destinationPortRange, destAddr:destinationAddressPrefix, srcAddr:sourceAddressPrefix}' -o table
How to distinguish a network problem from an application issue:
| Symptom | DNS/Service Discovery issue | NSG/Firewall issue | VNet routing issue |
|---|---|---|---|
| nslookup | NXDOMAIN | Resolves correctly | Resolves correctly |
| ping IP | Works (if ICMP allowed) | “Request timeout” | “No route to host” |
| nc -zv port | “Name not resolved” | “Connection refused” | “Connection timed out” |
| curl endpoint | DNS error | Connection reset | Timeout after 30s |
Root Cause
AWS Fargate and Azure ACI/ACA have fundamentally different networking and service discovery models:
1. Service Discovery - no direct equivalent of ECS Service Connect:
On AWS, ECS Service Connect or Cloud Map automatically registers each task in a private DNS namespace (e.g. backend.local → task’s private IP). On Azure there is no direct equivalent. ACI has no built-in service discovery - containers cannot see each other by name without additional configuration.
| AWS (Fargate + ECS) | Azure (ACI/ACA) |
|---|---|
| ECS Service Connect → automatic DNS | No equivalent - must use Private DNS Zone or Azure Container Apps internal |
Cloud Map namespace (.local) |
Azure Private DNS Zone (requires manual configuration) |
| awsvpc network mode = ENI per task | VNet integration per container group (not per container) |
| Security Groups per task ENI | NSG per subnet (not per container) |
| Service mesh via App Mesh / Envoy | Dapr sidecar in Container Apps (separate service) |
2. NSG vs Security Groups - different granularity model:
AWS Security Groups are assigned per ENI (i.e. per task in Fargate). On Azure, NSGs are assigned per subnet or per NIC. ACI containers in a VNet share a subnet - they all have the same NSG rules. You cannot have different rules for different containers in the same subnet.
3. DNS resolution in private VNets:
On AWS, the VPC DNS resolver (169.254.169.253) automatically resolves names from Cloud Map and Route 53 Private Hosted Zones. On Azure, ACI containers in a VNet use Azure DNS (168.63.129.16) by default, but they do not have access to Azure Private DNS Zones without explicitly linking the DNS zone to the VNet.
4. Private endpoints - different routing model:
On AWS, PrivateLink + VPC endpoint creates an ENI in the subnet with a private IP. On Azure, Private Endpoints also create a NIC in the subnet, but require a Private DNS Zone with an A record pointing to the endpoint’s private IP. Without this, DNS resolution returns a public IP and traffic may be blocked by NSG.
5. Container group vs task definition:
An AWS task definition can contain multiple containers communicating over localhost. An Azure ACI container group works similarly (containers in a group share localhost), BUT the limit is 1 container group per VNet subnet delegation. This means you cannot have multiple container groups in the same subnet delegated to ACI.
Solution
A) Service discovery - Azure Private DNS Zone:
Replace ECS Service Connect / Cloud Map with Azure Private DNS Zone configuration:
# 1. Create Private DNS Zone
az network private-dns zone create \
--resource-group rg-app-prod \
--name app.internal
# 2. Link DNS zone to VNet (required for resolution)
az network private-dns link vnet create \
--resource-group rg-app-prod \
--zone-name app.internal \
--name link-vnet-prod \
--virtual-network vnet-prod \
--registration-enabled false
# 3. Add A records for each service
# First check the container's private IP
BACKEND_IP=$(az container show \
--resource-group rg-app-prod \
--name my-backend-service \
--query 'ipAddress.ip' -o tsv)
az network private-dns record-set a add-record \
--resource-group rg-app-prod \
--zone-name app.internal \
--record-set-name backend \
--ipv4-address $BACKEND_IP
# 4. Repeat for each service
DATABASE_IP=$(az container show \
--resource-group rg-app-prod \
--name my-database-proxy \
--query 'ipAddress.ip' -o tsv)
az network private-dns record-set a add-record \
--resource-group rg-app-prod \
--zone-name app.internal \
--record-set-name database-proxy \
--ipv4-address $DATABASE_IP
Automation with Terraform (recommended):
# dns.tf - Private DNS Zone with auto-registration
resource "azurerm_private_dns_zone" "app" {
name = "app.internal"
resource_group_name = azurerm_resource_group.prod.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "prod" {
name = "link-vnet-prod"
resource_group_name = azurerm_resource_group.prod.name
private_dns_zone_name = azurerm_private_dns_zone.app.name
virtual_network_id = azurerm_virtual_network.prod.id
registration_enabled = false
}
# A records for each service
resource "azurerm_private_dns_a_record" "backend" {
name = "backend"
zone_name = azurerm_private_dns_zone.app.name
resource_group_name = azurerm_resource_group.prod.name
ttl = 60
records = [azurerm_container_group.backend.ip_address]
}
Update service addresses in your application:
# Before (AWS ECS Service Connect):
# backend-service: http://backend.app-namespace:8080
# After (Azure Private DNS):
# backend-service: http://backend.app.internal:8080
B) Fix NSG configuration - rules per subnet:
# Check existing NSG rules
az network nsg rule list \
--resource-group rg-app-prod \
--nsg-name nsg-aci-subnet \
-o table
# Add rule allowing communication between containers in VNet
az network nsg rule create \
--resource-group rg-app-prod \
--nsg-name nsg-aci-subnet \
--name AllowVnetInbound \
--priority 100 \
--direction Inbound \
--source-address-prefixes VirtualNetwork \
--destination-address-prefixes VirtualNetwork \
--destination-port-ranges '*' \
--protocol '*' \
--access Allow
# Add rule for access to Azure SQL / PostgreSQL
az network nsg rule create \
--resource-group rg-app-prod \
--nsg-name nsg-aci-subnet \
--name AllowSqlOutbound \
--priority 200 \
--direction Outbound \
--source-address-prefixes VirtualNetwork \
--destination-address-prefixes Sql \
--destination-port-ranges 1433 5432 \
--protocol Tcp \
--access Allow
# Allow outbound traffic to Azure Storage (required by ACI runtime)
az network nsg rule create \
--resource-group rg-app-prod \
--nsg-name nsg-aci-subnet \
--name AllowStorageOutbound \
--priority 300 \
--direction Outbound \
--source-address-prefixes VirtualNetwork \
--destination-address-prefixes Storage \
--destination-port-ranges 443 \
--protocol Tcp \
--access Allow
C) VNet integration and subnet delegation:
# ACI requires subnet delegation - check if it is configured
az network vnet subnet show \
--resource-group rg-app-prod \
--vnet-name vnet-prod \
--name subnet-aci \
--query 'delegations[].{service:serviceName}' -o table
# If delegation is missing - add it (NOTE: subnet must be empty)
az network vnet subnet update \
--resource-group rg-app-prod \
--vnet-name vnet-prod \
--name subnet-aci \
--delegations Microsoft.ContainerInstance/containerGroups
# Create separate subnets for different services (each ACI group needs its own)
az network vnet subnet create \
--resource-group rg-app-prod \
--vnet-name vnet-prod \
--name subnet-aci-backend \
--address-prefix 10.0.10.0/24 \
--delegations Microsoft.ContainerInstance/containerGroups
az network vnet subnet create \
--resource-group rg-app-prod \
--vnet-name vnet-prod \
--name subnet-aci-worker \
--address-prefix 10.0.11.0/24 \
--delegations Microsoft.ContainerInstance/containerGroups
D) Private Endpoints for managed services (databases, storage):
# Create Private Endpoint for Azure PostgreSQL
az network private-endpoint create \
--resource-group rg-app-prod \
--name pe-postgres \
--vnet-name vnet-prod \
--subnet subnet-endpoints \
--private-connection-resource-id /subscriptions/SUB_ID/resourceGroups/rg-app-prod/providers/Microsoft.DBforPostgreSQL/flexibleServers/my-postgres \
--group-id postgresqlServer \
--connection-name conn-postgres
# Create Private DNS Zone for PostgreSQL
az network private-dns zone create \
--resource-group rg-app-prod \
--name privatelink.postgres.database.azure.com
# Link DNS zone to VNet
az network private-dns link vnet create \
--resource-group rg-app-prod \
--zone-name privatelink.postgres.database.azure.com \
--name link-postgres \
--virtual-network vnet-prod \
--registration-enabled false
# Add DNS record pointing to Private Endpoint's private IP
PE_IP=$(az network private-endpoint show \
--resource-group rg-app-prod \
--name pe-postgres \
--query 'customDnsConfigs[0].ipAddresses[0]' -o tsv)
az network private-dns record-set a add-record \
--resource-group rg-app-prod \
--zone-name privatelink.postgres.database.azure.com \
--record-set-name my-postgres \
--ipv4-address $PE_IP
E) Alternative: Azure Container Apps with built-in service discovery:
If you are migrating multiple inter-communicating services, consider Azure Container Apps instead of ACI. ACA offers built-in service discovery (similar to ECS Service Connect):
# Create Container Apps Environment (equivalent to ECS Cluster)
az containerapp env create \
--resource-group rg-app-prod \
--name cae-prod \
--location westeurope \
--infrastructure-subnet-resource-id /subscriptions/SUB_ID/resourceGroups/rg-app-prod/providers/Microsoft.Network/virtualNetworks/vnet-prod/subnets/subnet-cae \
--internal-only true
# Deploy backend - automatically gets internal DNS: backend.internal.cae-prod.westeurope.azurecontainerapps.io
az containerapp create \
--resource-group rg-app-prod \
--name backend \
--environment cae-prod \
--image myregistry.azurecr.io/backend:latest \
--target-port 8080 \
--ingress internal \
--min-replicas 2 \
--max-replicas 10
# Other services in the same environment can communicate by name
# URL: http://backend.internal.cae-prod.westeurope.azurecontainerapps.io
# Or short (within environment): http://backend
Validation
# 1. Test DNS resolution from inside the container
az container exec \
--resource-group rg-app-prod \
--name my-api-container \
--exec-command "nslookup backend.app.internal"
# Expected: returns a private IP from the VNet range
# 2. Test TCP connection to backend service
az container exec \
--resource-group rg-app-prod \
--name my-api-container \
--exec-command "nc -zv backend.app.internal 8080"
# Expected: "Connection to backend.app.internal 8080 port [tcp/*] succeeded!"
# 3. Test connection to database via Private Endpoint
az container exec \
--resource-group rg-app-prod \
--name my-api-container \
--exec-command "nc -zv my-postgres.privatelink.postgres.database.azure.com 5432"
# Expected: succeeded
# 4. Check effective network rules
az network nic list-effective-nsg \
--resource-group rg-app-prod \
--network-interface-name nic-aci-backend \
--query 'value[].effectiveSecurityRules[?direction==`Inbound` && access==`Allow`].{dest:destinationPortRange, src:sourceAddressPrefix}' -o table
# 5. End-to-end application test
curl -s http://my-api.app.internal:8080/health | jq .
# Expected: {"status": "healthy", "database": "connected", "backend": "reachable"}
Migrating containers from AWS to Azure?
Schedule a free 30-minute call. We will analyse the network architecture of your AWS services and plan a migration to Azure that preserves service discovery and private networking.