AZ-104Chapter 110 of 168Objective 3.4

Bicep and ARM Template Advanced Patterns

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.

25 min read
Intermediate
Updated May 31, 2026

Blueprint and Foreman for Azure Resources

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.

How It Actually Works

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:

1.

Validation: The template is checked for syntax errors, resource provider availability, and correct property values. This phase does not create any resources.

2.

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).

3.

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.json

Deployment 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.bicep

Error 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.

Walk-Through

1

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.

2

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.

3

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.

4

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.

5

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).

What This Looks Like on the Job

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

1.

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.

2.

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.

3.

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.

4.

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).

How AZ-104 Actually Tests This

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

1.

"ARM templates support loops using `for`" — Wrong. ARM templates use copy loops. Bicep uses for. The exam may ask about ARM specifically.

2.

"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.

3.

"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.

4.

"`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.

Key Takeaways

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.

Easy to Mix Up

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.

Watch Out for These

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.

Do You Actually Know This?

Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.

Frequently Asked Questions

What is the difference between Incremental and Complete deployment modes in ARM templates?

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.

Can I deploy a Bicep file directly to Azure without converting to ARM?

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`.

How do I pass parameters to an ARM template or Bicep file?

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.

What is a linked template and how is it different from a nested template?

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.

How do I create multiple resources of the same type in ARM or Bicep?

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.

What is a template spec and when should I use it?

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.

How do I handle secrets in ARM templates?

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()`.

Terms Worth Knowing

Ready to put this to the test?

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?