Bicep Coding Standards
Goal: Create consistent, secure Azure infrastructure
Naming Convention
Use resourceToken from uniqueString() :
var token = toLower(uniqueString(subscription().id, environmentName, location)) name: '${abbrs.appContainerApps}web-${token}' // ca-web-abc123
Exception: ACR requires alphanumeric only: cr${resourceToken}
Parameters
Always add @description() and use @allowed() for constrained values:
@description('Environment (dev, prod)') param environmentName string
@description('Azure region') @allowed(['eastus2', 'westus2']) param location string = 'eastus2'
Outputs
Expose key identifiers for azd and other modules:
output containerAppName string = containerApp.name output webEndpoint string = 'https://${containerApp.properties.configuration.ingress.fqdn}' output identityPrincipalId string = containerApp.identity.principalId
Managed Identity
Use a user-assigned MI for ACR pull and OBO (avoids circular dependencies). Create it in the infrastructure module so its principalId is available before the Container App:
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2024-11-30' = { name: '${abbrs.managedIdentityUserAssignedIdentities}web-${resourceToken}' location: location properties: { isolationScope: 'Regional' } } output managedIdentityPrincipalId string = managedIdentity.properties.principalId
Attach to Container App with identity: { type: 'UserAssigned', userAssignedIdentities: { '${miId}': {} } } . Use MI for ACR pull via registries: [{ server: acr.loginServer, identity: miId }] .
RBAC Assignments
Use guid() for names + specify principalType :
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { name: guid(resource.id, principalId, roleId) properties: { roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleId) principalId: principalId principalType: 'ServicePrincipal' } }
Container Apps
Key settings: System identity + scale-to-zero + HTTPS only:
resource containerApp 'Microsoft.App/containerApps@2023-05-01' = { identity: { type: 'UserAssigned', userAssignedIdentities: { '${userAssignedIdentityId}': {} } } properties: { configuration: { ingress: { external: true targetPort: 8080 allowInsecure: false } } template: { scale: { minReplicas: 0, maxReplicas: 3 } } } }
ACR Pull Pattern
Use user-assigned MI for ACR pull (no admin credentials or secrets):
registries: [{ server: containerRegistry.properties.loginServer identity: userAssignedIdentityId // MI with AcrPull role }]
Validation
az bicep build --file main.bicep az deployment group what-if --template-file main.bicep
Project-Specific: Module Hierarchy
main.bicep (subscription scope) ├─ Resource group ├─ main-infrastructure.bicep (ACR + Container Apps Env + Log Analytics + User-Assigned MI) ├─ entra-app.bicep (SPA app + conditional OBO backend app with FIC + admin consent) ├─ main-app.bicep (Container App with MI-based ACR pull) └─ RBAC (Cognitive Services User role via postprovision CLI)
Project-Specific: Container App Configuration
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = { identity: { type: 'UserAssigned' userAssignedIdentities: { '${userAssignedIdentityId}': {} } } properties: { managedEnvironmentId: containerAppsEnvironmentId configuration: { ingress: { external: true targetPort: 8080 allowInsecure: false } registries: [{ server: containerRegistry.properties.loginServer identity: userAssignedIdentityId // MI-based pull, no secrets }] } template: { containers: [{ name: 'web' image: containerImage env: containerEnv // Base env + conditional OBO env resources: { cpu: json('0.5'), memory: '1Gi' } }] scale: { minReplicas: 0, maxReplicas: 3 } } } }
output fqdn string = containerApp.properties.configuration.ingress.fqdn output identityPrincipalId string = containerApp.identity.principalId
Related Skills
-
deploying-to-azure - Deployment commands and hook workflow
-
writing-csharp-code - Backend configuration for Container Apps
-
troubleshooting-authentication - RBAC and managed identity debugging