AZ-204Chapter 86 of 102Objective 1.3

Bicep Templates for Developers

This chapter covers Bicep templates, Microsoft's domain-specific language (DSL) for deploying Azure infrastructure as code. For the AZ-204 exam, Bicep is a key topic under Objective 1.3 (Create and manage compute resources) and appears in roughly 10–15% of questions, often alongside ARM templates and Terraform. You will learn how Bicep simplifies ARM template authoring, its syntax, modules, and deployment workflows, and how to integrate it into CI/CD pipelines. Mastery of Bicep is essential for passing the exam and for efficient Azure resource management in real-world development.

25 min read
Intermediate
Updated May 31, 2026

Bicep: A Blueprint for Azure Infrastructure

Think of deploying Azure infrastructure like building a house. With ARM templates, you write the house plan in a massive JSON file—every beam, nail, and window listed with exact coordinates. It's precise but verbose; a single missing comma can collapse the whole project. Bicep is like switching to a modern architectural blueprint language. Instead of writing "resources": [{"type": "Microsoft.Compute/virtualMachines", ...}], you write resource vm 'Microsoft.Compute/virtualMachines@2023-03-01' = { name: 'myVM' ... }. The key mechanical difference: Bicep is a domain-specific language (DSL) that compiles directly to ARM template JSON. When you run bicep build, the Bicep transpiler parses your .bicep file, resolves all modules and functions, and outputs a standard ARM template JSON file. This JSON is then deployed via az deployment group create --template-file main.json. The Bicep language provides syntactic sugar like symbolic resource names, parameter files with @secure() decorators, and the module keyword for composing deployments from reusable modules. Under the hood, every Bicep construct maps 1:1 to ARM template constructs—there's no magic, just cleaner syntax. For example, a resource declaration in Bicep becomes a resource entry in the JSON's resources array. The output keyword becomes an outputs object. Bicep also handles dependency ordering automatically using symbolic references: if resource A references resource B's property (e.g., b.properties.id), Bicep injects a dependsOn in the JSON. This eliminates manual dependsOn arrays, a major source of errors in ARM templates. In short, Bicep is a developer-friendly abstraction over ARM templates, just as a blueprint language is an abstraction over raw measurements and materials lists.

How It Actually Works

What is Bicep and Why Does It Exist?

Bicep is an open-source, domain-specific language (DSL) developed by Microsoft for deploying Azure resources declaratively. It is a transparent abstraction over Azure Resource Manager (ARM) templates—every Bicep file compiles to a standard ARM JSON template. Bicep was created to address the pain points of writing raw JSON ARM templates: verbose syntax, lack of modularity, manual dependency management, and poor readability. Bicep provides a cleaner, more concise syntax with features like symbolic resource names, automatic dependency detection, module composition, and native support for parameter files. For AZ-204, you must understand that Bicep is not a separate tool; it is a language that produces ARM templates, which are then deployed by ARM.

How Bicep Works Internally

When you write a .bicep file, you are writing in a declarative language. The Bicep compiler (bicep build or az bicep build) transpiles the .bicep file into a standard ARM template JSON file. This JSON file is the artifact that gets deployed. The compilation process involves:

1.

Parsing: The Bicep file is parsed into an abstract syntax tree (AST).

2.

Symbol Resolution: Symbolic names for resources, modules, parameters, and variables are resolved. References like storageAccount.name are checked for correctness.

3.

Dependency Analysis: Bicep automatically determines resource dependencies by scanning symbolic references. For example, if a virtual machine references a network interface's ID, Bicep adds a dependsOn entry in the JSON.

4.

Code Generation: The AST is translated into ARM template JSON, including all resource declarations, parameters, variables, outputs, and functions.

5.

Validation: Bicep performs pre-flight validation (e.g., type checking, identifier uniqueness) before generating JSON.

The generated JSON is then deployed using standard Azure deployment methods: New-AzResourceGroupDeployment (PowerShell), az deployment group create (CLI), or REST API. Bicep itself does not handle deployment; it only generates the template.

Key Components, Values, and Defaults

#### Parameters - Declared with param keyword. - Supported types: string, int, bool, object, array, and secureString, secureObject. - Default values can be specified: param location string = resourceGroup().location. - Decorators: @secure(), @minLength(), @maxLength(), @allowed(), @description(), @metadata(). - Example:

@secure()
  param adminPassword string
  
  @allowed([
    'Standard_LRS'
    'Standard_GRS'
  ])
  param storageSKU string = 'Standard_LRS'

#### Variables - Declared with var keyword. - Can hold complex expressions, including function calls. - Evaluated at compile time (not runtime). - Example:

