If you're building infrastructure on Azure today, you'll hit this question early: Terraform or ARM Templates (and its successor, Bicep)? Both are valid. The right answer depends on your team's context, multi-cloud ambitions, and how much you value state management.
Here's how I think about the decision after using both extensively in enterprise environments.
What They Have in Common
Both let you declare the desired state of your Azure infrastructure as code, check it into source control, and apply changes through a pipeline. Both are mature and production-ready. Both integrate well with Azure DevOps and GitHub Actions.
The differences are in the details. And those details matter a lot at scale.
ARM Templates and Bicep
ARM Templates are the native Azure IaC format - JSON documents that map directly to Azure Resource Manager's API. Bicep is Microsoft's domain-specific language that compiles down to ARM JSON, with much cleaner syntax.
What they do well:
- Perfect fidelity with Azure - every new Azure feature is available in ARM/Bicep on day one
- No state file to manage - Azure itself is the source of truth
- Excellent Azure Portal integration (template specs, deployment stacks)
- Strong typing and IntelliSense in VS Code via the Bicep extension
Example Bicep:
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
}
}
Where they fall short:
- Azure-only — you can't reuse anything across AWS or GCP
- Complex loops and conditionals get verbose fast
what-ifis useful, but it's not as battle-tested asterraform planwhen dependency graphs get complicated
Terraform
Terraform uses HCL (HashiCorp Configuration Language) and maintains a state file that tracks what it has deployed. It supports hundreds of providers, making it the natural choice for any infrastructure that spans more than one platform.
What it does well:
- Multi-cloud and multi-service — manage Azure, DNS, GitHub, Datadog, and Vault in the same codebase
terraform plangives an exact diff of what will change. That makes PR reviews for infrastructure actually useful.- Rich module ecosystem - the Terraform Registry has production-ready Azure modules
- Mature tooling - Terragrunt, Atlantis, Spacelift all build on it
Example Terraform:
resource "azurerm_storage_account" "main" {
name = var.storage_account_name
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "LRS"
min_tls_version = "TLS1_2"
https_traffic_only_enabled = true
}
Where it falls short:
- State file is a liability - it must be stored securely (Azure Blob + state locking) and can drift
- New Azure features lag by days or weeks until the provider is updated
- Requires Terraform Cloud, a backend, or self-managed state for teams
Decision Framework
| Factor | Use ARM/Bicep | Use Terraform |
|---|---|---|
| Azure-only infrastructure | ✓ | ✓ |
| Multi-cloud or multi-provider | ✓ | |
| New Azure preview features | ✓ | |
| Complex module reuse | ✓ | |
| Team knows HCL | ✓ | |
| No state management overhead | ✓ | |
| Enterprise policy enforcement | ✓ (template specs) | ✓ (Sentinel) |
What I Use in Practice
For pure Azure landing zone work - management groups, policies, subscriptions, hub networking - I use Terraform. The plan output makes change reviews in PRs much easier, and the multi-provider support means I can manage the Azure infrastructure alongside Datadog monitors and GitHub repository configuration in a single pipeline run.
For application teams who only need to deploy their service's Azure resources and want to stay close to the Azure API surface, Bicep is often the better fit. The learning curve is lower, there's no state to manage, and new resource types are available immediately.
The real mistake is treating this as a binary choice. Bicep and Terraform coexist fine. Use each where it fits. Forcing a single tool across every context is how you end up with the wrong tool in too many places.
