· Charlie Holland · DevOps · 9 min read
Terraform Won. Get Over It.
The Infrastructure as Code wars are over. Terraform won. And yet enterprise teams are still clinging to vendor-native tooling like ARM templates and CloudFormation because 'it's supported by the vendor.' Supported doesn't mean good.
I’ve just finished migrating a global pharma company from Azure ARM templates to Terraform. Hundreds of resources. Dozens of environments. Years of accumulated JSON that nobody wanted to touch because the last person who understood it left eighteen months ago.
The migration took weeks. It should have happened years ago.
By the end of 2021, the Infrastructure as Code wars are effectively over. Terraform won. Not because it’s perfect — it isn’t — but because it’s the least bad option by a considerable margin, and in enterprise technology, “least bad” is how winners are decided.
And yet I keep walking into organisations where the platform team is still writing ARM templates, or CloudFormation stacks, or — God help us — clicking through the Azure portal and calling it “infrastructure management.” The reasons are always the same, and they’re always rubbish.
The case against vendor-native tooling
Let me be specific about what I mean when I say ARM templates are a nightmare, because I want this on the record.
ARM templates: a eulogy
Azure Resource Manager templates are JSON. Not “JSON-like” or “JSON-inspired” — actual, literal JSON. A declarative infrastructure language with no comments, no variables (well, not real ones), no loops, no conditionals worth the name, and error messages that read like they were generated by a random word generator.
Here’s what a simple storage account looks like in ARM:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-02-01",
"name": "mystorageaccount",
"location": "[resourceGroup().location]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2"
}
]
}And here’s the same thing in Terraform:
resource "azurerm_storage_account" "example" {
name = "mystorageaccount"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = "Standard"
account_replication_type = "LRS"
}One of these is readable by a human. The other is ARM.
ARM’s “template functions” are a special kind of awful. Want to concatenate two strings? "[concat(parameters('prefix'), '-', parameters('suffix'))]". Want a conditional? "[if(equals(parameters('environment'), 'prod'), 'Premium_LRS', 'Standard_LRS')]". Nested inside JSON strings. No syntax highlighting. No IDE support worth mentioning. Debugging means deploying and reading the Azure activity log, which is about as much fun as it sounds.
Microsoft eventually noticed this was rubbish and created Bicep — a domain-specific language that compiles down to ARM templates. Bicep is genuinely better. It has proper syntax, modules, and it’s readable. But it arrived far too late. By the time Bicep was production-ready, most teams that cared about IaC had already moved to Terraform. And Bicep still only works with Azure. If you’re multi-cloud — or might be someday — it’s a dead end.
CloudFormation: marginally better, still locked in
AWS CloudFormation is the same idea executed slightly better. YAML instead of JSON (a low bar, but it clears it). Better documentation. A larger ecosystem. AWS CDK lets you write infrastructure in real programming languages, which is a genuine improvement.
But it’s still CloudFormation under the hood, with all the limitations that implies: vendor lock-in, glacial update cycles, stack drift that’s painful to detect, and rollback behaviour that routinely makes bad situations worse. I’ve watched a CloudFormation rollback delete a production database because someone fat-fingered a parameter. The stack was “protecting” itself by reverting to the previous state, which didn’t include the database. Thanks, CloudFormation.
And again — it only works with AWS. The moment you need to manage a DNS record in Cloudflare, a repository in GitHub, or a cluster in Datadog, you’re out of luck. You need another tool. Which means your team needs to know two tools. Which means your infrastructure is split across two systems with no shared state.
Why Terraform won
Terraform didn’t win because HashiCorp had better marketing (although they did). It won because it solved real problems that vendor-native tools ignored.
One language, every provider
The Terraform Registry has providers for over 3,000 services. AWS, Azure, GCP, Kubernetes, GitHub, Datadog, PagerDuty, Cloudflare, Okta — the list is absurd. You write HCL once, and you can manage your entire technology estate from a single codebase.
This isn’t theoretical multi-cloud nonsense. I’m not suggesting you run the same workload on three cloud providers simultaneously. That’s almost always a terrible idea. But every organisation I’ve worked with has infrastructure that spans multiple providers. Your compute might be on AWS, but your DNS is on Cloudflare, your monitoring is on Datadog, your source control is on GitHub, and your identity is in Azure AD. Terraform manages all of it.
State management that works
Terraform’s state file is one of those things that seems terrifying until you understand it, and then it’s genuinely elegant. It’s a mapping between your configuration and the real world. It knows what exists, what’s changed, and what needs to happen to make reality match your code.
Yes, state management requires discipline. You need remote state backends (S3, Azure Blob, GCS). You need state locking. You need to think about state file boundaries. But once you’ve set it up, terraform plan gives you a diff of exactly what will change before you apply it. That’s something ARM templates and CloudFormation never managed — the “what will this actually do?” question was always answered with “deploy it and find out.”
The module ecosystem
Terraform modules let you package and reuse infrastructure patterns. The terraform-aws-modules organisation alone has production-ready modules for VPCs, EKS clusters, RDS instances, Lambda functions — essentially everything you’d need to build on AWS. The Azure equivalent exists too. These aren’t toy examples; they’re battle-tested by thousands of organisations.
Compare this with ARM template libraries (sparse, poorly maintained) or CloudFormation modules (which AWS calls “nested stacks” — a phrase that should come with a health warning).
The plan/apply workflow
This is the killer feature that nobody talks about enough. terraform plan shows you exactly what will happen. terraform apply makes it happen. The feedback loop is tight, the output is readable, and you can review changes before they’re applied.
In a pull request workflow, this means your infrastructure changes get the same review process as your application code. The plan output goes in the PR comments. The team reviews it. Someone approves it. The apply runs. This is how infrastructure should work, and Terraform made it normal.
”But we’re an Azure/AWS shop”
This is the most common objection, and it’s the weakest. “We’re an Azure shop, so we should use Azure tools.” By that logic, you should also use Azure DevOps for source control (don’t), Azure Boards for project management (please don’t), and Bing for search (I rest my case).
The best tool for a job is the best tool for the job. Your cloud vendor has a financial incentive to keep you within their ecosystem. That incentive is not aligned with your engineering team’s productivity or your organisation’s long-term flexibility.
I’ve heard the “supported by the vendor” argument dozens of times. Here’s what “supported by the vendor” actually means: there’s a documentation page that was last updated eight months ago, a support ticket process that takes three days to get a first response, and a community forum where your question has two replies, both from bots.
Terraform has over 36,000 stars on GitHub, a community that actually answers questions, and a provider ecosystem maintained by the people who build the services. The AWS provider alone has hundreds of contributors. That’s real support, not vendor support theatre.
What about Pulumi?
Pulumi deserves a mention because it’s genuinely interesting. Write your infrastructure in Python, TypeScript, Go, or C# instead of HCL. Real programming languages with real type systems, real testing frameworks, and real IDE support.
The idea is sound. If you’re a developer-heavy team that doesn’t want to learn HCL, Pulumi is compelling. I’ve used it on a couple of projects and it’s well-made.
But it never got traction outside of teams that were already developer-first. Operations teams — the people who traditionally own infrastructure — don’t want to write TypeScript. They want a declarative language that describes the desired state without procedural logic. HCL hits that sweet spot. It’s not a programming language, and that’s a feature.
Pulumi’s adoption numbers tell the story. It’s a niche tool for developer-heavy organisations. Terraform is the industry default. Sometimes the market picks the pragmatic option over the technically superior one, and that’s what happened here.
The elephant in the room: HashiCorp’s licensing
I’d be dishonest if I didn’t mention this. In August 2023, HashiCorp switched Terraform from the Mozilla Public License to the Business Source License (BSL). The community response was swift and predictable: the OpenTofu fork was announced within weeks, backed by a coalition of companies who didn’t fancy having their infrastructure tooling controlled by a single vendor’s commercial interests.
There’s an irony here that’s hard to miss. The entire thesis of this post is “don’t let your vendor lock you into their tools.” And now the tool I’m recommending has a vendor that changed the rules of the game after everyone was already committed. HashiCorp got acquired by IBM in 2024 for $6.4 billion, which should give you some idea of how valuable that lock-in is.
The BSL change is unlikely to affect most users — it restricts competitive products, not end users. But it’s a reminder that “open source” and “open source until the VC wants a return” are different things. OpenTofu exists as insurance. Whether it becomes the new default depends on how IBM plays its hand.
The real lesson
The Infrastructure as Code wars taught me something I keep relearning: the best tool is rarely the vendor’s tool. Vendors optimise for their ecosystem, their billing model, their roadmap. Your team needs to optimise for productivity, maintainability, and the ability to change direction when the business requires it.
ARM templates were never good. They were convenient — available by default, documented (badly) by Microsoft, and endorsed by the Azure sales team. Convenience is not a technical argument. “It’s already there” is not an architecture decision.
Terraform won because it solved the real problem: managing infrastructure as code, across providers, with a workflow that fits how engineering teams actually work. The language is approachable. The ecosystem is vast. The community is active. The plan/apply workflow is the best in the industry.
If you’re still writing ARM templates in 2021, stop. If you’re still writing CloudFormation without CDK, at least look at what you’re missing. And if you’re starting a greenfield project on any cloud, start with Terraform.
The wars are over. Time to get on with the actual work.