var uniqueName = 'storage${uniqueString(resourceGroup().id)}'

#### Resources - Declared with resource keyword followed by a symbolic name. - Syntax: resource <symbolicName> '<type>@<apiVersion>' = { ... } - The symbolic name is used to reference the resource elsewhere (e.g., myStorage.name). - Properties are defined in the body. - Example:

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
    name: 'mystorage${uniqueString(resourceGroup().id)}'
    location: resourceGroup().location
    sku: {
      name: 'Standard_LRS'
    }
    kind: 'StorageV2'
  }

#### Modules - Modules allow you to compose deployments from separate Bicep files. - Declared with module keyword. - Syntax: module <symbolicName> '<path-to-file>' = { ... } - Modules can pass parameters and receive outputs. - Example:

module networkingModule './networking.bicep' = {
    name: 'networkingDeployment'
    params: {
      vnetName: 'myVNet'
      addressPrefix: '10.0.0.0/16'
    }
  }

Modules are compiled into nested deployments in the ARM template.

#### Outputs - Declared with output keyword. - Used to return values from the deployment, e.g., resource IDs or endpoints. - Example:

output storageEndpoint string = storageAccount.properties.primaryEndpoints.blob

#### Functions - Bicep supports built-in functions similar to ARM template functions. - Examples: resourceGroup(), subscription(), uniqueString(), concat(), format(), reference(). - Functions are evaluated at deployment time (except uniqueString which is deterministic based on input).

#### Decorators - Decorators are metadata annotations that modify behavior. - Common decorators: @secure(), @description(), @allowed(), @minLength(), @maxLength(), @minValue(), @maxValue(), @metadata(). - Example:

@description('The Azure region for resources.')
  param location string = resourceGroup().location

Configuration and Verification Commands

#### Install Bicep CLI - Via Azure CLI: az bicep install - Via manual download from GitHub releases. - Check version: az bicep version

#### Build Bicep to JSON - az bicep build --file main.bicep - Outputs main.json in the same directory. - To specify output: --outfile deploy.json

#### Deploy Bicep Directly (az deployment group create) - az deployment group create --resource-group myRG --template-file main.bicep - Azure CLI automatically builds the Bicep file before deployment (requires Bicep CLI installed).

#### Decompile ARM JSON to Bicep - az bicep decompile --file template.json - Produces a .bicep file. Note: decompilation may not be perfect and often requires manual adjustments.

#### Validate Bicep - az deployment group validate --resource-group myRG --template-file main.bicep

#### What-if Deployment - az deployment group what-if --resource-group myRG --template-file main.bicep - Shows preview of changes without applying them.

Interaction with Related Technologies

Bicep is tightly integrated with Azure DevOps and GitHub Actions. You can use the AzureResourceManagerTemplateDeployment@3 task in Azure Pipelines with a main.bicep file. The task automatically builds the Bicep file if the Bicep CLI is present on the agent. Similarly, GitHub Actions can use the azure/arm-deploy@v1 action with a Bicep file.

Bicep also integrates with Azure Policy: you can deploy policy definitions using Bicep modules. For secrets, use Azure Key Vault references in parameter files, not hardcoded values. Bicep supports getSecret function when referencing key vault.

Bicep does not replace Terraform; both can manage Azure resources. However, Bicep is Azure-native, while Terraform is multi-cloud. For AZ-204, focus on Bicep and ARM templates.

Best Practices for AZ-204

Always use parameters for environment-specific values (e.g., names, SKUs).

Use @secure() for passwords and keys.

Use modules to break down complex deployments into reusable components.

Leverage resourceGroup().location for location to avoid hardcoding.

Use uniqueString() to generate globally unique names.

Use output to expose resource IDs for chaining deployments.

Avoid using reference() function in Bicep; use symbolic references instead.

For conditional deployment, use if expressions in resource declarations.

Use loops with for to create multiple resources.

Always run what-if before actual deployment to catch errors.

Common Exam Traps

Trap: Thinking Bicep is a separate deployment engine. Reality: Bicep compiles to ARM JSON; ARM does the actual deployment.

Trap: Using dependsOn manually. Bicep handles dependencies automatically; manual dependsOn is rarely needed and can cause circular dependencies.

Trap: Assuming Bicep supports all ARM template features. Bicep covers most, but some advanced ARM functions (e.g., copy index loops) are handled differently (use for loops).

Trap: Forgetting to install Bicep CLI for az deployment group create with a .bicep file. The CLI will prompt to install if missing.

