Skip to content

Instantly share code, notes, and snippets.

@maskati
Last active October 21, 2025 14:28
Show Gist options
  • Select an option

  • Save maskati/e56d1bf6b4cc0e14f77d409e7f158e45 to your computer and use it in GitHub Desktop.

Select an option

Save maskati/e56d1bf6b4cc0e14f77d409e7f158e45 to your computer and use it in GitHub Desktop.
Azure Private Link service Direct Connect

Until recently Azure Private Link has been restricted to specific Microsoft enabled resource types or your own Azure VM hosted resources.

A notable limitation has been the requirement for the destination to be a Standard Load Balancer with a backend pool configured by NIC. This excluded services such as VNet integrated Azure Container Instances, which do not support private endpoints and also do not provision a NIC in the VNet and therefore could only be routed by IP address.

Microsoft recently released in public preview Private Link Service Direct Connect. Direct Connect allows connectivity to any privately routable destination IP address. To demonstrate this new feature we can publish a private Azure Container Instance over Azure Private Link Service direct connect.

Important

To use direct connect you need to enable the feature flag Microsoft.Network/AllowPrivateLinkserviceUDR on your subscription e.g. using az feature register --namespace Microsoft.Network --name AllowPrivateLinkserviceUDR. Review the direct connect prerequisites for more details.

Deploy to Azure

The example deploys the following:

flowchart LR
  subgraph VNET1["vnet-pls: 10.0.0.0/16"]
    subgraph SN1["subnet-aci: 10.0.1.0/24"]
      ACI1["Container Instance / nginx 10.0.1.4"]
    end
    subgraph SN2["subnet-pls: 10.0.2.0/24"]
      subgraph PLS["Private Link Service"]
        NAT1["NAT IP 10.0.2.4"]
        NAT2["NAT IP 10.0.2.5"]
      end
    end
    NAT1 -- "Direct connect" --> ACI1
    NAT2 -- "Direct connect" --> ACI1
  end
  subgraph VNET2["vnet-pe: 10.0.0.0/16"]
    subgraph SN3["subnet-aci: 10.0.1.0/24"]
      ACI2["Container Instance / client 10.0.1.4"]
    end
    subgraph SN4["subnet-pe: 10.0.2.0/24"]
      PE["Private Endpoint 10.0.2.4"]
    end
  end
  subgraph DNS["DNS: directconnect.test"]
    A["A ping -> 10.0.2.4"]
  end
  ACI2 -- "curl http://ping.directconnect.test" --> PE
  PE -- "Private Endpoint Connection" --> PLS
  VNET2 --- DNS
  DNS -.- PE
Loading

We can verify connectivity by running a curl command using az container exec (see the curlCommand deployment output parameter), noting that connections are distributed across private link service NAT addresses:

~ % az container exec --ids '/subscriptions/.../resourceGroups/.../providers/Microsoft.ContainerInstance/containerGroups/aci-pe' --exec-command 'curl http://ping.directconnect.test.'
2025-10-21T10:32:14+00:00 remote_addr=10.0.2.4

