This chapter covers advanced patterns for Infrastructure as Code (IaC) on Azure: Bicep and ARM templates. For the AZ-104 exam (Objective 3.4), approximately 15-20% of questions touch IaC concepts, with 5-10% specifically on templates. You must understand the declarative syntax, deployment modes, parameter files, module references, and how to handle dependencies and conditional deployments. We will dive deep into the mechanics of Bicep transpilation, ARM template evaluation, and the differences between incremental and complete deployments. By the end, you will be able to read and modify templates, understand deployment errors, and choose the right tool for the job.
Jump to a section
Think of deploying Azure resources as constructing a house. ARM templates are like a detailed architectural blueprint — a massive, single document that specifies every nail, wire, and pipe. It's comprehensive but unwieldy; a small change (like moving a wall outlet) requires redrawing the entire blueprint. Bicep is like a modern foreman who uses a set of modular, reusable prefab plans. Each prefab plan (Bicep module) describes a room or a subsystem (e.g., a networking module, a storage module). The foreman assembles these plans, passes parameters (like room dimensions), and the final blueprint (ARM template) is generated automatically. Bicep's syntax is cleaner and more readable — like using a high-level language instead of assembly. The key mechanistic difference: Bicep is a domain-specific language (DSL) that transpiles to ARM templates. When you run 'bicep build', it produces an ARM template JSON. The foreman doesn't just 'organize' the blueprint; he re-expresses it in a more efficient language. This allows for loops, conditions, and modular composition that are cumbersome in raw JSON. For the AZ-104 exam, you don't need to write complex Bicep, but you must understand how Bicep files are compiled, how they relate to ARM templates, and when to use each.
What Are ARM Templates and Bicep?
Azure Resource Manager (ARM) templates are JSON files that define the infrastructure and configuration for your Azure solution declaratively. They specify resources, their properties, and dependencies. Bicep is a domain-specific language (DSL) that provides a more concise, readable syntax and compiles to ARM templates. The exam expects you to know both, but you'll typically work with Bicep in practice.
How ARM Templates Work Internally
When you deploy an ARM template, Azure Resource Manager evaluates the template and makes REST API calls to create or update resources. The deployment goes through these phases:
Validation: The template is checked for syntax errors, resource provider availability, and correct property values. This phase does not create any resources.
Processing: ARM resolves functions, evaluates expressions, and determines dependencies. It builds a dependency graph using the dependsOn element and implicit dependencies (e.g., a subnet depends on a virtual network).
Deployment: Resources are created or updated in parallel where possible, respecting dependencies. The deployment mode (Incremental or Complete) determines whether existing resources are modified or deleted.
Key Components of ARM Templates
An ARM template has the following structure:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"functions": [],
"resources": [],
"outputs": {}
}parameters: Input values supplied during deployment. They can have default values and constraints (allowed values, min/max length).
variables: Values that are computed within the template, often derived from parameters.
functions: User-defined functions to simplify expressions.
resources: The actual Azure resources to deploy. Each resource has a type, apiVersion, name, location, properties, and optionally dependsOn and condition.
outputs: Values returned after deployment, such as the resource ID or connection string.
Deployment Modes: Incremental vs Complete
Incremental (default): ARM only adds or modifies resources defined in the template. Existing resources not in the template are left unchanged.
Complete: ARM ensures the resource group matches the template exactly. Resources not in the template are deleted. This is dangerous for production — use with care. The exam tests this distinction.
Bicep Basics
Bicep reduces boilerplate. For example, an ARM template resource like:
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-09-01",
"name": "[parameters('storageName')]",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2"
}Becomes in Bicep:
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = {
name: storageName
location: resourceGroup().location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}Bicep uses symbolic names (e.g., storageAccount) that are not the Azure resource name. The actual name is set via the name property.
Advanced Patterns: Modules
Bicep modules allow you to reuse template snippets. A module is a separate Bicep file that can be deployed as a nested deployment. For example:
module networkingModule './networking.bicep' = {
name: 'networkingDeployment'
params: {
vnetName: vnetName
addressPrefix: addressPrefix
}
}The module file networking.bicep contains its own parameters and resources. The parent template passes parameters. Modules are compiled into a single ARM template with nested deployments.
Advanced Patterns: Loops and Conditions
Loops: Use for to create multiple resources. In ARM templates, you use copy loops. In Bicep:
resource storageAccounts 'Microsoft.Storage/storageAccounts@2021-09-01' = [for i in range(1, 3): {
name: 'storage${i}'
location: resourceGroup().location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}]Conditions: Use if to conditionally deploy a resource.
resource publicIP 'Microsoft.Network/publicIPAddresses@2020-06-01' = if (deployPublicIP) {
name: publicIPName
location: resourceGroup().location
properties: {
publicIPAllocationMethod: 'Dynamic'
}
}Dependency Management
ARM templates automatically determine dependencies based on resource references. For example, if a subnet references a virtual network via properties.addressPrefix, ARM knows the VNet must exist first. However, sometimes you need explicit dependsOn for non-obvious dependencies. In Bicep, you can use the dependsOn property or rely on symbolic references.
Functions and Expressions
ARM template functions are used to retrieve values dynamically. Common functions:
- resourceGroup().location – location of the resource group.
- subscription().subscriptionId – current subscription ID.
- concat(), format() – string manipulation.
- uniqueString() – creates a unique hash based on inputs.
- parameters() – retrieves parameter values.
In Bicep, these functions are available directly (e.g., resourceGroup().location).
Parameter Files
Parameter files separate environment-specific values from the template. They are JSON files with the same structure as the parameters section. You can reference them during deployment:
az deployment group create --resource-group myRG --template-file main.bicep --parameters main.parameters.jsonDeployment Scripts
For complex tasks that cannot be expressed declaratively, you can use deployment scripts (PowerShell or CLI) within ARM templates. These run in a container and can perform custom actions. The exam may ask about this as a way to extend templates.
Bicep Transpilation
Bicep is transpiled to ARM templates using the Bicep compiler. The command bicep build main.bicep produces main.json. You can also use az bicep build. The generated ARM template is fully functional. You can even decompile ARM templates to Bicep using bicep decompile main.json.
Best Practices
Use parameters for values that change between environments.
Use variables for computed values.
Use modules to avoid repetition.
Use outputs to pass information between modules.
Apply tags to all resources for cost tracking.
Use what-if operation to preview changes before deployment.
What-If Operation
The what-if operation shows what resources will be created, modified, or deleted without making actual changes. It's crucial for validating templates, especially with Complete mode. Run:
az deployment group what-if --resource-group myRG --template-file main.bicepError Handling
Common errors: - Missing dependencies: Resource required by another is not defined or not deployed yet. - Invalid properties: Wrong API version or property name. - Quota limits: Exceeding subscription quotas. - Name conflicts: Resource names must be globally unique (e.g., storage accounts).
Interaction with Azure DevOps
ARM templates and Bicep can be integrated into CI/CD pipelines. The exam may ask about using AzureResourceManagerTemplateDeployment task in Azure Pipelines. The task can deploy templates with parameters and override parameters.
Summary of Exam-Relevant Details
Default deployment mode: Incremental.
Bicep file extension: .bicep.
Bicep modules: Use module keyword.
Loops: for in Bicep, copy in ARM.
Conditions: if in Bicep, condition property in ARM.
Parameter files: .parameters.json.
What-if: az deployment group what-if.
Decompile: bicep decompile.
Functions: resourceGroup(), subscription(), uniqueString(), etc.
Create a Bicep template file
Start by creating a file with `.bicep` extension. Define parameters for configurable inputs (e.g., `param location string = resourceGroup().location`). Declare resources using symbolic names and the resource type with API version. Use expressions and functions to set properties. For example, a storage account resource: `resource stg 'Microsoft.Storage/storageAccounts@2021-09-01' = { ... }`. The Bicep compiler will validate syntax and types.
Build the Bicep file to ARM
Run `bicep build main.bicep` or `az bicep build --file main.bicep`. The compiler produces an ARM template JSON file (e.g., `main.json`). This step resolves all symbolic references, loops, and conditions into the JSON structure. The output is a standard ARM template that can be deployed directly. You can inspect the generated JSON to understand how Bicep constructs map to ARM.
Create a parameter file (optional)
Create a JSON file (e.g., `main.parameters.json`) that contains parameter values for the deployment. The structure matches the parameters section of the ARM template. For example: `{"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": {"storageName": {"value": "mystorage123"}}}`. Parameter files allow environment-specific values without modifying the template.
Run a what-if deployment
Execute `az deployment group what-if --resource-group myRG --template-file main.bicep --parameters main.parameters.json`. The what-if operation simulates the deployment and outputs a list of changes: resources to create, modify, or delete (in Complete mode). It uses the ARM API to evaluate the template against the current state. Review the output carefully to avoid unintended deletions or modifications.
Deploy the template
Run `az deployment group create --resource-group myRG --template-file main.bicep --parameters main.parameters.json`. This starts the actual deployment. ARM validates the template, processes dependencies, and creates/updates resources. Monitor the deployment using `az deployment group show`. If errors occur, check the error details (e.g., missing resource provider registration, invalid properties).
Enterprise Scenario 1: Multi-Environment Deployment with Parameter Files
A large enterprise manages development, staging, and production environments. They use a single Bicep template for a web application (App Service, SQL Database, Redis Cache). Each environment has different values: SKUs, instance counts, and connection strings. They maintain separate parameter files (dev.parameters.json, staging.parameters.json, prod.parameters.json). In CI/CD pipelines (Azure DevOps), they run bicep build and then deploy with the appropriate parameter file. The key challenge is managing secrets (like SQL passwords) — they use Azure Key Vault references in the parameter file: {"reference": {"keyVault": {"id": "/subscriptions/.../resourceGroups/.../providers/Microsoft.KeyVault/vaults/myVault"}, "secretName": "sqlPassword"}}. This avoids storing secrets in source control.
Enterprise Scenario 2: Modular Infrastructure with Nested Deployments
A cloud team builds a landing zone for new projects. They create reusable Bicep modules: networking.bicep (VNet, subnets, NSGs), security.bicep (Key Vault, RBAC), compute.bicep (VM scale sets). Each module is versioned and stored in a container registry (Azure Container Registry for modules). The landing zone template references these modules from the registry: module networking 'br:myregistry.azurecr.io/bicep/modules/networking:v1' = {...}. This promotes consistency and reduces duplication. The deployment order is critical: networking must be deployed before compute. They use dependsOn in the parent template to ensure correct ordering.
Common Pitfalls in Production
Complete mode deletion: An engineer accidentally uses --mode Complete on a resource group containing production resources. The template only defines a single VM, so all other resources (databases, storage accounts) are deleted. Mitigation: Always use what-if before Complete mode, and restrict Complete mode permissions.
Parameter file mismatch: The parameter file references a Key Vault secret that doesn't exist in the target subscription. The deployment fails with a secret not found error. Mitigation: Validate parameter files in CI/CD.
Resource name collisions: Storage account names must be globally unique. When deploying multiple environments from the same template, they use uniqueString(resourceGroup().id) to generate unique names. Without this, deployments fail with name already taken.
Circular dependencies: Two resources reference each other (e.g., a VM with a NIC that references the VM). ARM cannot resolve the dependency graph. Mitigation: Separate the creation into two templates or use a different design (e.g., create NIC with VM reference after VM creation).
What AZ-104 Tests on This Topic
Objective 3.4: "Create and manage Azure Resource Manager (ARM) templates and Bicep files." The exam focuses on:
Understanding the structure of ARM templates and Bicep files.
Differentiating between incremental and complete deployment modes.
Using parameters and parameter files.
Implementing dependencies (explicit vs implicit).
Using functions like resourceGroup(), uniqueString(), concat().
Handling conditional deployments and loops.
Deploying templates via Azure CLI, PowerShell, or portal.
Interpreting deployment errors.
Common Wrong Answers and Why
"ARM templates support loops using `for`" — Wrong. ARM templates use copy loops. Bicep uses for. The exam may ask about ARM specifically.
"Complete mode only affects resources defined in the template" — Wrong. Complete mode deletes resources NOT in the template. This is a classic trap. Candidates think it only modifies what's in the template, but it ensures exact match.
"Bicep files are deployed directly to Azure" — Wrong. Bicep must be transpiled to ARM templates first. However, the Azure CLI az deployment group create accepts .bicep files directly and transpiles them automatically. The exam tests whether you know that Bicep is a DSL that compiles to ARM.
"`dependsOn` is always required for dependencies" — Wrong. ARM automatically detects implicit dependencies through resource references (e.g., a subnet referencing a VNet's ID). Only use explicit dependsOn when dependencies are not obvious.
Specific Numbers and Terms
Default deployment mode: Incremental.
API version format: YYYY-MM-DD or YYYY-MM-DD-preview.
contentVersion in template: 1.0.0.0 (but can be any string).
Parameter file schema: https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#.
Bicep file extension: .bicep.
Bicep module registry: Azure Container Registry (ACR).
What-if command: az deployment group what-if.
Decompile command: bicep decompile.
Edge Cases and Exceptions
Linked templates vs nested templates: Linked templates are separate files stored externally (e.g., in a storage account). Nested templates are defined inline within the parent template. The exam may ask which to use for reusability.
Template specs: A newer feature that packages ARM templates as resources (type Microsoft.Resources/templateSpecs). You can deploy a template spec by its version. The exam may include this as an alternative to storage accounts for linked templates.
Bicep modules in private registries: Requires authentication. The exam may ask about using br: prefix for module references.
How to Eliminate Wrong Answers
If the question mentions "delete resources not in template", the answer is Complete mode.
If the question mentions "reusable across environments", think parameter files.
If the question mentions "conditionally deploy", look for condition in ARM or if in Bicep.
If the question mentions "deploy without affecting existing resources", the answer is Incremental mode.
Always check the tool: ARM template or Bicep? The syntax differs.
ARM templates are JSON files that define Azure resources declaratively.
Bicep is a DSL that compiles to ARM templates; use `.bicep` extension.
Default deployment mode is Incremental (adds/modifies resources only).
Complete mode deletes resources not in the template — use with caution.
Parameter files separate environment-specific values from templates.
Dependencies can be implicit (via resource references) or explicit (`dependsOn`).
Loops in ARM use `copy`; in Bicep use `for`.
Conditions in ARM use `condition`; in Bicep use `if`.
What-if operation previews changes before actual deployment.
Bicep modules enable reusable infrastructure components.
Always use `uniqueString()` for globally unique resource names.
Deployment scripts can execute custom PowerShell/CLI within templates.
These come up on the exam all the time. Here's how to tell them apart.
ARM Templates
JSON format — verbose and hard to read.
Uses `copy` loops for iteration.
Uses `condition` property for conditional deployment.
No symbolic references — must use `resourceId()` or `reference()` functions.
Mature and widely used; all Azure tools support it.
Bicep
Declarative DSL — concise and readable.
Uses `for` loops for iteration.
Uses `if` keyword for conditional deployment.
Symbolic references — resources can be referenced by name.
Newer but rapidly adopted; compiles to ARM templates.
Mistake
Bicep is a new Azure resource type that can be deployed directly.
Correct
Bicep is a domain-specific language that compiles to ARM templates. While Azure CLI can deploy .bicep files directly (by transpiling them), the underlying mechanism is still ARM templates. The exam expects you to know that Bicep is not a native resource type.
Mistake
Incremental deployment mode deletes resources not in the template.
Correct
Incremental mode only adds or modifies resources defined in the template. It never deletes existing resources. Complete mode deletes resources not in the template. This is a frequent exam trap.
Mistake
You must always specify `dependsOn` for every resource dependency.
Correct
ARM automatically detects many dependencies through resource references (e.g., a subnet referencing a VNet's `id`). Explicit `dependsOn` is only needed when the dependency is not implicit, such as when a resource uses a parameter that references another resource's property.
Mistake
ARM templates cannot use loops or conditions.
Correct
ARM templates support loops via `copy` and conditions via the `condition` property. Bicep supports loops via `for` and conditions via `if`. Both are fully supported.
Mistake
Parameter files are required for every deployment.
Correct
Parameter files are optional. You can pass parameters inline using `--parameters` in Azure CLI or PowerShell. However, parameter files are recommended for complex deployments to separate configuration from template.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
Incremental mode (default) only adds or modifies resources defined in the template; existing resources not in the template remain unchanged. Complete mode ensures the resource group matches the template exactly — resources not in the template are deleted. Use Complete mode only for resource groups that are entirely managed by the template, and always run a what-if operation first to avoid accidental deletions.
Yes, you can use Azure CLI (`az deployment group create --template-file main.bicep`) or PowerShell, which automatically transpile the Bicep file to an ARM template internally. However, behind the scenes, the Bicep file is compiled to JSON before deployment. You can also manually build the ARM template using `bicep build`.
You can pass parameters inline via the `--parameters` flag (e.g., `--parameters storageName=myStorage`), or use a parameter file (e.g., `--parameters @params.json`). Parameter files are JSON files that contain key-value pairs. For Bicep, you can also use Bicep parameters syntax directly.
A linked template is a separate ARM template file stored externally (e.g., in a storage account or GitHub) and referenced via a URL. A nested template is defined inline within the parent template using the `Microsoft.Resources/deployments` resource type. Linked templates are better for reusability and versioning; nested templates are simpler but harder to manage.
In ARM templates, use the `copy` loop construct within a resource definition. In Bicep, use a `for` loop on the resource declaration. For example, to create three storage accounts: in Bicep, `resource stg '...' = [for i in range(1,4): { ... }]`. The exam may test the syntax differences.
A template spec is a resource type (`Microsoft.Resources/templateSpecs`) that packages an ARM template as an Azure resource. You can deploy a template spec by referencing its version. It provides versioning, RBAC, and easier sharing compared to storing templates in storage accounts. Use template specs for organizational governance.
Never hardcode secrets in templates. Use parameter files with Key Vault references: `{"reference": {"keyVault": {"id": "..."}, "secretName": "..."}}`. During deployment, ARM retrieves the secret from Key Vault. Alternatively, use Azure Key Vault as a linked resource and reference it with `reference()`.
You've just covered Bicep and ARM Template Advanced Patterns — now see how well it sticks with free AZ-104 practice questions. Full explanations included, no account needed.
Done with this chapter?