Trap: Using reference() function when symbolic reference works. reference() is an ARM function that works in Bicep but is less readable.

Step-by-Step: Deploying a Storage Account with Bicep

1. Create a Bicep file storage.bicep:

param storageName string
   param location string = resourceGroup().location
   
   resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
     name: storageName
     location: location
     sku: {
       name: 'Standard_LRS'
     }
     kind: 'StorageV2'
   }
   
   output storageEndpoint string = storageAccount.properties.primaryEndpoints.blob

2. Create a parameter file storage.parameters.json:

{
     "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
     "contentVersion": "1.0.0.0",
     "parameters": {
       "storageName": {
         "value": "mystorage12345"
       }
     }
   }

3. Build the Bicep file (optional, for inspection):

az bicep build --file storage.bicep

4. Deploy the Bicep file:

az deployment group create --resource-group myRG --template-file storage.bicep --parameters @storage.parameters.json

5. Verify deployment:

az storage account show --name mystorage12345 --resource-group myRG --query primaryEndpoints.blob

This process demonstrates the typical workflow: author Bicep, parameterize, deploy, verify.

Walk-Through

1

Author Bicep File

Create a `.bicep` file using any text editor or VS Code with the Bicep extension. Define parameters, variables, resources, and outputs. Use symbolic names for resources to enable automatic dependency detection. For example, declare a virtual network and a subnet, then reference the subnet ID in a network interface declaration. Bicep will parse these references and generate the correct `dependsOn` in the compiled JSON. Ensure all resource types and API versions are correct; you can find these in the Azure documentation or by exporting an existing resource's ARM template.

2

Parameterize the Template

Identify values that change between environments (e.g., names, SKUs, locations) and declare them as parameters with appropriate types and decorators. Use `@secure()` for secrets. Provide default values where possible. Create a separate parameters JSON file for each environment (dev, test, prod) to avoid modifying the Bicep file. The parameter file uses the same schema as ARM template parameters. During deployment, pass the parameter file using the `--parameters` flag.

3

Build the Bicep to JSON

Run `az bicep build --file main.bicep` to compile the Bicep file into an ARM template JSON. This step validates the syntax, resolves symbols, and generates the final JSON. The output file (`main.json`) can be inspected to verify correctness. If there are errors, the compiler provides line numbers and descriptions. Building is optional because `az deployment group create` can build on the fly, but building separately helps catch errors early and allows you to review the generated JSON.

4

Deploy Using Azure CLI or PowerShell

Use `az deployment group create --resource-group myRG --template-file main.bicep --parameters @params.json` to deploy. The CLI detects the `.bicep` extension and automatically invokes the Bicep compiler if installed. If not installed, it prompts to install. The deployment submits the compiled JSON to ARM, which then provisions resources in the specified resource group. Monitor deployment status with `az deployment group show`. For production, integrate this step into a CI/CD pipeline.

5

Verify and Manage Deployed Resources

After deployment, verify resources using Azure portal, CLI, or PowerShell. Check outputs from the deployment (e.g., storage endpoint). Use `az deployment group show` to retrieve outputs. For updates, modify the Bicep file and redeploy; ARM will incrementally apply changes. Use `what-if` to preview changes before actual deployment. For cleanup, delete the resource group or use `az deployment group delete`.

What This Looks Like on the Job

Enterprise Scenario 1: Multi-Tier Application Deployment

A financial services company deploys a three-tier web application (frontend, API, database) across multiple environments. They use Bicep modules to separate concerns: one module for networking (VNet, subnets, NSGs), one for compute (VMSS or App Service), and one for data (SQL Database, Cosmos DB). Each module has its own parameters and outputs. The main main.bicep file orchestrates these modules, passing outputs from networking (e.g., subnet ID) to compute and data modules. This modular approach allows different teams to own different parts of the infrastructure. In production, they deploy using Azure Pipelines with the AzureResourceManagerTemplateDeployment@3 task, which builds and deploys the Bicep. Common issues include module path resolution (use relative paths from the main file) and circular dependencies between modules (avoid by careful design). They run what-if deployments in pull request pipelines to catch unintended changes.

Enterprise Scenario 2: Policy-Driven Governance

A large enterprise enforces tagging and location restrictions across all subscriptions. They create a Bicep module that deploys Azure Policy initiatives (e.g., require tag 'CostCenter' and restrict locations to 'eastus' and 'westus'). This module is deployed to a management group. All application deployments include a reference to this policy module to ensure compliance. The Bicep module uses resource declarations for policy definitions and assignments. A common pitfall is forgetting to assign the policy at the correct scope; Bicep modules can accept a scope parameter. They also use @description decorators to document the purpose of each policy.

