Three Terraform posts deserve a counterweight, because the other serious answer for Azure IaC is Bicep, and the decision between them is about operating models more than syntax. Today: what Bicep actually is, its distinctive strengths, and an honest decision framework.
What Bicep Is
Bicep is a language that compiles to ARM templates, which means deployments execute inside Azure Resource Manager itself: no state file, because ARM compares the template against reality at deployment time; day zero support for new Azure features, because the resource API is the source of truth; and what if as the plan equivalent, showing creates, modifies, and deletes before you commit. The language is genuinely pleasant, strong typing against resource schemas with editor completion that knows every property, user defined types and functions for shaping clean module interfaces, and none of the HCL ceremony around count versus for_each. Modules are Bicep files calling Bicep files, shareable through the public module registry (the Azure Verified Modules initiative publishes Bicep alongside Terraform) or a private registry in your ACR from the September post.
@description('Environment name used in resource naming.')
@allowed(['dev', 'test', 'prod'])
param environment string
param location string = resourceGroup().location
module storage 'br/contoso:storage-account:2.1.0' = {
name: 'storage'
params: {
name: 'stords${environment}weu'
location: location
zoneRedundant: environment == 'prod'
}
}
output storageId string = storage.outputs.id
The no state property deserves its honest footnote: ARM’s default deployment mode is incremental, meaning resources removed from the template are not removed from Azure, the classic surprise for Terraform natives expecting destroy on delete. Deployment stacks close exactly that gap, tomorrow’s post, so hold the thought.
The Decision Framework
Choose Bicep when the estate is Azure only and the team is Azure native: the toolchain is lighter (no state backend to secure, no provider version matrix), new Azure capabilities arrive without waiting for provider releases, and Microsoft’s own reference architectures and support paths speak it fluently. Choose Terraform when any of these hold: the estate spans clouds or SaaS providers (the provider ecosystem is the moat, one workflow for Azure, GitHub, Datadog, and Cloudflare), the organization already runs Terraform at scale with the module, testing, and pipeline investment from this week, or you depend on the plan file as a reviewable artifact and the richer refactoring vocabulary (moved, import, removed) for brownfield work. The wrong reasons to switch either way: syntax preference, a single missing feature that ships next quarter, or resume alignment. And the coexistence answer is legitimate: plenty of estates run Terraform for the multi cloud landing zone and Bicep inside Azure centric application teams, with the boundary drawn at subscription handoff, exactly where the vending pattern from the first series already draws it.
If You Adopt It
Bring the whole discipline across, none of it is Terraform specific: modules with designed interfaces and semantic versions in the registry, linting (bicep lint plus PSRule for Azure for the security baseline), what if posted to pull requests exactly like plans, the test framework for assertions, OIDC federated pipeline identities, and drift managed by redeploying the template on schedule since deployment is idempotent. Deployment scopes are a genuine superpower to use deliberately: the same language deploys resource groups, subscriptions, management groups, and tenant scope resources, so the policy assignments and management group hierarchy from the landing zone posts express naturally. The IaC values this week established, reviewable diffs, tested modules, no manual changes, are tool independent; Bicep honors all of them.
Cheers
Osama
Leave a comment