11.1 Infrastructure as Code Strategy: Bicep, ARM & Terraform

Key Takeaways

  • Infrastructure as Code (IaC) guarantees idempotent, version-controlled, and auditable cloud provisioning, replacing manual portal actions with repeatable declarative definitions.
  • Bicep serves as Azure's first-class declarative Domain-Specific Language (DSL), offering immediate Day-0 resource provider support, cleaner syntax, and direct transpilation to ARM JSON without external state files.
  • Terraform delivers multi-cloud orchestration through HashiCorp Configuration Language (HCL), requiring an Azure Blob Storage remote backend with Blob lease state locking to prevent concurrent deployment corruption.
  • Pre-flight validation combines the Bicep/ARM what-if operation (`az deployment group what-if`) and Terraform plan with static linting via PSRule for Azure and security scanning with Checkov.
  • The `AzureResourceManagerTemplateDeployment@3` pipeline task natively compiles and deploys both ARM templates and Bicep files, supporting both Incremental and Complete deployment modes.
Last updated: September 2026

11.1 Infrastructure as Code Strategy: Bicep, ARM & Terraform

In modern DevOps delivery models, infrastructure provisioning is treated with the same engineering rigor as application source code. Historically, cloud infrastructure was provisioned through manual administrative portals or fragmented imperative shell scripts. These manual approaches introduced configuration drift, security vulnerabilities, human error, and deployment bottlenecks across enterprise environments.

Infrastructure as Code (IaC) solves these challenges by formalizing infrastructure requirements into declarative, version-controlled configuration files. For the AZ-400 exam, candidates must master core IaC architectural principles, evaluate trade-offs between Azure native tooling (ARM templates and Bicep) and multi-cloud solutions (HashiCorp Terraform), build multi-layered pre-flight testing and static analysis pipelines, and configure automated Azure Pipelines deployment tasks.


1. Core Principles of Infrastructure as Code

Enterprise IaC architectures are anchored by five fundamental engineering tenets:

Declarative vs. Imperative Infrastructure

  • Imperative Approach (Procedural): Defines how steps must be executed sequentially (e.g., Azure CLI scripts, PowerShell workflows, Bash scripts). The script issues explicit commands: az group create, az storage account create, az network vnet create. If an imperative script fails midway, resuming requires custom error handling to prevent attempting to recreate existing resources.
  • Declarative Approach (Desired State): Defines what the end-state target topology must look like (e.g., Bicep, ARM JSON, Terraform HCL). The underlying orchestration engine calculates the delta between the live cloud state and the desired state, executing only the necessary operations to reconcile differences.

Idempotency

Idempotency is the property where an operation can be executed repeatedly with identical inputs and produce the exact same outcome without causing unintended side effects or creating duplicate resources. If an IaC template defines a Virtual Network with subnet 10.0.1.0/24, running the deployment ten consecutive times leaves the Virtual Network intact with subnet 10.0.1.0/24, modifying only changed properties.

Immutability vs. Mutable Infrastructure

  • Mutable Infrastructure: Servers and network resources are updated in place over time via patch scripts, remote configuration tools, and manual tweaks. Over time, servers develop snowflake configurations that cannot be reproduced.
  • Immutable Infrastructure: Cloud components are never modified in place. When an update or patch is required, new infrastructure is provisioned from updated IaC templates, traffic is routed to the new infrastructure (e.g., blue/green deployment or canary release), and the old infrastructure is destroyed. This eliminates configuration drift entirely.

Version Control and Peer Review

IaC files reside alongside application code in Git repositories. Every infrastructure modification is proposed via Pull Requests (PRs), undergoes automated static analysis, requires branch policies and peer reviews, and generates an audit trail linking cloud modifications to specific commits and work items.


2. In-Depth Tooling Comparison: ARM, Bicep, and Terraform

DevOps architects must evaluate the specific trade-offs between Azure Resource Manager (ARM) JSON templates, Azure Bicep, and HashiCorp Terraform.

┌─────────────────────────────────────────────────────────────────────────────┐
│                     Azure Control Plane (ARM REST API)                      │
└──────────────────────────────────▲──────────────────────────────────────────┘
                                   │
         ┌─────────────────────────┼─────────────────────────┐
         │ Direct Submissions      │ Native Transpilation    │ REST API Provider
         │                         │                         │
┌─────────────────┐       ┌─────────────────┐       ┌─────────────────┐
│  ARM Templates  │       │   Azure Bicep   │       │ HashiCorp       │
│  (Verbose JSON) │       │   (Azure DSL)   │       │ Terraform (HCL) │
└─────────────────┘       └─────────────────┘       └────────▲────────┘
                                                             │ State File
                                                    ┌────────┴────────┐
                                                    │ Azure Blob      │
                                                    │ Storage Backend │
                                                    └─────────────────┘

