Back to blog
AzureBest PracticesCloud SecurityCompute & ContainersNetworking

Azure Container Registry Has Public Access: Risks and Remediation

Learn why public network access on Azure Container Registry is risky and how to disable it with CLI, Terraform, private endpoints, and Azure Policy.

TL;DR

This check flags Azure Container Registries reachable over the public internet. A public registry exposes your image metadata and attack surface to anyone who can reach the endpoint, so disable public network access and use private endpoints or trusted Azure services instead.

Your container images are not just application code. They carry base layers, dependency manifests, build secrets that someone forgot to scrub, and the exact blueprint of what runs in production. When an Azure Container Registry (ACR) accepts connections from any IP on the internet, you are putting that blueprint one credential leak away from a stranger. This check, acr_publicaccess, exists to catch registries that have public network access turned on.

Below we walk through what the check looks at, why a public registry is more dangerous than it sounds, and how to lock it down without breaking your build pipelines.


What this check detects

The check inspects the network configuration of each Azure Container Registry in your subscription and flags any registry where publicNetworkAccess is set to Enabled. This is the default setting when you create a registry, so most registries fail this check unless someone has explicitly hardened them.

Concretely, it looks for this property:

{
  "name": "myregistry",
  "properties": {
    "publicNetworkAccess": "Enabled",
    "networkRuleSet": {
      "defaultAction": "Allow"
    }
  }
}

When publicNetworkAccess is Enabled and there are no network rules restricting which IP ranges may connect, the registry login endpoint (myregistry.azurecr.io) resolves to a public IP and accepts TLS connections from anywhere.

Note: Public network access is not the same as anonymous pull. Authentication is still required by default. But network reachability is the first link in most attack chains, and a publicly reachable endpoint is exactly what credential-stuffing and token-replay attacks need to be useful.


Why it matters

A registry that only authenticated clients can use still benefits from being unreachable to the wider internet. Here is the practical risk.

Stolen credentials become immediately usable

ACR access often relies on service principal credentials, admin user passwords, or repository-scoped tokens. These end up in CI/CD variables, Kubernetes pull secrets, developer laptops, and the occasional committed .env file. When credentials leak and the registry is public, an attacker can authenticate and pull your images from anywhere. If the admin account is enabled, they may even push tampered images that your clusters then deploy.

Image contents are a reconnaissance goldmine

Pulled images reveal your dependency versions, internal package names, build tooling, and sometimes hardcoded secrets baked into layers. This shortens the path from initial access to a working exploit.

Danger: If your registry has the admin user enabled and public access enabled, a single leaked admin password gives an attacker full push access. They can replace a trusted image tag with a malicious one, and any cluster pulling that tag will run their code on your next rollout.

Larger attack surface

Every internet-facing endpoint is something you have to defend. A registry that is only reachable from your own VNet removes an entire class of exposure, including brute-force attempts, vulnerability scanning, and accidental anonymous-pull misconfigurations that would otherwise be exploitable from outside.


How to fix it

There are two common end states. Pick based on how your build and deploy systems reach the registry.

  1. Private endpoint only — the registry is reachable only from inside your virtual network. Best for production.
  2. IP allowlist — public access stays on but only specified IP ranges may connect. A reasonable middle ground when you have hosted CI runners with stable egress IPs.

Option 1: Disable public access entirely

This requires the Premium SKU, since network access controls and private endpoints are Premium-only features.

Warning: Network rules and private endpoints require the Premium ACR tier. If your registry is Basic or Standard you will need to upgrade first, which has a cost impact (roughly a few hundred dollars more per month per registry). Check your tier before running these commands.

# Check the current SKU
az acr show --name myregistry --query "sku.tier" -o tsv

# Upgrade to Premium if needed
az acr update --name myregistry --sku Premium

# Disable public network access
az acr update --name myregistry --public-network-enabled false

Then create a private endpoint so your VNet workloads can still reach it:

# Disable private endpoint network policies on the subnet
az network vnet subnet update \
  --name mysubnet \
  --vnet-name myvnet \
  --resource-group myrg \
  --disable-private-endpoint-network-policies true

# Create the private endpoint
az network private-endpoint create \
  --name acr-pe \
  --resource-group myrg \
  --vnet-name myvnet \
  --subnet mysubnet \
  --private-connection-resource-id $(az acr show --name myregistry --query id -o tsv) \
  --group-id registry \
  --connection-name acr-connection

You will also need a private DNS zone (privatelink.azurecr.io) linked to the VNet so that myregistry.azurecr.io resolves to the private IP. Without it, clients inside the VNet will still try the public name and fail.

Option 2: Restrict to known IP ranges

If full private networking is too big a change right now, set the default network action to deny and allow only your trusted egress IPs.

# Default deny
az acr update --name myregistry --default-action Deny

# Allow a specific CIDR (for example, your CI runner egress)
az acr network-rule add --name myregistry --ip-address 203.0.113.10/32

Tip: Pair the network rule change with disabling the admin user (az acr update --name myregistry --admin-enabled false) and using Microsoft Entra tokens or managed identities for authentication. Network controls and identity controls reinforce each other.

Fixing it with Terraform

If you manage ACR as code, set the relevant attributes directly so the fix survives the next apply.

resource "azurerm_container_registry" "main" {
  name                          = "myregistry"
  resource_group_name           = azurerm_resource_group.main.name
  location                      = azurerm_resource_group.main.location
  sku                           = "Premium"
  admin_enabled                 = false
  public_network_access_enabled = false

  network_rule_set {
    default_action = "Deny"
    ip_rule {
      action   = "Allow"
      ip_range = "203.0.113.10/32"
    }
  }
}

How to prevent it from happening again

Fixing one registry by hand is fine. Stopping the next one from launching with public access is what keeps the check green over time.

Azure Policy

Assign a policy that denies or audits registries with public access enabled. Azure ships a built-in definition for this:

# Container registries should have public network access disabled
az policy assignment create \
  --name "deny-acr-public" \
  --display-name "Deny ACR public network access" \
  --scope "/subscriptions/" \
  --policy "0fdf0491-d080-4575-b627-ad0e843cba0f" \
  --params '{"effect":{"value":"Deny"}}'

With the effect set to Deny, any attempt to create or update a registry with public access enabled is rejected at the control plane.

Warning: A Deny policy stops non-compliant deployments outright. Roll it out in Audit mode first so you can find existing violations and fix them before they start breaking legitimate pipelines.

CI/CD gate

Catch the misconfiguration before it reaches Azure by scanning your IaC in the pipeline. Checkov, for example, has a built-in rule for this:

checkov -d ./infra --check CKV_AZURE_139

Wire that into your pull request checks so a Terraform plan that enables public access never merges.


Best practices

  • Default to private. Treat public network access as an explicit, justified exception rather than the starting point.
  • Disable the admin user. Use Microsoft Entra authentication, managed identities, or repository-scoped tokens. The shared admin password is a single point of failure.
  • Use private endpoints for production. They keep registry traffic on the Azure backbone and out of the public internet entirely.
  • Scope tokens narrowly. When you do need pull or push tokens, scope them to specific repositories and actions instead of granting registry-wide access.
  • Enable content trust and image scanning. Network hardening protects access. Signing and scanning protect the integrity of what is inside.
  • Audit continuously. Network settings drift as teams spin up new registries. A recurring check catches the ones that slip through policy.

Disabling public access on a container registry is one of those changes with a high security payoff and, once your DNS and networking are in place, very little ongoing cost. Lock it down, gate it in your pipeline, and let policy stop the next public registry before it ever exists.