# Bicep Templates for Developers

> Chapter 86 of the Courseiva AZ-204 curriculum — https://courseiva.com/learn/az-204/bicep-204

**Official objective:** 1.3 — Compute

## Introduction

Central to the compute objective (1.3), Bicep templates are 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.

## Bicep: A Blueprint for Azure Infrastructure

A house is constructed from a detailed blueprint; deploying Azure infrastructure is no different. 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.

## Core explanation

### 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:
  ```bicep
  @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:
  ```bicep
  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:
  ```bicep
  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:
  ```bicep
  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:
  ```bicep
  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:
  ```bicep
  @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`:
   ```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`:
   ```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):
   ```bash
   az bicep build --file storage.bicep
   ```

4. **Deploy the Bicep file**:
   ```bash
   az deployment group create --resource-group myRG --template-file storage.bicep --parameters @storage.parameters.json
   ```

5. **Verify deployment**:
   ```bash
   az storage account show --name mystorage12345 --resource-group myRG --query primaryEndpoints.blob
   ```

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

## Real-world context

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

## Exam focus

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

## Step by step

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

## Comparisons

### Bicep vs ARM Templates (JSON)

**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 vs Terraform

**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

## Common misconceptions

- **Misconception:** Bicep is a separate infrastructure-as-code tool like Terraform that directly provisions resources. **Reality:** 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.
- **Misconception:** Bicep cannot manage existing resources; it only creates new ones. **Reality:** 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.
- **Misconception:** You must manually specify `dependsOn` for all resource dependencies in Bicep. **Reality:** 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.
- **Misconception:** Bicep files are deployed directly without any compilation step. **Reality:** 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.
- **Misconception:** Bicep only works with Azure CLI; you cannot use it with PowerShell or REST API. **Reality:** 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.

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

## FAQ

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

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/az-204/bicep-204
