Most resources in Azure can be well protected by disabling public access. If public access is completely deactivated, access can take place via private endpoints. In most cases it is sufficient to restrict access to selected network to use service endpoint.

The Azure Container Registry behaves differently. It is not possible to unlock selected networks. In addition, the activated IPs must be public. According to the documentation, retrieval of container instances via private endpoints is also not possible. If access from Trusted Services is activated, Container Instances can be called up, but IP activation is still necessary.

Access to the container registry can be encapsulated via an application gateway. The call tzuehlke.azurecr.io/v2/ can be used for the health check for the backend. Because the access is anonymous, the health check can validate for a 401 response to ensure the backend is reachable. The Application Gateway adds the header value X-Forwarded-For to the header when forwarding to the backend. This contains a list of IPs, starting with the client IP and followed by the IP of the application gateway. However, even after deleting the header value, it is not possible to call up the ACR without unlocking the client IP:

docker pull cr.zuehlke.cloud/nginx
Using default tag: latest
Error response from daemon: Head "https://cr.zuehlke.cloud/v2/nginx/manifests/latest": denied: client with IP '91.38.188.165' is not allowed access. Refer https://aka.ms/acr/firewall to grant access.

If an attempt is made to create a container instance via the portal, the dialog does not offer a selection of images. The selection is only possible once the current client IP has been activated in the firewall of the ACR:

But even if the activation for the current client IP has taken place, no instance can be created. Regardless of whether the instance should be public or private, the error message is:

{
    "status": "Failed",
    "error": {
        "code": "InaccessibleImage",
        "message": "The image 'tzuehlke.azurecr.io/nginx:latest' in container group 'ci' is not accessible. Please check the image and registry credential."
    }
}

And even if the request is made through the Application Gateway, the error message is the same:

{
    "status": "Failed",
    "error": {
        "code": "InaccessibleImage",
        "message": "The image 'cr.zuehlke.cloud/nginx' in container group 'ci' is not accessible. Please check the image and registry credential."
    }
}

The behavior of container apps is the same:

{
    "status": "Failed",
    "error": {
        "code": "WebhookInvalidParameterValue",
        "message": "The following field(s) are either invalid or missing. Invalid value: \"cr.zuehlke.cloud/nginx\": GET https:?scope=repository%3Anginx%3Apull&service=tzuehlke.azurecr.io: DENIED: client with IP '20.103.186.69' is not allowed access. Refer https://aka.ms/acr/firewall to grant access.: template.containers.bbhh.image."
    }
}

Conclusion

Securing an Azure Container Registry makes little sense. The retrieving IP must be entered even by private endpoints or application gateways. The Azure container registry checks the source IP in the incoming packets at the network level and this check cannot be bypassed.

Also, container instances or container apps cannot be created with existing public IPs. Activation before the resources are created is therefore not possible.

If the ACR should only be called from your own IPs, the Azure NAT gateway could help.