writing-bicep-templates
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