~ % az container exec --ids '/subscriptions/.../resourceGroups/.../providers/Microsoft.ContainerInstance/containerGroups/aci-pe' --exec-command 'curl http://ping.directconnect.test.'
2025-10-21T10:32:20+00:00 remote_addr=10.0.2.5
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"languageVersion": "2.0",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.38.33.27573",
"templateHash": "18102201612663974453"
}
},
"parameters": {
"location": {
"type": "string",
"allowedValues": [
"northcentralus",
"eastus",
"centralus",
"southcentralus",
"westus",
"westus2",
"westus3",
"asiasoutheast",
"australiaeast",
"spaincentral"
]
}
},
"variables": {
"$fxv#0": "d29ya2VyX3Byb2Nlc3NlcyAxOwoKZXZlbnRzIHsKICAgIHdvcmtlcl9jb25uZWN0aW9ucyAxMDI0Owp9CgpodHRwIHsKICAgIGRlZmF1bHRfdHlwZSB0ZXh0L3BsYWluOwogICAgc2VydmVyIHsKICAgICAgICBsaXN0ZW4gODA7CiAgICAgICAgbG9jYXRpb24gLyB7CiAgICAgICAgICAgICMgcmVtb3RlX2FkZHIgaXMgdGhlIHByaXZhdGUgbGluayBzZXJ2aWNlIG5hdGVkIGlwCiAgICAgICAgICAgIHJldHVybiAyMDAgIiR0aW1lX2lzbzg2MDEgcmVtb3RlX2FkZHI9JHJlbW90ZV9hZGRyXG4iOwogICAgICAgIH0KICAgIH0KfQo=",
"vnetPlsAddressPrefix": "10.0.0.0/16",
"vnetPeAddressPrefix": "10.0.0.0/16"
},
"resources": {
"vnetPls::subnetAci": {
"existing": true,
"type": "Microsoft.Network/virtualNetworks/subnets",
"apiVersion": "2024-10-01",
"name": "[format('{0}/{1}', 'vnet-pls', 'subnet-aci')]",
"dependsOn": [
"vnetPls"
]
},
"vnetPls::subnetPls": {
"existing": true,
"type": "Microsoft.Network/virtualNetworks/subnets",
"apiVersion": "2024-10-01",
"name": "[format('{0}/{1}', 'vnet-pls', 'subnet-pls')]",
"dependsOn": [
"vnetPls"
]
},
"vnetPe::subnetAci": {
"existing": true,
"type": "Microsoft.Network/virtualNetworks/subnets",
"apiVersion": "2024-10-01",
"name": "[format('{0}/{1}', 'vnet-pe', 'subnet-aci')]",
"dependsOn": [
"vnetPe"
]
},
"vnetPe::subnetPe": {
"existing": true,
"type": "Microsoft.Network/virtualNetworks/subnets",
"apiVersion": "2024-10-01",
"name": "[format('{0}/{1}', 'vnet-pe', 'subnet-pe')]",
"dependsOn": [
"vnetPe"
]
},
"dnsDirectConnectTest::linkVnetPe": {
"type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
"apiVersion": "2024-06-01",
"name": "[format('{0}/{1}', 'directconnect.test', 'link-vnet-pe')]",
"location": "global",
"properties": {
"registrationEnabled": false,
"virtualNetwork": {
"id": "[resourceId('Microsoft.Network/virtualNetworks', 'vnet-pe')]"
}
},
"dependsOn": [
"dnsDirectConnectTest",
"vnetPe"
]
},
"vnetPls": {
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2024-10-01",
"name": "vnet-pls",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('vnetPlsAddressPrefix')]"
]
},
"subnets": [
{
"name": "subnet-aci",
"properties": {
"addressPrefix": "[cidrSubnet(variables('vnetPlsAddressPrefix'), 24, 1)]",
"delegations": [
{
"name": "dMicrosoft.ContainerInstance/containerGroups",
"properties": {
"serviceName": "Microsoft.ContainerInstance/containerGroups"
}
}
]
}
},
{
"name": "subnet-pls",
"properties": {
"addressPrefix": "[cidrSubnet(variables('vnetPlsAddressPrefix'), 24, 2)]",
"privateLinkServiceNetworkPolicies": "Disabled"
}
}
]
}
},
"aciPls": {
"type": "Microsoft.ContainerInstance/containerGroups",
"apiVersion": "2025-09-01",
"name": "aci-pls",
"location": "[parameters('location')]",
"properties": {
"sku": "Standard",
"osType": "Linux",
"restartPolicy": "OnFailure",
"ipAddress": {
"type": "Private",
"ports": [
{
"port": 80
}
]
},
"subnetIds": [
{
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet-pls', 'subnet-aci')]"
}
],
"volumes": [
{
"name": "nginxconfvolume",
"secret": {
"nginx.conf": "[variables('$fxv#0')]"
}
}
],
"containers": [
{
"name": "nginx",
"properties": {
"image": "mcr.microsoft.com/azurelinux/base/nginx:1.25",
"command": [
"nginx",
"-g",
"daemon off;",
"-c",
"/mnt/nginx.conf"
],
"resources": {
"requests": {
"cpu": "[json('0.1')]",
"memoryInGB": "[json('0.1')]"
}
},
"ports": [
{
"port": 80
}
],
"volumeMounts": [
{
"name": "nginxconfvolume",
"mountPath": "/mnt"
}
]
}
}
]
},
"dependsOn": [
"vnetPls"
]
},
"plsDirectConnect": {
"type": "Microsoft.Network/privateLinkServices",
"apiVersion": "2024-10-01",
"name": "pls-directconnect",
"location": "[parameters('location')]",
"properties": {
"destinationIPAddress": "[reference('aciPls').ipAddress.ip]",
"enableProxyProtocol": false,
"ipConfigurations": [
{
"name": "ipconfig1",
"properties": {
"primary": true,
"privateIPAddressVersion": "IPv4",
"privateIPAllocationMethod": "Static",
"privateIPAddress": "[cidrHost(reference('vnetPls::subnetPls').addressPrefix, 3)]",
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet-pls', 'subnet-pls')]"
}
}
},
{
"name": "ipconfig2",
"properties": {
"primary": false,
"privateIPAddressVersion": "IPv4",
"privateIPAllocationMethod": "Static",
"privateIPAddress": "[cidrHost(reference('vnetPls::subnetPls').addressPrefix, 4)]",
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet-pls', 'subnet-pls')]"
}
}
}
]
},
"dependsOn": [
"aciPls",
"vnetPls",
"vnetPls::subnetPls"
]
},
"vnetPe": {
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2024-10-01",
"name": "vnet-pe",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"addressPrefixes": [
"[variables('vnetPeAddressPrefix')]"
]
},
"subnets": [
{
"name": "subnet-aci",
"properties": {
"addressPrefix": "[cidrSubnet(variables('vnetPeAddressPrefix'), 24, 1)]",
"delegations": [
{
"name": "Microsoft.ContainerInstance/containerGroups",
"properties": {
"serviceName": "Microsoft.ContainerInstance/containerGroups"
}
}
]
}
},
{
"name": "subnet-pe",
"properties": {
"addressPrefix": "[cidrSubnet(variables('vnetPeAddressPrefix'), 24, 2)]"
}
}
]
}
},
"aciPe": {
"type": "Microsoft.ContainerInstance/containerGroups",
"apiVersion": "2025-09-01",
"name": "aci-pe",
"location": "[parameters('location')]",
"properties": {
"sku": "Standard",
"osType": "Linux",
"restartPolicy": "Never",
"ipAddress": {
"type": "Private",
"ports": [
{
"port": 65534,
"protocol": "UDP"
}
]
},
"subnetIds": [
{
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet-pe', 'subnet-aci')]"
}
],
"containers": [
{
"name": "azurelinux",
"properties": {
"image": "mcr.microsoft.com/azurelinux/base/core:3.0",
"command": [
"sleep",
"infinity"
],
"resources": {
"requests": {
"cpu": "[json('0.1')]",
"memoryInGB": "[json('0.1')]"
}
},
"ports": [
{
"port": 65534,
"protocol": "UDP"
}
]
}
}
]
},
"dependsOn": [
"vnetPe"
]
},
"peDirectConnect": {
"type": "Microsoft.Network/privateEndpoints",
"apiVersion": "2024-10-01",
"name": "pe-directconnect",
"location": "[parameters('location')]",
"properties": {
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', 'vnet-pe', 'subnet-pe')]"
},
"privateLinkServiceConnections": [
{
"name": "pls-connection",
"properties": {
"privateLinkServiceId": "[resourceId('Microsoft.Network/privateLinkServices', 'pls-directconnect')]",
"requestMessage": "Approve connection for direct connect"
}
}
]
},
"dependsOn": [
"plsDirectConnect",
"vnetPe"
]
},
"dnsDirectConnectTest": {
"type": "Microsoft.Network/privateDnsZones",
"apiVersion": "2024-06-01",
"name": "directconnect.test",
"location": "global"
},
"dns": {
"type": "Microsoft.Resources/deployments",
"apiVersion": "2025-04-01",
"name": "[format('dns-{0}', uniqueString('dns', deployment().name))]",
"properties": {
"expressionEvaluationOptions": {
"scope": "inner"
},
"mode": "Incremental",
"parameters": {
"dnsAName": {
"value": "ping"
},
"dnsRid": {
"value": "[resourceId('Microsoft.Network/privateDnsZones', 'directconnect.test')]"
},
"nicRid": {
"value": "[reference('peDirectConnect').networkInterfaces[0].id]"
}
},
"template": {
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.38.33.27573",
"templateHash": "12135959616729773951"
}
},
"parameters": {
"nicRid": {
"type": "string",
"minLength": 116,
"metadata": {
"description": "The resource ID of the private endpoint network interface"
}
},
"dnsRid": {
"type": "string",
"minLength": 116,
"metadata": {
"description": "The resource ID of the private DNS zone"
}
},
"dnsAName": {
"type": "string",
"minLength": 1,
"maxLength": 253,
"metadata": {
"description": "The A record name to create in the private DNS zone"
}
}
},
"resources": [
{
"type": "Microsoft.Network/privateDnsZones/A",
"apiVersion": "2024-06-01",
"name": "[format('{0}/{1}', split(parameters('dnsRid'), '/')[8], parameters('dnsAName'))]",
"properties": {
"aRecords": [
{
"ipv4Address": "[reference(resourceId('Microsoft.Network/networkInterfaces', split(parameters('nicRid'), '/')[8]), '2024-10-01').ipConfigurations[0].properties.privateIPAddress]"
}
],
"ttl": 300
}
}
],
"outputs": {
"ipAddress": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Network/networkInterfaces', split(parameters('nicRid'), '/')[8]), '2024-10-01').ipConfigurations[0].properties.privateIPAddress]"
},
"fqdn": {
"type": "string",
"value": "[reference(resourceId('Microsoft.Network/privateDnsZones/A', split(parameters('dnsRid'), '/')[8], parameters('dnsAName')), '2024-06-01').fqdn]"
}
}
}
},
"dependsOn": [
"dnsDirectConnectTest",
"peDirectConnect"
]
}
},
"outputs": {
"curlCommand": {
"type": "string",
"value": "[format('az container exec --ids ''{0}'' --exec-command ''curl http://{1}''', resourceId('Microsoft.ContainerInstance/containerGroups', 'aci-pe'), reference('dns').outputs.fqdn.value)]"
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment