Why IaC Matters Architecturally

Infrastructure as Code isn't just automation — it's an architectural control plane. Without it, you have:

  • Configuration drift — prod diverges from what you think it is
  • No review process — changes bypass pull requests and peer review
  • No blast radius control — one click can delete everything
  • No repeatability — recreating an environment means tribal knowledge

IaC as Architecture Governance

When infrastructure is code, you get: version control, pull request reviews, automated testing, deployment gates, and audit trails. This makes your infrastructure as governable as your application code.

ARM vs Bicep vs Terraform — Decision Framework

CriterionARM JSONBicepTerraform
SyntaxVerbose JSONClean, concise DSLHCL (declarative)
Learning curveHigh (JSON nesting)Low (transpiles to ARM)Medium
State managementNone (stateless)None (stateless)Required (state file)
Multi-cloudAzure onlyAzure onlyAny cloud
Day-0 supportImmediateImmediate (same engine)Delayed (provider updates)
ModularityLinked templatesModules + registriesModules + registry
EcosystemMicrosoft docsGrowing, Microsoft-backedMassive (HashiCorp)
Best forLegacy, auto-generatedAzure-only shopsMulti-cloud or existing Terraform orgs

AZ-305 Exam Tip

The exam tests Bicep/ARM concepts (deployment scopes, what-if, template specs) — not Terraform. For exam prep, focus on Bicep. In real life, the choice depends on your org's multi-cloud strategy and existing tooling.

Architect's Rule of Thumb

  • Azure-only + greenfield → Bicep (first-class Azure support, no state to manage)
  • Multi-cloud or existing Terraform → Terraform (unified tooling across providers)
  • Never write raw ARM JSON — Bicep transpiles to it; use Bicep as your authoring format

Bicep Deep Dive

Core Syntax

// Parameters with decorators
@description('The Azure region for deployment')
@allowed(['eastus', 'westeurope', 'southeastasia'])
param location string = resourceGroup().location

@minValue(1)
@maxValue(10)
param instanceCount int = 2

// Variables
var baseName = 'app-${uniqueString(resourceGroup().id)}'

// Resource with conditions
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: '${baseName}st'
  location: location
  kind: 'StorageV2'
  sku: { name: 'Standard_ZRS' }
  properties: {
    minimumTlsVersion: 'TLS1_2'
    supportsHttpsTrafficOnly: true
  }
}

// Loops
resource nsgRules 'Microsoft.Network/networkSecurityGroups/securityRules@2023-04-01' = [for (rule, i) in securityRules: {
  name: rule.name
  properties: rule.properties
}]

// Outputs
output storageId string = storageAccount.id
output storageName string = storageAccount.name

Key Language Features for Architects

  • Decorators (@allowed, @minValue, @secure) — enforce guardrails at template level
  • Conditions (if) — deploy resources conditionally based on environment
  • Loops (for) — create multiple resources from arrays
  • Existing resources (existing) — reference resources you don't own without recreating
  • User-defined types — define complex parameter shapes for module interfaces

Deployment Scopes

Bicep/ARM deployments target a scope. The scope determines what you can deploy. This is a critical architecture concept — you deploy different things at different levels.

ScopeTargetWhat You DeployCLI Flag
Resource GroupExisting RGResources (VMs, storage, VNets)az deployment group create
SubscriptionExisting subscriptionResource groups, policies, RBAC, budgetsaz deployment sub create
Management GroupExisting MGPolicies, RBAC, sub-management-groupsaz deployment mg create
TenantEntra ID tenantManagement groups, tenant-level policiesaz deployment tenant create
Deployment scope hierarchy — each level deploys different resource types

Platform Team vs App Team Scopes

  • Platform team deploys at tenant/MG/subscription scope: governance, networking, shared services
  • App teams deploy at resource group scope: their application resources
  • This separation enforces the principle of least privilege in IaC

Modules, Registries & Versioning

Module Composition

// main.bicep — composes modules for a 3-tier app
module network './modules/network.bicep' = {
  name: 'network-deploy'
  params: {
    vnetAddressSpace: '10.1.0.0/16'
    location: location
  }
}

module compute './modules/compute.bicep' = {
  name: 'compute-deploy'
  params: {
    subnetId: network.outputs.appSubnetId
    vmSize: 'Standard_D4s_v5'
    instanceCount: instanceCount
  }
}

module data './modules/database.bicep' = {
  name: 'data-deploy'
  params: {
    subnetId: network.outputs.dataSubnetId
    skuName: 'GP_Gen5_4'
  }
}
Module composition: main orchestrates modules pulled from a versioned registry

Module Registry (Azure Container Registry)

// Consuming a module from a private registry
module network 'br:myregistry.azurecr.io/bicep/modules/network:v1.2.0' = {
  name: 'network-deploy'
  params: { ... }
}

// Publishing a module
// az bicep publish --file ./modules/network.bicep \
//   --target br:myregistry.azurecr.io/bicep/modules/network:v1.2.0

Versioning Strategy

  • Use semantic versioning: major.minor.patch
  • Major = breaking changes (removed parameters, renamed outputs)
  • Minor = new features (new optional parameters)
  • Patch = bug fixes (no interface changes)
  • App teams pin to major version; platform team owns the registry

Template Specs & Deployment Stacks

FeatureTemplate SpecDeployment Stack
What it isVersioned ARM/Bicep template stored in AzureAzure resource that tracks deployed resources
PurposeShare curated templates with RBACLifecycle management + drift protection
Drift detectionNoYes — can deny out-of-band changes
Resource cleanupManualAutomatic — deletes resources removed from template
Deny settingsNoYes — denyWriteAndDelete, denyDelete
Best forSelf-service catalog ("deploy this approved pattern")Platform team managing landing zones with drift protection

Deployment Stacks Are New (GA 2024)

Deployment Stacks solve the "I deleted a resource from my template but it still exists in Azure" problem. They track what was deployed and clean up orphaned resources. Expect this to appear on updated AZ-305 exams.

What-If & Rollback

What-If Deployments

# Preview changes before deploying
az deployment group what-if \
  --resource-group rg-app-prod \
  --template-file main.bicep \
  --parameters @params.prod.json

What-if shows: Create, Delete, Modify, NoChange, Ignore. Use it in CI/CD as a mandatory gate before production deployments.

Rollback Strategies

  • Redeploy previous version — IaC is idempotent; just redeploy the last-known-good commit
  • Deployment Stacks — automatic cleanup when you roll back template changes
  • Feature flags in parameters — toggle features without changing templates
  • Blue-green at infra level — deploy new resource group, switch traffic, delete old

Multi-Environment Patterns

Parameter Files per Environment

├── main.bicep              # Shared template
├── modules/                # Reusable modules
├── params.dev.json         # Dev: small SKUs, single instance
├── params.staging.json     # Staging: prod-like but fewer replicas
└── params.prod.json        # Prod: full scale, HA, geo-redundancy

CI/CD Pipeline Pattern

IaC deployment pipeline: code → validate → preview → approve → deploy → verify

Real-World: Platform Team IaC Governance at CloudScale Inc.

Scenario: Platform team serving 20 application teams. They need: consistent infrastructure patterns, security guardrails, and team autonomy for app-specific resources.

Architecture

  • Module registry (ACR): Platform team publishes approved modules (vnet, aks-cluster, sql-server, storage-secure) with semantic versioning
  • App teams compose their main.bicep from registry modules — can't use raw resource definitions for governed resources
  • Deployment Stacks at subscription scope: Platform team manages landing zone resources with denyDelete — app teams can't accidentally remove shared infra
  • CI/CD gates: All PRs run bicep lint + what-if; prod deployments require platform team approval if the diff touches networking or identity
  • Template specs for self-service: App teams can "Deploy a new microservice" from a catalog that provisions RG + AKS namespace + Key Vault + monitoring

Result

Time-to-provision for a new service dropped from 2 weeks to 45 minutes. Zero drift incidents in production (Deployment Stacks + deny settings). App teams have autonomy within guardrails.

Knowledge Check

1. Your organization is Azure-only and wants the simplest IaC approach with no state file to manage. Which tool?

2. You need to deploy Azure Policy assignments across all subscriptions in a management group. What deployment scope do you use?

3. A platform team wants to prevent app teams from deleting shared networking resources deployed via IaC. What should they use?

4. What is the primary advantage of a Bicep module registry over local module files?

5. In a CI/CD pipeline, what should run BEFORE the approval gate for a production deployment?

Key Takeaways

  • IaC is governance infrastructure — it enables review, audit, and blast radius control
  • Bicep for Azure-only; Terraform for multi-cloud. Never write raw ARM JSON.
  • Deploy at the right scope: platform team owns MG/sub, app teams own RG
  • Module registries + semantic versioning = enterprise IaC at scale
  • Deployment Stacks solve drift and orphan cleanup — the future of Azure IaC