Pull ACR Images from AKS Over a Private Endpoint
Keep AKS image pulls off the public internet by pairing an ACR Private Endpoint with Private DNS and a managed identity.
24 Jul 2025, 06:38 UTC

The problem: image pulls leave the virtual network
By default, an AKS node reaches Azure Container Registry (ACR) over the public internet. The kubelet authenticates with a managed identity or an image pull secret, but the container image itself travels across public Azure egress. That path depends on outbound firewall rules, internet availability, and long-lived registry credentials stored in the cluster. For production workloads, you often want the registry reachable only from inside the virtual network.
Azure Private Link solves this by placing a private endpoint for ACR inside your AKS virtual network. The registry gets a private IP in your subnet, and Azure Private DNS resolves the registry name to that address so AKS nodes never use the public endpoint.
Design: private endpoint, private DNS, and managed identity
Private endpoint
A private endpoint is a network interface in your virtual network that maps to an Azure service resource. For ACR, the endpoint lives in the same subnet or a dedicated subnet as your AKS nodes. Traffic to the endpoint does not traverse the public internet.
Private DNS
ACR private endpoints use the DNS zone privatelink.azurecr.io. You create this zone, add an A record for your registry, and link the zone to the AKS virtual network. Without the link, nodes resolve the registry to its public IP and the private endpoint is never used.
Authentication
Use the AKS node's system-assigned managed identity and assign it the AcrPull role on the registry. Avoid image pull secrets with passwords where possible; they work but create credential rotation overhead.
Worked example: configure private image pulls
Run these commands from Azure Cloud Shell or a local machine with the Azure CLI and kubectl. You need Contributor rights on the resource group and User Access Administrator rights to create role assignments.
Replace the placeholders with your own values.
# Variables
RG="my-rg"
LOCATION="eastus"
VNET="my-vnet"
AKS_SUBNET="aks-subnet"
AKS_NAME="myaks"
ACR_NAME="myregistry"
DNS_ZONE="privatelink.azurecr.io"
SUB=$(az account show --query id -o tsv)
# 1. Create the private endpoint for ACR in the AKS subnet
az network private-endpoint create \
--resource-group $RG \
--name acr-private-endpoint \
--location $LOCATION \
--subnet $AKS_SUBNET \
--vnet-name $VNET \
--private-connection-resource-id "/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.ContainerRegistry/registries/$ACR_NAME" \
--group-ids registry \
--connection-name acr-connection
# 2. Create the private DNS zone and link it to the VNet
az network private-dns zone create --resource-group $RG --name $DNS_ZONE
az network private-dns link vnet create \
--resource-group $RG \
--zone-name $DNS_ZONE \
--name acr-dns-link \
--virtual-network $VNET \
--registration-enabled false
# 3. Add the A record mapping the registry to the private endpoint IP
NIC_ID=$(az network private-endpoint show --resource-group $RG --name acr-private-endpoint --query "networkInterfaces[0].id" -o tsv)
PRIVATE_IP=$(az network nic show --ids $NIC_ID --query "ipConfigurations[0].privateIpAddress" -o tsv)
az network private-dns record-set a create --resource-group $RG --zone-name $DNS_ZONE --name $ACR_NAME
az network private-dns record-set a add-record --resource-group $RG --zone-name $DNS_ZONE --record-set-name $ACR_NAME --ipv4-address $PRIVATE_IP
# 4. Grant the AKS node identity pull access
AKS_IDENTITY=$(az aks show --resource-group $RG --name $AKS_NAME --query "identityProfile.kubeletidentity.objectId" -o tsv)
ACR_ID="/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.ContainerRegistry/registries/$ACR_NAME"
az role assignment create --assignee $AKS_IDENTITY --role AcrPull --scope $ACR_ID
# 5. Deploy a test pod that pulls from the private registry name
kubectl run acr-private-pull --image=$ACR_NAME.azurecr.io/myapp:v1 --restart=Never
Expected checks:
- The private endpoint provisioning state reaches
Succeededand its network interface shows a private IP inside the AKS subnet. - From a pod or jumpbox in the VNet,
nslookup $ACR_NAME.azurecr.ioresolves to that private IP, not the public registry address. - The test pod reaches
ContainerCreatingand thenRunningwithout image pull errors.
Risk: Private Endpoint creation is eventually consistent. DNS propagation can take a minute or two, so a pull immediately after setup may fail with a DNS or connection error until the record is live.
Enforce private-only access and the CI/CD caveat
Creating the endpoint does not block public access. To enforce the private path, disable public network access on the registry:
az acr update --name $ACR_NAME --resource-group $RG --public-network-enabled false
After this change, any agent outside the VNet—such as a CI/CD runner on GitHub Actions or Azure DevOps Microsoft-hosted agents—cannot reach the registry unless it connects through a private path, such as a VPN, ExpressRoute, or a self-hosted runner inside the network. Plan that exception before disabling public access.
Limitations and verification
- Private Link adds an hourly endpoint charge plus data-transfer costs. Review pricing for your region before enabling it in production.
- DNS becomes a hard dependency. If the private DNS zone link is missing or misconfigured, image pulls fall back to the public IP or fail entirely.
- The
AcrPullassignment must target the registry scope and the identity used by the kubelet. A user-assigned identity must be attached to the node pool; a system-assigned identity is assigned automatically.
Practical check: after configuration, run kubectl describe pod acr-private-pull and confirm Successfully pulled image in the events. Then inspect the endpoint resource group for the private endpoint connection state. If you disabled public access, test from an external IP to confirm the registry now rejects the connection.
Rollback
Because this procedure creates Azure resources, you can reverse it by deleting the private endpoint, removing the DNS zone link and A record, revoking the AcrPull role assignment, and re-enabling public network access on the registry. Re-enabling public access restores the original internet-facing path.
Closing
Pairing an ACR Private Endpoint with Private DNS and a managed identity keeps image pulls inside your virtual network and removes public registry credentials from the data path. Start with a non-production cluster, verify DNS resolution and pod pull events, then disable public access only after your CI/CD agents have a private route.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.