Azure Resource Manager (ARM) Templates

ARM templates are native JSON documents that declare Azure resources, parameters, variables, and outputs.

  • Structure: Root schema includes $schema, contentVersion, parameters, variables, functions, resources, and outputs.
  • Limitations: Verbose, complex JSON syntax; lack of native looping and modularity; difficult readability for large architectures; complex parameter and expression escaping ("[concat('storage', uniqueString(resourceGroup().id))]").
  • Scopes: Supports deployments at Resource Group, Subscription, Management Group, and Tenant scopes.

Azure Bicep

Bicep is Microsoft's domain-specific language (DSL) purpose-built for deploying Azure resources declaratively. It acts as a transparent abstraction layer over ARM JSON.

  • Transparent Transpilation: Bicep files (.bicep) compile directly into standard ARM template JSON (az bicep build). The ARM engine processes the compiled JSON natively.
  • Day-0 Resource Provider Support: Because Bicep maps 1:1 with ARM APIs, any new Azure service or API feature released by Microsoft is supported in Bicep on Day 0 without requiring third-party provider updates.
  • Stateless Architecture: Bicep does not use or maintain an external state file. The Azure Resource Manager control plane itself acts as the single source of truth for all deployed resources.
  • Clean Modular Syntax: Supports strongly typed parameters, decorators (@secure(), @description(), @allowed(), @minLength()), and intuitive module consumption (module myStorage 'modules/storage.bicep' = { ... }).
// Example: Modular Bicep Definition (main.bicep)
@description('Environment name for resource naming')
@allowed(['dev', 'qa', 'prod'])
param environment string = 'dev'

@description('Location for all deployment resources')
param location string = resourceGroup().location

@secure()
@description('Administrator password for the SQL Server')
param adminPassword string

var uniqueStorageName = 'st${environment}${uniqueString(resourceGroup().id)}'

resource appStorage 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: uniqueStorageName
  location: location
  sku: {
    name: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    supportsHttpsTrafficOnly: true
    allowBlobPublicAccess: false
  }
}

output storageAccountId string = appStorage.id

HashiCorp Terraform

Terraform is an open-source, multi-cloud IaC tool using HashiCorp Configuration Language (HCL). It manages cloud infrastructure across Azure (azurerm provider), AWS, GCP, and SaaS platforms.

  • Provider Architecture: Relies on community and HashiCorp-maintained providers that interface with cloud REST APIs.
  • State Management: Terraform requires a state file (terraform.tfstate) to map declared resources to real-world cloud resource IDs, track metadata, and cache resource attributes.
  • State Storage in Azure: In enterprise production pipelines, the state file must be stored in a remote backend: an Azure Blob Storage container within a locked-down storage account.
  • State Locking via Blob Leases: To prevent concurrent pipeline runs from corrupting the state file, the azurerm backend utilizes native Azure Blob Storage leases. When a pipeline executes terraform apply, Terraform acquires an exclusive write lease on the .tfstate blob. Concurrent runs detect the active lease and halt immediately.
# Example: Production Terraform Remote Backend Configuration
terraform {
  required_version = ">= 1.6.0"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.100.0"
    }
  }
  backend "azurerm" {
    resource_group_name  = "rg-platform-governance"
    storage_account_name = "sttfstateproduction001"
    container_name       = "tfstate"
    key                  = "prod.microservices.tfstate"
    use_azuread_auth     = true
  }
}

provider "azurerm" {
  features {}
}

3. Comprehensive IaC Decision Matrix

| Architectural Dimension | Azure Resource Manager (ARM) | Azure Bicep | HashiCorp Terraform | | :--- | :--- | :--- | | Syntax & Language | Verbose JSON | Clean, concise DSL | HashiCorp Configuration Language (HCL) | | Target Ecosystem | Azure only | Azure only | Multi-cloud (Azure, AWS, GCP, Kubernetes) | | State Management | Stateless (ARM is source of truth) | Stateless (ARM is source of truth) | Stateful (terraform.tfstate in Azure Blob Storage) | | State Locking Mechanism | Native (handled by ARM transactions) | Native (handled by ARM transactions) | Azure Blob lease on .tfstate blob | | Azure Day-0 Feature Support | Yes (immediate API access) | Yes (immediate API access) | Delayed (depends on provider releases) | | Modularity & Reuse | Linked/Nested templates | Native module keyword | Modules (local and Terraform Registry) | | Learning Curve | High (steep JSON boilerplate) | Low (intuitive for Azure engineers) | Moderate (HCL syntax + state lifecycle) | | Execution Engine | Azure Control Plane (ARM) | Transpiles to ARM -> Azure Control Plane | Terraform CLI engine on pipeline agent |


