This chapter covers Azure Resource Manager (ARM) templates and Bicep, the two primary Infrastructure as Code (IaC) tools for deploying and managing Azure resources declaratively. For the AZ-104 exam, understanding ARM templates and Bicep is critical because approximately 15-20% of questions touch on deployment automation, resource organization, and configuration management. You will be tested on template structure, parameters, modules, dependencies, deployment modes, and Bicep syntax. Mastering these tools enables you to automate repeatable, consistent deployments and is a core skill for any Azure administrator.
Jump to a section
Think of ARM templates as a detailed architectural blueprint for a restaurant kitchen. The blueprint specifies every component: the exact model of stove (VM SKU), the location of each counter (subnet), the plumbing connections (network interfaces), and the power requirements (storage accounts). Every time you build a kitchen from this blueprint, you get exactly the same layout, appliances, and wiring—no variation, no forgotten parts. This is declarative: you declare what the final kitchen should look like; the contractor (Azure Resource Manager) figures out the steps to build it. Bicep is like having a master chef who speaks a concise, readable shorthand for the same blueprint. Instead of writing pages of JSON, the chef says, 'I need a 6-burner gas range with double oven, a 40-inch prep table, and a three-compartment sink,' and then a translator converts that into the full blueprint. Bicep files are transpiled into ARM JSON templates before deployment. The key is that both produce the same end result—identical kitchens—but Bicep is easier to write, read, and maintain. Without templates, you'd be manually placing each appliance and running wires every time, which is slow and error-prone (like using the Azure portal click-by-click). With templates, you achieve infrastructure as code (IaC): repeatable, version-controlled, and auditable deployments.
What Are ARM Templates and Bicep?
Azure Resource Manager (ARM) templates are JSON files that define the infrastructure and configuration of Azure resources. They are declarative: you specify what you want the end state to be, and ARM orchestrates the creation, update, or deletion of resources to achieve that state. Bicep is a domain-specific language (DSL) that simplifies writing ARM templates. Bicep files are transpiled into ARM JSON templates before deployment. Both are used for Infrastructure as Code (IaC), enabling repeatable, version-controlled deployments.
Why Use ARM Templates and Bicep?
Repeatability: Deploy the same environment multiple times with identical configuration.
Idempotency: Deploying the same template multiple times results in the same state.
Version Control: Templates can be stored in Git and tracked.
Declarative Syntax: Focus on what resources are needed, not the order of operations.
Modularity: Break complex deployments into smaller, reusable modules.
Validation: Templates can be validated before deployment.
Role-Based Access Control (RBAC): Deployments can be scoped to resource groups or subscriptions.
ARM Template Structure
An ARM template has the following top-level elements:
$schema: Location of the JSON schema file that describes the template structure.
contentVersion: Version of the template (e.g., "1.0.0.0").
parameters: Values that are provided at deployment time to customize the deployment.
variables: Values that are used within the template to simplify expressions.
functions: User-defined functions for reusable expressions.
resources: The actual Azure resources to deploy.
outputs: Values returned after deployment (e.g., resource IDs or connection strings).
Example minimal ARM template:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [],
"outputs": {}
}Bicep Structure
Bicep has a simpler, more concise syntax. The equivalent of the above ARM template in Bicep is:
// Just a comment, no required boilerplateBicep files do not require a schema declaration. Resources are declared using a readable syntax. For example, a storage account in Bicep:
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = {
name: 'mystorageaccount'
location: resourceGroup().location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}Deployment Modes
When deploying templates, you can use two modes:
Incremental (default): Adds resources to the resource group. If a resource exists and its properties are different, it is updated. Existing resources not in the template are left unchanged.
Complete: Deploys the resources defined in the template and deletes any existing resources in the resource group that are not in the template. Use with caution—can cause data loss. Not supported for subscription-level deployments.
Resource Dependencies
ARM determines the order of resource creation by analyzing dependencies. There are two ways to define dependencies:
Implicit: ARM can infer dependencies from expressions. For example, if a VM references a network interface by ID, ARM knows the NIC must exist first.
Explicit: Use the dependsOn element in ARM templates or the dependsOn property in Bicep to force a dependency. This is needed when the dependency is not obvious from resource properties.
Example in ARM:
{
"type": "Microsoft.Network/networkInterfaces",
"name": "myNic",
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks', 'myVnet')]"
]
}In Bicep:
resource myNic 'Microsoft.Network/networkInterfaces@2022-01-01' = {
name: 'myNic'
dependsOn: [
myVnet
]
}Parameters and Variables
Parameters allow you to pass values into templates at deployment time. They can have default values, allowed values, and constraints (min/max length, allowed values, etc.). Variables are computed within the template and are not input by the user.
Example parameter in ARM:
"parameters": {
"storageName": {
"type": "string",
"minLength": 3,
"maxLength": 24,
"metadata": {
"description": "Name of the storage account"
}
}
}In Bicep:
@minLength(3)
@maxLength(24)
@description('Name of the storage account')
param storageName stringFunctions
ARM templates support template functions that can be used in expressions. Common functions include:
resourceGroup(): Returns information about the current resource group.
subscription(): Returns information about the current subscription.
concat(): Concatenates strings.
uniqueString(): Creates a deterministic hash string based on the provided parameters.
resourceId(): Returns the unique identifier of a resource.
In Bicep, you can call ARM functions using the az namespace or built-in functions like resourceGroup().location.
Modules
Bicep modules allow you to encapsulate a set of resources into a reusable file. A module is a Bicep file that can be referenced from another Bicep file. Modules can have their own parameters and outputs.
Example module reference:
module myModule './modules/storageAccount.bicep' = {
name: 'storageDeploy'
params: {
storageName: 'myuniquestorage'
location: resourceGroup().location
}
}Deployment Commands
Deploy using Azure CLI:
az deployment group create --resource-group myRG --template-file template.json --parameters parameters.jsonFor Bicep:
az deployment group create --resource-group myRG --template-file main.bicep --parameters parameters.jsonOr use PowerShell:
New-AzResourceGroupDeployment -ResourceGroupName myRG -TemplateFile template.json -TemplateParameterFile parameters.jsonWhat Happens During Deployment?
Validation: ARM validates the template syntax and checks that resource types and API versions are valid.
Parameter Resolution: Parameters are resolved from provided values or defaults.
Dependency Graph: ARM builds a dependency graph and determines the order of operations.
Resource Creation/Update: ARM calls the appropriate resource providers to create or update resources in the correct order.
Output: After deployment, outputs are returned.
Best Practices
Use parameters for values that change between environments.
Use variables for values that are computed within the template.
Use modules to break down large templates.
Store templates in source control.
Use deployment stacks (preview) for managing multiple resources as a unit.
Validate templates with Test-AzResourceGroupDeployment or az deployment group validate.
Use what-if to preview changes before deployment.
What-if Operation
The what-if operation shows what changes will happen without actually making them. It is useful for review before a complete mode deployment.
az deployment group what-if --resource-group myRG --template-file template.jsonCommon Exam Scenarios
Nested templates: You can link to other templates using templateLink.
Conditional deployment: Use condition property to deploy a resource only if a condition is true.
Copy loops: Deploy multiple instances of a resource using copy.
Outputs: Use outputs to get connection strings or resource IDs after deployment.
Interaction with Other Services
Azure DevOps: Use ARM templates in pipelines for CI/CD.
Azure Policy: Templates can be used to deploy resources that comply with policies.
Azure Blueprints: Blueprints can include ARM templates as artifacts.
Key Vault: Reference secrets from Key Vault in parameters using reference() function.
Limits and Constraints
Maximum template size: 4 MB.
Maximum parameters: 256.
Maximum variables: 256.
Maximum resources: 800 per deployment.
Maximum outputs: 64.
Deployment name length: 64 characters.
Resource group name length: 90 characters.
Bicep vs. ARM JSON
Bicep offers several advantages:
Simpler syntax: No need for quotes, brackets, or commas.
Automatic dependency detection: Bicep can infer dependencies from symbolic references.
Modularity: Native support for modules.
Type safety: Bicep validates types at compile time.
Tooling: Better IDE support (VS Code extension).
However, ARM JSON is the underlying language, and Bicep transpiles to it. You can mix both in a deployment by using templateLink to reference a JSON template from a Bicep file.
Exam Tip: Deployment Mode
The exam often tests the difference between incremental and complete modes. Remember:
Incremental: default, only adds/updates resources in the template.
Complete: deletes resources not in the template (only at resource group level).
A common question: 'You want to ensure that only resources defined in the template exist in the resource group after deployment. Which deployment mode should you use?' Answer: Complete.
Exam Tip: DependsOn
If a resource depends on another but the dependency is not implicit, you must use dependsOn. The exam might present a scenario where a VM depends on a NIC, which depends on a VNet. Since the NIC references the VNet, the VNet dependency is implicit, but the VM depends on the NIC explicitly because the VM references the NIC ID.
Exam Tip: Parameters File
You can use a separate parameters file to pass values. The format is:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageName": {
"value": "myuniquestorage"
}
}
}Exam Tip: Bicep Transpilation
Bicep files are transpiled to ARM JSON using the Bicep compiler. You can run bicep build main.bicep to generate the JSON. The exam may ask about the relationship: Bicep is a DSL that compiles to ARM JSON, not a replacement for ARM.
Summary
ARM templates and Bicep are essential for automated, repeatable Azure deployments. Understanding their structure, deployment modes, dependencies, and best practices is crucial for the AZ-104 exam. Focus on parameterization, modularization, and the what-if operation.
Define Infrastructure Requirements
Identify all Azure resources needed for the solution: virtual networks, storage accounts, VMs, NICs, public IPs, etc. Determine their configuration details like SKU, size, location, and naming conventions. This step involves planning the architecture and mapping dependencies between resources. For example, a VM requires a NIC, which requires a VNet and subnet. Document these relationships to ensure the template captures all implicit and explicit dependencies.
Create the Bicep File
Write a Bicep file using the declarative syntax. Start with parameters for values that change per deployment (e.g., VM size, admin username). Define variables for computed values (e.g., storage account name using uniqueString). Declare each resource with its type, API version, and properties. Use symbolic references to link resources; Bicep will automatically infer dependencies from these references. For example, reference a NIC in a VM declaration to create an implicit dependency.
Build the ARM Template
If using Bicep, run `bicep build main.bicep` to transpile the Bicep file into an ARM JSON template. The resulting JSON includes the full schema, parameters, variables, resources, and outputs. Inspect the JSON to verify that dependencies are correctly represented (look for `dependsOn` entries). Alternatively, write the ARM template directly in JSON if preferred. The ARM template is what gets deployed to Azure.
Validate the Template
Use the Azure CLI command `az deployment group validate --resource-group myRG --template-file template.json --parameters parameters.json` to validate the template against Azure Resource Manager. This checks for syntax errors, invalid resource types, and missing required properties. Validation does not create any resources. It returns a list of errors if any. Fix errors and re-validate until successful.
Preview Changes with What-If
Run `az deployment group what-if --resource-group myRG --template-file template.json` to get a preview of the changes that will be made. The output shows resources that will be created, modified, or deleted. This is especially important when using complete deployment mode, as it can show resources that will be deleted. Review the changes and confirm they match expectations.
Deploy the Template
Execute the deployment using `az deployment group create --resource-group myRG --template-file template.json --parameters parameters.json`. For incremental mode (default), resources not in the template are unaffected. For complete mode, use `--mode Complete`. Monitor the deployment progress using `az deployment group show`. After completion, verify resources are created as expected.
Verify Outputs and Clean Up
After deployment, retrieve outputs using `az deployment group show --resource-group myRG --name deploymentName --query properties.outputs`. Outputs might include connection strings or resource IDs. Use these in applications. If the deployment was for testing, delete the resource group to avoid ongoing costs. For production, consider using deployment stacks for lifecycle management.
Scenario 1: Multi-Tier Web Application Deployment
A company deploys a three-tier web application (frontend, backend, database) across multiple environments (dev, test, prod). Using Bicep, they create a main template that calls modules for each tier. Parameters for environment-specific values (e.g., VM sizes, database SKU) are passed via parameters files. The template includes a load balancer, two VMs in an availability set, a SQL database, and a storage account for diagnostics. Dependencies are implicit: the load balancer references the backend pool, which references the NICs. This setup allows the team to deploy a consistent environment in under 10 minutes, reducing manual errors. In production, they use complete deployment mode to ensure no stray resources remain. The what-if operation is run in CI/CD pipelines to review changes before approval.
Scenario 2: Governance and Compliance with Azure Policy
An enterprise must ensure all deployed resources comply with internal policies (e.g., only certain VM sizes, mandatory tags). They embed Azure Policy definitions as part of the deployment using ARM templates. The template includes a policy assignment resource that applies a built-in policy to the resource group. Bicep modules are used to encapsulate policy definitions. During deployment, if a resource violates policy, the deployment fails. This enforces compliance at deployment time rather than relying on audits. The team also uses deployment stacks to manage the lifecycle of the entire solution, including policy assignments.
Scenario 3: Disaster Recovery with Azure Site Recovery
A financial services firm replicates its on-premises infrastructure to Azure for disaster recovery. They use ARM templates to deploy the recovery site resources: virtual networks, storage accounts, and a recovery services vault. The template includes parameters for source environment details and uses conditions to deploy only specific resources based on the recovery plan. The what-if operation is crucial to ensure no accidental deletion of existing recovery resources. The template is version-controlled and deployed via Azure DevOps. In a real disaster, the template is used to quickly spin up the recovery environment, and outputs provide connection strings for failover scripts.
AZ-104 Exam Focus on ARM Templates and Bicep
Objective Code: This topic falls under Domain 3: Compute, Objective 3.4: Deploy and manage Azure compute resources using ARM templates and Bicep. Expect 5-10 questions on this topic.
Common Wrong Answers and Why
Wrong: 'Bicep is a replacement for ARM templates.' Reality: Bicep transpiles to ARM JSON; ARM is the underlying engine. Bicep is a DSL, not a replacement.
Wrong: 'Complete deployment mode is the default.' Reality: Incremental is default. Complete mode deletes resources not in the template.
Wrong: 'Dependencies are always explicit.' Reality: ARM infers dependencies from resource property references; explicit dependsOn is only needed when no implicit reference exists.
Wrong: 'Parameters must be provided at deployment time.' Reality: Parameters can have default values, making them optional.
Specific Numbers and Values to Know
Maximum template size: 4 MB.
Maximum resources per deployment: 800.
Maximum parameters: 256.
Maximum variables: 256.
Maximum outputs: 64.
Deployment name length: 64 characters.
Resource group name length: 90 characters.
API version format: YYYY-MM-DD or YYYY-MM-DD-preview.
$schema URL for resource group deployments: https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#.
Bicep file extension: .bicep.
Bicep transpilation command: bicep build main.bicep.
Deployment command: az deployment group create.
What-if command: az deployment group what-if.
Edge Cases and Exceptions
Subscription-level deployments: Use different schema: https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#. Not supported in Bicep directly; use targetScope = 'subscription'.
Cross-resource group deployments: Use nested deployments with resourceGroup parameter.
Conditional resources: Use condition property; if false, the resource is not created.
Copy loops: Use copy to create multiple instances; in Bicep, use for loop.
Outputs from modules: Module outputs are accessed using moduleName.outputs.outputName.
How to Eliminate Wrong Answers
If a question asks about deployment mode and mentions 'ensure only resources in template exist', choose Complete.
If a question asks about dependencies and the resources reference each other's IDs, the dependency is implicit.
If a question asks about Bicep vs ARM, remember Bicep compiles to ARM.
If a question asks about template size limit, answer 4 MB.
If a question asks about what-if, it is used to preview changes without applying them.
ARM templates are JSON files that declaratively define Azure resources.
Bicep is a DSL that transpiles to ARM JSON; it is not a replacement.
Incremental deployment mode is default; complete mode deletes untracked resources.
Resource dependencies are inferred from property references; use dependsOn only when needed.
Maximum template size is 4 MB; max 800 resources per deployment.
Use what-if operation to preview changes before deployment.
Parameters can have default values and are optional if defaults provided.
Bicep modules allow reusable components with their own parameters and outputs.
Validation checks syntax and resource types without creating resources.
Always use source control for templates and Bicep files.
These come up on the exam all the time. Here's how to tell them apart.
ARM Templates (JSON)
Verbose JSON syntax with brackets, commas, and quotes.
No native module support; must use linked templates.
Dependencies must sometimes be explicitly declared.
Larger file size for complex deployments.
Built-in to Azure; no additional tools required.
Bicep
Concise, readable syntax similar to programming languages.
Native module support for breaking down deployments.
Automatic dependency detection from symbolic references.
Smaller file size; transpiles to JSON.
Requires Bicep CLI or VS Code extension to compile.
Mistake
Bicep is a completely new language that replaces ARM JSON.
Correct
Bicep is a domain-specific language that transpiles to ARM JSON. ARM JSON is still the underlying format that Azure Resource Manager understands. Bicep is an abstraction layer, not a replacement.
Mistake
Complete deployment mode is the default and safest option.
Correct
Incremental mode is the default. Complete mode deletes any resources in the resource group that are not defined in the template, which can cause data loss. It should be used with caution and after using what-if.
Mistake
All resource dependencies must be explicitly declared using dependsOn.
Correct
ARM automatically infers dependencies from resource property references. For example, if a VM's networkProfile references a NIC ID, ARM knows the NIC must be created first. Explicit dependsOn is only needed when there is no such reference.
Mistake
ARM templates cannot be used to deploy resources across multiple subscriptions.
Correct
ARM templates can deploy to multiple resource groups within a subscription using nested deployments, but cross-subscription deployments require separate deployments per subscription. Subscription-level templates can deploy resources across multiple resource groups in the same subscription.
Mistake
Parameters in ARM templates are mandatory and must be provided at deployment time.
Correct
Parameters can have default values, making them optional. If a default value is provided, the parameter does not need to be passed during deployment.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
ARM templates are JSON files that define Azure resources declaratively. Bicep is a domain-specific language that simplifies writing ARM templates by providing a cleaner syntax. Bicep files are transpiled into ARM JSON before deployment. Both achieve the same result, but Bicep is easier to read and maintain. For the exam, know that Bicep is an abstraction over ARM JSON.
Use the command: `az deployment group create --resource-group <RG> --template-file <path> --parameters <params>`. For Bicep, use the same command with a `.bicep` file. The CLI automatically transpiles Bicep to JSON if the Bicep CLI is installed. Example: `az deployment group create --resource-group myRG --template-file main.bicep --parameters storageName=myStorage`.
The what-if operation previews the changes that a deployment would make without actually applying them. It shows which resources will be created, modified, or deleted. Use it before a complete mode deployment to avoid accidental deletions. Command: `az deployment group what-if --resource-group myRG --template-file template.json`.
Yes, by using nested deployments. You can specify a different resource group for each nested deployment using the `resourceGroup` property. However, all resources must be within the same subscription. For subscription-level deployments, use a subscription-level template with `targetScope = 'subscription'` in Bicep.
Use Azure Key Vault to store secrets. In the ARM template, reference the secret using the `reference()` function with the Key Vault ID and secret name. Ensure the deployment identity has access to the Key Vault. Example: `"adminPassword": {"reference": {"keyVault": {"id": "/subscriptions/.../..."},"secretName": "vmPassword"}}`.
The maximum is 800 resources per deployment. If you need more, break the deployment into multiple templates or use nested deployments. Also, the template file size cannot exceed 4 MB.
Use the `copy` element in ARM JSON or a `for` loop in Bicep. For example, in Bicep: `resource storageAccounts 'Microsoft.Storage/storageAccounts@2021-09-01' = [for i in range(1, 5): { name: 'storage${i}' ... }]`. In ARM JSON, use `"copy": {"name": "storageCopy", "count": 5}`.
You've just covered ARM Templates and Bicep — now see how well it sticks with free AZ-104 practice questions. Full explanations included, no account needed.
Done with this chapter?