Scenario 3: Blue-Green Deployment with Bicep

A SaaS provider uses blue-green deployment for zero-downtime updates. They maintain two identical environments (blue and green) in separate resource groups. Bicep templates are parameterized with an environment suffix (e.g., '-blue' or '-green'). The deployment pipeline runs Bicep to update the inactive environment, then swaps the traffic manager endpoint. Bicep's output is used to capture the new endpoint URL. The challenge is managing state between environments; they store deployment outputs in a key vault or a configuration database. Misconfiguration often occurs when the parameter file for the wrong environment is used, leading to accidental updates of the live environment. They mitigate this by using separate parameter files and pipeline variables.

How AZ-204 Actually Tests This

What AZ-204 Tests on Bicep

AZ-204 Objective 1.3 includes 'Create and manage compute resources' and specifically mentions 'Use ARM templates and Bicep to deploy resources'. Exam questions typically test:

Basic Bicep syntax (resource, param, var, module, output)

Automatic dependency detection vs manual dependsOn

How to deploy Bicep files (CLI commands)

Parameter files and secure parameters

Modules and nested deployments

Differences between Bicep and ARM templates

Integration with Azure DevOps/GitHub Actions

Common Wrong Answers and Why Candidates Choose Them

1.

'Bicep is a deployment engine separate from ARM.' Candidates confuse Bicep with Terraform or other tools. Reality: Bicep compiles to ARM JSON; ARM does the deployment.

2.

'You must manually add dependsOn for all resources.' Candidates unfamiliar with Bicep assume it works like raw ARM templates. Reality: Bicep auto-detects dependencies via symbolic references.

3.

'Bicep cannot deploy existing resources; it only creates new ones.' Candidates think Bicep is only for greenfield deployments. Reality: Bicep supports incremental updates and can manage existing resources.

4.

'Bicep files are deployed directly without compilation.' Candidates see the --template-file main.bicep syntax and assume it's deployed as-is. Reality: The CLI builds it behind the scenes.

Specific Numbers, Values, and Terms That Appear on the Exam

Bicep version 0.4+ (current at exam time)

Command: az bicep install, az bicep build, az deployment group create --template-file main.bicep

Parameter decorators: @secure(), @allowed(), @description()

Module syntax: module <name> '<path>' = { ... }

Output syntax: output <name> <type> = <value>

Built-in functions: resourceGroup().location, uniqueString(), concat()

File extension: .bicep

Edge Cases and Exceptions

Circular dependencies: Bicep cannot resolve circular symbolic references; you must redesign.

Module scopes: Modules can be deployed to different resource groups using scope property.

Conditional deployment: Use if condition in resource declaration, e.g., resource vm '...' = if (deployVM) { ... }.

Loops: Use for expression: resource disks '...' = [for i in range(0, 3): { ... }].

Decompilation limitations: Decompiling ARM JSON to Bicep is not perfect; manual cleanup often needed.

How to Eliminate Wrong Answers Using the Underlying Mechanism

When you see a question about deploying a Bicep file, remember: Bicep is just a syntax for ARM templates. Any answer that suggests Bicep bypasses ARM or has its own deployment engine is wrong. If a question mentions dependsOn, know that Bicep auto-generates it, so you rarely need to specify it manually. If the question involves parameter files, recall that they follow the same JSON schema as ARM parameter files. For module questions, remember that modules become nested deployments in the ARM template. Use these mechanisms to eliminate distractors.

Key Takeaways

Bicep is a domain-specific language that compiles to ARM templates; it does not replace ARM.

Use symbolic resource names to enable automatic dependency detection—avoid manual dependsOn.

Parameterize all environment-specific values; use @secure() for secrets.

Modules enable code reuse; each module becomes a nested deployment in the ARM template.

Deploy Bicep with `az deployment group create --template-file file.bicep`; the CLI builds it automatically.

Run `az deployment group what-if` to preview changes before deployment.

Bicep supports loops with `for`, conditionals with `if`, and functions like uniqueString().

Easy to Mix Up

These come up on the exam all the time. Here's how to tell them apart.

Bicep

Declarative DSL with cleaner syntax, no commas, no brackets

Automatic dependency detection via symbolic references

Supports modules for code reuse and composition

Built-in parameter decorators like @secure() and @allowed()

Compiles to ARM JSON; requires Bicep CLI or Azure CLI with Bicep extension

ARM Templates (JSON)

Verbose JSON syntax with strict formatting

Manual dependsOn array required for dependencies