4. Pre-Flight Testing, Security Linting, and Validation

Deploying broken or non-compliant infrastructure directly to cloud subscriptions causes runtime failures and security breaches. Production pipelines incorporate automated pre-flight testing and security scanning stages.

The what-if Operation (Bicep / ARM)

The what-if operation evaluates your Bicep or ARM template against the current live cloud state, predicting the exact changes Azure Resource Manager will make without modifying resources (az deployment group what-if or az deployment sub what-if).

What-If categorizes predicted actions into six distinct change types:

  • Create: Resource does not exist and will be newly created.
  • Delete: Resource exists live but is missing from template (occurs only in Complete deployment mode).
  • Modify: Resource exists and properties will be changed in place.
  • NoChange: Resource exists and configuration matches template perfectly.
  • Ignore: Resource type is not evaluated by what-if.
  • Unsupported: What-if cannot predict changes for this specific resource provider.
# Running what-if analysis via Azure CLI
az deployment group what-if \
  --resource-group rg-production-workloads \
  --template-file main.bicep \
  --parameters environment=prod adminPassword=$(sqlAdminPassword)

Terraform plan

Similar to what-if, terraform plan compares the local configuration against the remote state and live infrastructure. In automated CI/CD pipelines, output the plan to an execution file: terraform plan -out=tfplan. In the deployment stage, execute terraform apply tfplan to guarantee that only the inspected plan is deployed.

Static Code Analysis & Security Scanning

  1. Bicep Linter: Built directly into the Bicep compiler. Enforces naming conventions, unused parameters, hardcoded secrets, and secure configurations via bicepconfig.json.
  2. PSRule for Azure: PowerShell-based analysis engine that validates ARM and Bicep templates against 400+ Azure Well-Architected Framework (WAF) rules prior to deployment (e.g., verifying storage encryption, TLS versions, and geo-redundancy).
  3. Checkov & tfsec / Trivy: Security scanners that scan Terraform, Bicep, and ARM templates for security misconfigurations, CIS benchmarks, and compliance violations (e.g., open NSG ports, unencrypted disks, missing tags).
  4. Terratest: An advanced Go-based automated integration testing framework for infrastructure. Terratest spins up ephemeral infrastructure in real Azure test subscriptions, validates endpoints via HTTP/TLS requests, and runs terraform destroy or Bicep teardown upon completion.

5. Pipeline Deployment Tasks & Deployment Modes

In Azure DevOps, infrastructure automation relies on specialized tasks:

AzureResourceManagerTemplateDeployment@3

This official Microsoft task natively handles both ARM JSON templates and .bicep files. When a .bicep file is targeted, the task automatically compiles the Bicep code using the agent's installed Bicep CLI before submitting the payload to ARM.

Incremental vs. Complete Deployment Modes

A fundamental AZ-400 exam concept is the distinction between deployment modes:

Deployment ModeHandling of Existing Resources in Resource Group
Incremental (Default)Modifies existing resources to match the template and adds newly defined resources. Leaves any existing resources in the resource group that are not mentioned in the template completely untouched.
CompleteReconciles the resource group to mirror the template exactly. Deletes any existing resources in the resource group that are not declared in the template.

[!CAUTION] Using deploymentMode: 'Complete' in a pipeline will permanently delete existing storage accounts, databases, or networking components inside the target resource group if they are omitted from the template definition. Production pipelines almost universally mandate Incremental mode unless managing dedicated, disposable resource groups.


6. Production YAML Pipeline: Bicep Validation, What-If & Deployment

The following multi-stage YAML pipeline implements pre-flight linting, PSRule compliance checking, what-if change preview, manual gate approval, and automated deployment:

name: $(Date:yyyyMMdd)$(Rev:.r)

