Bicep Patterns
Bicep default-path patterns for AppOnboard scaffold. Used as the primary IaC format. For the alternative Terraform path (existing .tf files or user override), scaffold uses mcp_azure_mcp_azureterraformbestpractices output patterns.
Source: Adapted from Azure Bicep best practices. See Bicep best practices for updates.
File Structure
⛔ Always use
targetScope = 'subscription'. Subscription-scope Bicep creates the resource group in IaC with all 5 AppOnboard tags (includingcreated-at). Resource-group scope requiresaz group createvia CLI, which consistently missescreated-atbecause CLI-created resource groups don't receive IaC-managed tags. There are zero benefits to resource-group scope for AppOnboard.
infra/
├── main.bicep # Entry point (subscription scope)
├── main.parameters.json # ARM JSON parameter values (NOT .bicepparam)
└── modules/
├── container-app.bicep
├── app-service.bicep
├── sql-database.bicep
├── key-vault.bicep
├── log-analytics.bicep
└── ...Each service gets its own module. main.bicep orchestrates resource group creation + module calls.
main.bicep Skeleton
targetScope = 'subscription'
@minLength(1)
@maxLength(64)
param environmentName string
@minLength(1)
param location string
param sessionId string
param deployedBy string // resolved via: az ad signed-in-user show --query displayName -o tsv
// ⛔ createdAt: passed in parameters.json, NOT utcNow() default (crashes Portal blade)
param createdAt string
var tags = {
'app-onboard-skill': 'true'
'app-onboard-session-id': sessionId
'created-at': createdAt
environment: environmentName
'deployed-by': deployedBy
}
resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
name: 'rg-${environmentName}'
location: location
tags: tags
}
// ⛔ scope: rg (symbolic) — creates implicit dependsOn. Do NOT use resourceGroup(name) — it races against RG creation.
module resources './modules/resources.bicep' = {
name: 'resources'
scope: rg
params: {
location: location
environmentName: environmentName
tags: tags
}
}main.parameters.json
⛔ ARM JSON only. Do NOT use
.bicepparamsyntax (using,param,readEnvironmentVariable()). AppOnboard deploys viaaz deployment sub create(subscription-scope default) — notazd— and.bicepparamrequires azd or newer tooling. If the user lacks subscription-level permissions, the deploy phase falls back toaz deployment group createautomatically.
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environmentName": { "value": "{project}-{env}" },
"location": { "value": "{region}" },
"sessionId": { "value": "{context.json.sessionId}" },
"deployedBy": { "value": "{context.json.azure.userDisplayName}" }
}
}Naming Convention (Bicep)
The prepare phase generates a logical resource prefix in prepare-plan.json.naming.resourcePrefix (e.g., myapp-dev). Scaffold MUST add a globally unique suffix using Bicep's uniqueString() function to prevent cross-deployment name collisions on globally unique Azure resources (App Service, Key Vault, Storage Account, ACR).
// main.bicep — derive unique suffix from resource group
var nameSuffix = uniqueString(resourceGroup().id)
// Pass unique names to modules
param kvName string = 'kv-${resourcePrefix}-${take(nameSuffix, 4)}'
param appName string = 'app-${resourcePrefix}-${take(nameSuffix, 4)}'
param storName string = 'st${replace(resourcePrefix, '-', '')}${take(nameSuffix, 4)}'
param acrName string = 'cr${replace(resourcePrefix, '-', '')}${take(nameSuffix, 4)}'⛔ Do NOT use
uniqueString()for secrets — it is deterministic and predictable. See bicep-patterns-security.md § Secrets for correct secret patterns.
If prepare-plan.json.naming.resources[] provides pre-computed names with suffixes, prefer those — but ALWAYS ensure globally unique resources include a uniqueString() or equivalent hash in main.bicep as a safety net.
Log Analytics Module Output
⛔ Output the resource ID (
.id), NOT.properties.customerId. Container Apps Environment requiresworkspaceResourceId(the full ARM resource ID)..properties.customerIdis the GUID used for queries — passing it asworkspaceResourceIdcauses an ARM deploy failure (BadRequest). Separate the two outputs:output workspaceId string = logAnalyticsWorkspace.id // ARM resource ID — for CAE, App Insights output workspaceCustomerId string = logAnalyticsWorkspace.properties.customerId // GUID — for Log Analytics queries only
Compute-Target Patterns
Read the file(s) matching the service mapping — load only what's needed:
- App Service / Functions: bicep-app-service.md — module template, SCM/FTP auth, native module deploy strategy
- Container Apps: bicep-container-apps.md — two-phase ACR wiring, ingress, secretRef, image parameter, multi-container DNS
- Static Web Apps: bicep-swa.md — module template, detached deploy rule
Load multiple only if the plan includes multiple compute targets.
⛔ F1/D1 SKU: do NOT generate a Dockerfile. If
prepare-plan.jsonspecifies F1 or D1 (free/shared tier), use the platform's built-in runtime stack (e.g.,NODE|24-ltsfor Node.js,PYTHON|3.14for Python). Dockerfiles are for B1+ or Container Apps only.
⛔ Native module deploy strategy. If
prepare-plan.json.deployStrategyexists, read bicep-app-service.md § Native Module Deploy Strategy and apply the startup command + app settings.deployStrategy.startupCommand→appCommandLine,deployStrategy.requiredAppSettings→appSettings[]. When nodeployStrategyexists, do NOT setappCommandLine.
Service Tagging
⛔ You MUST read iac-generation-rules.md § Session Tags. All resources MUST include the 5 AppOnboard session tags. Pass
tagsobject frommain.bicepinto every module.
API Version Policy
Use the latest stable API version for each resource type. Never use preview APIs unless required for a feature with no GA alternative. Validate via bicep build — stale API versions produce warnings.
Key Vault Reference Syntax
Syntax differs by service — never mix. See bicep-container-apps.md for Container Apps secretRef and bicep-patterns-security.md for App Service @Microsoft.KeyVault(). For Terraform, see terraform-patterns.md.