No native module support; use nested deployments which are cumbersome

No decorators; parameters defined in a separate section with metadata

Native to ARM; no compilation step needed, but harder to author and maintain

Bicep

Azure-native, tightly integrated with Azure services

Declarative but with a simpler syntax than Terraform HCL

State management handled by ARM (idempotent deployments)

Supports only Azure; no multi-cloud capability

Free and open-source, built by Microsoft

Terraform

Multi-cloud (Azure, AWS, GCP, etc.)

HCL syntax with more complex constructs

Requires state file management (local or remote backend)

Supports multiple providers, not just Azure

Open-source but with a paid enterprise version

Watch Out for These

Mistake

Bicep is a separate infrastructure-as-code tool like Terraform that directly provisions resources.

Correct

Bicep is a domain-specific language that compiles to ARM templates. The actual deployment is performed by Azure Resource Manager, not Bicep. Bicep only generates the JSON template that ARM consumes.

Mistake

Bicep cannot manage existing resources; it only creates new ones.

Correct

Bicep supports incremental deployments. If you deploy a Bicep template to an existing resource group, ARM will update existing resources and create new ones as defined. Bicep is fully declarative and idempotent.

Mistake

You must manually specify `dependsOn` for all resource dependencies in Bicep.

Correct

Bicep automatically detects dependencies by analyzing symbolic references. For example, if a virtual machine references a network interface's ID, Bicep adds the appropriate `dependsOn` in the compiled JSON. Manual `dependsOn` is rarely needed and can cause circular dependency errors.

Mistake

Bicep files are deployed directly without any compilation step.

Correct

When you run `az deployment group create --template-file main.bicep`, the Azure CLI first invokes the Bicep compiler to build the ARM template JSON, then submits that JSON to ARM. The Bicep file itself is never sent to ARM.

Mistake

Bicep only works with Azure CLI; you cannot use it with PowerShell or REST API.

Correct

Bicep can be used with any deployment method that accepts ARM templates. You can build the JSON with `az bicep build` and then deploy it using PowerShell (`New-AzResourceGroupDeployment -TemplateFile main.json`) or REST API. The Bicep CLI is separate from Azure CLI.

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

How do I install Bicep CLI?

You can install Bicep CLI via Azure CLI by running `az bicep install`. Alternatively, download the binary from GitHub releases. After installation, verify with `az bicep version`. The Bicep CLI is required for building .bicep files to JSON. Azure CLI can also build on the fly if Bicep is installed.

Can I deploy a Bicep file without building it first?

Yes, you can deploy directly with `az deployment group create --template-file main.bicep`. The Azure CLI will automatically invoke the Bicep compiler to build the JSON before sending it to ARM. However, building separately with `az bicep build` allows you to inspect the generated JSON and catch errors early.

How do I pass parameters to a Bicep deployment?

Create a parameters JSON file (e.g., params.json) with the same schema as ARM parameter files. Then use `--parameters @params.json` in the deployment command. You can also pass individual parameters with `--parameters param1=value1 param2=value2`. For secure parameters, use @secure() decorator and avoid hardcoding values.

What is the difference between Bicep modules and nested deployments?

Bicep modules are a syntactic feature that compiles to nested deployments in the ARM template. A module reference in Bicep becomes a `Microsoft.Resources/deployments` resource in the JSON. Modules allow you to split your template into separate files and reuse them. Nested deployments in raw ARM templates are more verbose and harder to manage.

How does Bicep handle resource dependencies?

Bicep automatically detects dependencies by analyzing symbolic references. For example, if you reference a resource's property (e.g., `nic.id`), Bicep adds a `dependsOn` entry in the compiled JSON. You can also add explicit dependencies using the `dependsOn` property, but it's rarely needed and can cause circular dependencies.

Can I use Bicep to update existing resources?

Yes, Bicep supports incremental deployments. When you deploy a Bicep template to an existing resource group, ARM compares the desired state with the current state and makes necessary changes. This includes creating, updating, or deleting resources as defined. Use `what-if` to preview changes.

What are common mistakes when writing Bicep?

Common mistakes include: forgetting to use `@secure()` for secrets, hardcoding resource names instead of using parameters, not using `uniqueString()` for globally unique names, manually adding `dependsOn` when not needed, and incorrect module paths. Also, ensure you use the correct API version for resource types; you can find these in the Azure documentation or by exporting a template.

Terms Worth Knowing

Ready to put this to the test?

You've just covered Bicep Templates for Developers — now see how well it sticks with free AZ-204 practice questions. Full explanations included, no account needed.

Done with this chapter?