trigger:
  branches:
    include:
      - main
  paths:
    include:
      - infrastructure/**

variables:
  azureServiceConnection: 'sc-azure-platform-prod'
  resourceGroupName: 'rg-ecommerce-prod'
  location: 'eastus2'
  templateFile: 'infrastructure/main.bicep'

stages:
  # ==========================================================================
  # STAGE 1: STATIC CODE ANALYSIS & LINTING
  # ==========================================================================
  - stage: ValidateAndLint
    displayName: 'Lint & Compliance Evaluation'
    jobs:
      - job: BicepLintAndPSRule
        displayName: 'Lint Bicep and Run PSRule'
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - checkout: self
          - task: AzureCLI@2
            displayName: 'Bicep Build & Syntactic Lint'
            inputs:
              azureSubscription: $(azureServiceConnection)
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az bicep build --file $(templateFile)
          - task: ps-rule-assert@2
            displayName: 'Run PSRule Azure Well-Architected Checks'
            inputs:
              inputType: 'repository'
              modules: 'PSRule.Rules.Azure'

  # ==========================================================================
  # STAGE 2: PRE-FLIGHT WHAT-IF PREVIEW
  # ==========================================================================
  - stage: PreviewChanges
    displayName: 'What-If Change Analysis'
    dependsOn: ValidateAndLint
    condition: succeeded()
    jobs:
      - job: WhatIfAnalysis
        displayName: 'Execute ARM What-If'
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: AzureCLI@2
            displayName: 'Generate What-If Prediction'
            inputs:
              azureSubscription: $(azureServiceConnection)
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az deployment group what-if \
                  --resource-group $(resourceGroupName) \
                  --template-file $(templateFile) \
                  --result-format FullResourcePayloads

  # ==========================================================================
  # STAGE 3: PRODUCTION DEPLOYMENT
  # ==========================================================================
  - stage: DeployProduction
    displayName: 'Deploy to Production'
    dependsOn: PreviewChanges
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: DeployBicep
        displayName: 'Deploy Bicep Infrastructure'
        environment: 'production-infrastructure'
        strategy:
          runOnce:
            deploy:
              steps:
                - checkout: self
                - task: AzureResourceManagerTemplateDeployment@3
                  displayName: 'Deploy Bicep Template'
                  inputs:
                    deploymentScope: 'Resource Group'
                    azureResourceManagerConnection: $(azureServiceConnection)
                    subscriptionId: ''
                    action: 'Create Or Update Resource Group'
                    resourceGroupName: $(resourceGroupName)
                    location: $(location)
                    templateLocation: 'Linked artifact'
                    csmFile: $(templateFile)
                    deploymentMode: 'Incremental'

7. Realistic Exam Scenarios & Common Traps

Scenario: Disaster Recovery from Unintended Resource Deletion

Context: A DevOps engineer updates an existing ARM pipeline for a production billing system. After the pipeline runs, developers report that an Azure Key Vault and several storage accounts in the resource group were abruptly destroyed, causing an immediate outage. Root Cause: The engineer configured deploymentMode: 'Complete' on the AzureResourceManagerTemplateDeployment@3 task instead of deploymentMode: 'Incremental'. Because the Key Vault and storage accounts were provisioned in an earlier phase and omitted from the new template, ARM deleted every resource not explicitly defined in the template.

Common Exam Traps to Avoid

  • Trap: Believing Bicep requires a state file. Candidates frequently assume Bicep operates like Terraform and search for options to configure an Azure Blob Storage backend for Bicep. Bicep has no state file; the Azure Resource Manager engine natively interrogates the live cloud state.
  • Trap: Neglecting state locking in Terraform. Storing a Terraform state file in Azure Blob Storage without configuring the azurerm backend's native Blob lease mechanism permits concurrent CI/CD pipelines to overwrite state simultaneously, resulting in catastrophic state corruption.
  • Trap: Confusing Bicep compilation with native execution. While developers write .bicep files, the Azure Resource Manager engine underneath strictly executes ARM JSON. Bicep tooling on the pipeline agent transpiles .bicep to JSON before the REST payload is transmitted to the Azure management endpoint.
Loading diagram...
Automated Infrastructure as Code Validation and Deployment Pipeline
Test Your Knowledge

An enterprise organization is establishing their cloud infrastructure delivery strategy exclusively on Microsoft Azure. The architecture team mandates that the chosen Infrastructure as Code (IaC) solution must provide immediate Day-0 support for all newly released Azure resource provider capabilities, must not require maintaining or locking an external state file in remote storage, and must offer concise, modular syntax. Which tooling strategy should the DevOps architect select?

A
B
C
D
Test Your Knowledge

A DevOps engineer configures an Azure Pipelines release stage to deploy updated networking and security templates to an existing production resource group using the 'AzureResourceManagerTemplateDeployment@3' task. Shortly after execution, administrators report that several unreferenced Azure Storage Accounts and a legacy Virtual Network inside the resource group were permanently deleted. What configuration setting in the deployment task caused this behavior?

A
B
C
D
Test Your Knowledge

A DevOps team wants to implement an automated validation gate in their continuous delivery pipeline before deploying Bicep templates to production. The gate must preview proposed changes against the live cloud environment, highlighting which resources will be created, modified, or deleted without actually executing the deployment or altering infrastructure. Which command or tool should the team integrate into the preview stage?

A
B
C
D