AWS Azure Fargate Container Instances networking migracja

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.

Jerzy Kopaczewski ·
After migrating containers from AWS ECS/Fargate to Azure Container Instances (ACI) or Azure Container Apps (ACA), applications fail to communicate with each other properly. DNS resolution returns errors, service discovery does not work, and containers in private networks cannot access dependent services. The problem stems from fundamental differences in the networking model between AWS VPC + ECS Service Connect and Azure VNet + ACI/ACA networking.

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"}
The biggest pitfall when migrating from AWS Fargate to Azure ACI/ACA is assuming that service discovery will work out of the box. On AWS, ECS Service Connect is configured with a single line in the task definition. On Azure, the equivalent setup requires: Private DNS Zone + VNet link + A records + NSG rules + subnet delegation. Missing any of these components results in silent timeouts in inter-service communication. Azure Container Apps (ACA) offers a simplified model closer to ECS Service Connect - if you are migrating more than 3 services, consider ACA over raw ACI.

 

Jerzy Kopaczewski

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.