Container App + ACR Authentication
Reference guide for configuring Container App authentication with Azure Container Registry. Use this when generating Bicep that involves Container Apps pulling images from ACR.
How azd Deploy Works with ACR
Understanding this flow prevents most ACR auth issues:
- •
azd provisioncreates infrastructure (ACR, Container App Environment, Container App) - •
azd deploybuilds the Docker image, pushes it to ACR, then updates the Container App's image reference - •azd deploy does NOT configure ACR pull auth — the Container App must already have permission to pull from ACR
This means your Bicep must configure ACR pull authentication during provisioning.
Recommended Approach: System-Assigned Managed Identity + AcrPull Role
This is the preferred, secure approach. The key challenge is dependency ordering: the Container App's principal ID is only known after creation, but the role assignment needs that principal ID.
Solution: Use dependsOn — create the Container App first with a placeholder image,
then assign the role.
// 1. Container Apps Environment (via AVM)
module containerAppsStack 'br/public:avm/ptn/azd/container-apps-stack:0.1.0' = {
name: 'container-apps-stack'
scope: rg
params: {
containerAppsEnvironmentName: 'cae-${resourceToken}'
containerRegistryName: 'cr${resourceToken}'
logAnalyticsWorkspaceResourceId: monitoring.outputs.logAnalyticsWorkspaceResourceId
location: location
}
}
// 2. Container App with system-assigned identity (starts with a placeholder image)
module api 'br/public:avm/ptn/azd/acr-container-app:0.1.0' = {
name: 'api'
scope: rg
params: {
name: 'ca-api-${resourceToken}'
containerAppsEnvironmentName: containerAppsStack.outputs.environmentName
containerRegistryName: containerAppsStack.outputs.registryName
location: location
identityType: 'SystemAssigned'
// Do NOT set registryIdentity here yet — role assignment happens below
exists: false
containerName: 'api'
targetPort: 3000
env: []
tags: union(tags, { 'azd-service-name': 'api' })
}
}
// 3. AcrPull role assignment AFTER Container App exists
// This uses the Container App's system-assigned identity principal ID
module acrPullRole 'br/public:avm/ptn/authorization/resource-role-assignment:0.1.1' = {
name: 'acr-pull-role'
scope: rg
params: {
principalId: api.outputs.systemAssignedMIPrincipalId
roleDefinitionId: '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull
resourceId: containerAppsStack.outputs.registryId
principalType: 'ServicePrincipal'
}
}
Key Outputs from AVM Modules
These are the correct output names (do NOT guess):
| Module | Output | Description |
|---|---|---|
container-apps-stack | .outputs.environmentName | Container Apps Environment name |
container-apps-stack | .outputs.registryName | ACR name |
container-apps-stack | .outputs.registryLoginServer | ACR login server URL |
container-apps-stack | .outputs.registryId | ACR resource ID |
acr-container-app | .outputs.uri | Container App URI (NOT fqdn) |
acr-container-app | .outputs.systemAssignedMIPrincipalId | System identity principal ID |
acr-container-app | .outputs.name | Container App name |
⚠️ The output is
.outputs.uri— NOT.outputs.fqdn. Usingfqdnwill cause a Bicep compile error.
Alternative: ACR Admin Credentials (Simpler but Less Secure)
For prototypes or simple apps where managed identity overhead isn't worth it:
module containerAppsStack 'br/public:avm/ptn/azd/container-apps-stack:0.1.0' = {
name: 'container-apps-stack'
scope: rg
params: {
containerAppsEnvironmentName: 'cae-${resourceToken}'
containerRegistryName: 'cr${resourceToken}'
containerRegistryAdminUserEnabled: true // Enable admin credentials
logAnalyticsWorkspaceResourceId: monitoring.outputs.logAnalyticsWorkspaceResourceId
location: location
}
}
When containerRegistryAdminUserEnabled: true is set on the stack, azd deploy can
use the admin credentials to push and the Container App can pull using those same credentials.
⚠️ Admin credentials are less secure. Prefer managed identity for production apps.
Common Pitfalls
1. Zone Redundancy Requires a Subnet
The container-apps-stack module defaults to zoneRedundant: true, which requires
a delegated subnet. For simple apps without a VNet:
params: {
zoneRedundant: false // Disable if no subnet/VNet is configured
}
2. Circular Dependency: Identity → Role → Pull
You cannot configure registryIdentity: 'system' on the Container App AND
assign the AcrPull role in the same Bicep deployment if the Container App doesn't
exist yet. The principal ID isn't known until after creation.
Solutions (pick one):
- •Use the two-step pattern above (create CA first, then role assignment with
dependsOn) - •Use ACR admin credentials instead of managed identity
- •Use a user-assigned managed identity (create identity first, assign role, then reference in CA)
3. Stuck Container App Provisioning
If a Container App gets stuck in InProgress state after a failed image pull:
- •Wait up to 5 minutes — Azure will eventually timeout the failed revision
- •If still stuck:
az containerapp delete -n <name> -g <rg> --yesthen re-provision - •Do NOT keep retrying
azd deployon a stuck Container App — it will queue behind the failed operation
4. main.parameters.json Location
main.parameters.json MUST be in the infra/ directory (not project root).
The standard azd convention is infra/main.parameters.json.
Bicep Outputs for azd Deploy
azd needs certain outputs from your Bicep to deploy services. Add these to main.bicep:
// Required for azd deploy to find the Container App output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerAppsStack.outputs.registryLoginServer output API_BASE_URL string = api.outputs.uri