A critical examination of Infrastructure as Code tools in 2025, with Terraform's dominance facing serious competition from Pulumi, CloudFormation, and emerging alternatives.
Julian Morley
Infrastructure as Code (IaC) has become the backbone of how we manage cloud infrastructure these days. But here's the thing—even after a decade of evolution, the IaC world is still kind of a mess. Everyone's got their favorite tool, everyone swears theirs is the best, and nobody can agree on anything. And sitting right in the middle of all this chaos is Terraform—HashiCorp's infrastructure tool that's basically become synonymous with IaC itself.
But is Terraform actually the best? I'm not so sure anymore.
I've spent years building infrastructure for all kinds of companies, and I've used pretty much every major IaC tool out there in real production environments. I've celebrated their wins, cursed their limitations, and felt the very specific pain each one brings. What I've learned is that the "right" tool really depends on what you're trying to do—but more importantly, I think we need to be more honest about Terraform's problems instead of just singing its praises all the time.
Before comparing tools, let's establish why IaC matters in the first place.
I still remember the "bad old days"—and honestly, they weren't even that long ago. Setting up infrastructure meant:
This approach was a nightmare. Nothing was reproducible. Everything lived in people's heads. And trying to audit who changed what? Forget about it.
Infrastructure as Code changed all this by treating infrastructure like, well, code:
These benefits are so huge that IaC has gone from "nice to have" to "you're crazy if you're not doing this" for any serious engineering team.
But here's the million-dollar question: which tool should you actually use?
Let's talk about the elephant—or gorilla—in the room. Terraform has basically become the default choice for managing infrastructure across multiple clouds, and there are some pretty good reasons for that (HashiCorp, n.d.).
Multi-Cloud from Day One
Terraform's provider system was genuinely revolutionary. Instead of locking you into one cloud vendor, Terraform could manage AWS, Azure, GCP, and hundreds of other services through the same interface (HashiCorp, n.d.). If you're worried about vendor lock-in (and you should be), this is incredibly appealing.
Declarative and Predictable
Terraform's declarative approach lets you describe the desired end state, and Terraform figures out how to get there. The plan command shows you exactly what will change before you apply it—a safety feature that has saved countless production incidents.
Massive Ecosystem
The Terraform Registry contains thousands of providers and modules. Need to manage Datadog monitors? There's a provider. Want to configure GitHub repositories? There's a provider. This ecosystem effect created a virtuous cycle—people chose Terraform because it had providers, which encouraged more provider development.
HashiCorp Configuration Language (HCL)
HCL strikes a balance between readability and functionality. It's not a full programming language, which some view as a limitation, but it's more expressive than JSON or YAML while remaining declarative.
In production environments, Terraform excels at:
State Management
Terraform's state file tracks the relationship between your configuration and real infrastructure. This enables accurate drift detection and dependency management. When you use remote state with locking, it prevents concurrent modifications that could corrupt infrastructure.
Dependency Graph
Terraform automatically builds a dependency graph and parallelizes resource creation where possible. This makes provisioning fast and reliable.
Import Existing Resources
You can import existing infrastructure into Terraform state, enabling gradual migration from manual provisioning to IaC.
Okay, here's where I'm going to disagree with the Terraform fanboys.
HCL Is a Straight Jacket
Sure, HCL is better than raw JSON. But it's still yet another domain-specific language you have to learn. And when you need to do something even slightly complex? Get ready for nested for_each loops and conditional expressions that'll make your head spin.
I've seen Terraform code that was way harder to understand than the equivalent in Python or TypeScript would've been. HCL's limitations force you into these weird workarounds that just... shouldn't be necessary.
State File Nightmares
The state file is Terraform's biggest weakness, hands down. Lose it, corrupt it, or have it drift from reality, and you're going to have a bad time. I've burned entire days debugging state file issues that feel like they shouldn't even be a thing.
Yes, remote state helps. But now you need locking mechanisms, Azure Storage Blob, S3 buckets, DynamoDB tables, proper permissions... And if two people accidentally work on the same infrastructure at the same time without coordination? Good luck cleaning up that mess.
Provider Quality Varies Wildly
While the major cloud providers maintain excellent Terraform providers, third-party providers range from "excellent" to "abandoned and buggy." I've wasted days working around provider bugs or missing features.
Refactoring is Painful
Renaming resources, moving them between modules, or restructuring your Terraform code often requires state manipulation with terraform state mv. Get it wrong and you might accidentally destroy and recreate production infrastructure.
Testing is an Afterthought
Testing Terraform code is possible but awkward. Tools like Terratest exist, but they're clunky compared to testing frameworks in real programming languages. Most teams skip testing entirely, which is terrifying for production infrastructure.
Terraform isn't the only game in town, and its competitors address some of its fundamental limitations.
Pulumi takes a completely different approach: what if you could just use actual programming languages to define infrastructure? (Pulumi, n.d.)
The Pulumi Pitch
Instead of learning HCL, just write your infrastructure in TypeScript, Python, Go, C#, or Java—languages you probably already know. Use actual for loops, real if statements, classes, functions, and all the tooling those ecosystems already have (Pulumi, n.d.).
Where Pulumi Shines
Actual Programming Languages
Want to loop over something? Just use a for loop. Need conditional logic? Use an if statement. Want reusable components? Write a class or function (Pulumi, n.d.).
// Pulumi TypeScript - this is just... normal code
const buckets = ["dev", "staging", "prod"].map(env =>
new aws.s3.Bucket(`app-${env}`, {
acl: "private",
tags: { Environment: env }
})
);
Compare that to Terraform's for_each gymnastics. The difference is pretty stark.
Superior Testing
Because you're writing in real languages, you can use real testing frameworks. Unit tests, integration tests, property-based tests—all the testing approaches mature ecosystems provide (Pulumi, n.d.).
Better IDE Support
TypeScript in VS Code gives you autocomplete, inline documentation, and type checking. This catches errors before you run anything, unlike Terraform where you only discover problems during plan or apply.
Pulumi Automation API
Pulumi's Automation API lets you embed infrastructure provisioning in applications. Want to build an internal developer platform that provisions infrastructure on-demand? Pulumi makes this straightforward.
Where Pulumi Falls Short
Smaller Ecosystem
Pulumi has fewer providers than Terraform. While it supports all major clouds, niche services might lack Pulumi support. Though Pulumi can use Terraform providers, this defeats the purpose of using Pulumi in the first place.
Steep Learning Curve
Ironically, using real programming languages can be more complex than HCL for simple use cases. You need to understand async/await, promises, and other programming concepts that HCL abstracts away.
State Management (Still Present)
Pulumi still uses a state file (though they call it a checkpoint), so you haven't escaped this fundamental IaC problem. Pulumi Cloud offers hosted state management, but that's a commercial service.
Team Adoption
Try convincing a team that knows Terraform to switch to Pulumi. The network effects and existing Terraform investment make this a hard sell, even if Pulumi is technically superior for your use case.
CloudFormation is AWS's built-in IaC service. If you're all-in on AWS, it's worth a serious look (Amazon Web Services, n.d.).
CloudFormation's Advantages
Native Integration
CloudFormation is baked right into AWS. New AWS features often show up in CloudFormation before any third-party tool supports them. Plus, authentication is dead simple—it just uses your existing IAM permissions.
Service Catalog Integration
CloudFormation works seamlessly with AWS Service Catalog, which lets you build self-service infrastructure with proper governance controls.
No State File Management
Here's the big one: CloudFormation manages state on AWS's servers. You don't have to worry about state files, locking, or keeping things in sync. This alone eliminates a huge category of headaches.
StackSets for Multi-Account Deployment
CloudFormation StackSets let you deploy the same infrastructure across multiple AWS accounts and regions in one go. For enterprise environments, this is incredibly powerful.
CloudFormation's Limitations
AWS Only
This one's obvious: CloudFormation only works with AWS. Need multi-cloud? You'll need a different tool.
YAML/JSON Hell
CloudFormation templates are written in YAML or JSON. For anything complex, these turn into massive, unreadable files. I've seen 3,000-line CloudFormation templates that nobody wants to touch.
Limited Expressiveness
CloudFormation's template language is primitive. Simple loops require intricate nested structures. Conditionals are awkward. Complex logic is nearly impossible.
Slow Updates
CloudFormation updates can be painfully slow. What takes Terraform seconds can take CloudFormation minutes, especially for large stacks.
The CDK Solution
AWS Cloud Development Kit (CDK) addresses CloudFormation's language limitations by generating CloudFormation from TypeScript, Python, or Java. This gives you real programming language benefits while leveraging CloudFormation's native AWS integration.
CDK is essentially AWS's answer to Pulumi, and it's compelling if you're AWS-only.
Ansible isn't strictly an IaC tool—it's a configuration management platform. But it can provision infrastructure, and many organizations use it for both (Red Hat, n.d.).
Ansible's Unique Position
Agentless
Ansible doesn't require agents on target systems—it uses SSH. This simplifies deployment and reduces security surface area.
Configuration + Provisioning
Ansible can provision infrastructure and configure it in the same playbook. For organizations already using Ansible for configuration management, using it for infrastructure makes sense.
Procedural Approach
Unlike Terraform's declarative approach, Ansible is procedural—you define steps to execute. This is more intuitive for some use cases but less elegant for infrastructure management.
Why Ansible Struggles as Pure IaC
Not Idempotent by Design
While Ansible modules can be idempotent, the language doesn't enforce it. You can easily write non-idempotent playbooks that behave unpredictably when run multiple times.
No Dependency Graph
Ansible executes tasks in order, not based on dependencies. You manually handle ordering, which is error-prone for complex infrastructure.
State Management is Awkward
Ansible doesn't maintain state like Terraform. Determining what changed requires complex logic in your playbooks.
Limited Cloud Provider Support
While Ansible has cloud modules, they're less comprehensive than Terraform providers or native cloud tools.
Crossplane takes a radically different approach: manage infrastructure using Kubernetes APIs (Crossplane, n.d.).
The Crossplane Vision
If you're already on Kubernetes, why not manage your infrastructure through Kubernetes too? Crossplane extends Kubernetes to provision and manage cloud resources using custom resource definitions (CRDs) (Crossplane, n.d.).
Where Crossplane Excels
Kubernetes-Native
If your entire ecosystem is Kubernetes, Crossplane provides a consistent interface. Manage infrastructure with kubectl and Kubernetes manifests.
Composition and Abstraction
Crossplane's composition features let you create custom infrastructure APIs. Platform teams can expose simplified interfaces while hiding complexity.
GitOps Integration
Crossplane integrates naturally with GitOps workflows using tools like ArgoCD or Flux.
Where Crossplane Struggles
Kubernetes Requirement
You must run Kubernetes. If you're not already deeply invested in Kubernetes, running it just for infrastructure management is overkill.
Complexity
Crossplane adds significant complexity. You need to understand Kubernetes operators, CRDs, and Crossplane's specific patterns.
Maturity
Crossplane is younger than Terraform or CloudFormation. Provider coverage is improving but still limited (Crossplane, n.d.).
Let me provide a pragmatic comparison based on real-world experience:
If you need to manage infrastructure across AWS, Azure, GCP, and various SaaS providers, Terraform remains the best option. The provider ecosystem is unmatched, and the learning investment pays off across providers.
If you're exclusively on AWS, CloudFormation with CDK gives you native integration, real programming languages, and no state file management. The velocity and reliability gains outweigh multi-cloud portability you don't need.
If your infrastructure provisioning requires complex logic, dynamic resource creation, or tight integration with application code, Pulumi's real programming languages provide capabilities that Terraform's HCL simply can't match.
If you need configuration management and infrastructure provisioning and your infrastructure is relatively simple, Ansible provides a unified tool. But for complex infrastructure, combine it with a dedicated IaC tool.
If you're building an internal platform on Kubernetes and want infrastructure management integrated with your existing Kubernetes workflows, Crossplane's approach makes sense.
Here's what nobody really wants to say out loud: every single one of these tools has some pretty major flaws.
State management is just broken in Terraform and Pulumi. The state file is a single point of failure that creates constant headaches.
CloudFormation's template language is awful, even though the actual service works well. CDK helps, but now you've got another layer of abstraction to deal with.
Ansible wasn't built for infrastructure provisioning, and you can tell. Using it for IaC is like using a screwdriver as a hammer—sure, it works, but it's awkward.
Crossplane makes you run Kubernetes just to manage infrastructure, which feels like massive overkill for what should be a simpler problem.
Pulumi's "use any language" approach sounds great until you realize that real programming languages bring real complexity. Simple infrastructure tasks become complicated code.
Basically, we're all just picking the least-bad option for our specific situation. There's no perfect tool here.
After doing this for years, here's my honest take:
Go with CloudFormation + CDK if you're AWS-only, Terraform if you need multi-cloud.
Startups don't have time for complicated tooling. CloudFormation eliminates the whole state file problem. Terraform gives you flexibility if you need to work with multiple clouds.
Skip Ansible (too procedural), Crossplane (way too complex), and Pulumi (more power than you need).
Use Terraform for infrastructure, Ansible for configuration management.
At this stage, you've got people who can handle Terraform state properly. The huge provider ecosystem starts to really pay off as you integrate with more services. Use Ansible for configuring servers and deploying apps.
Use different tools for different jobs.
Big companies have the resources to support multiple tools. Pick what works best for each team instead of forcing everyone to use the same thing.
The IaC landscape continues evolving. New players emerge, existing tools improve, and patterns shift. We're moving toward:
The worst thing you can do is not pick a tool at all and keep doing everything manually. The second worst thing is picking a tool because it's trendy instead of actually thinking about what you need.
Terraform's popularity is deserved in a lot of ways—it's mature, well-documented, and has a massive ecosystem. But it's not perfect, and honestly, the alternatives are getting really compelling for certain use cases.
My advice:
The "best" IaC tool is whatever solves your problems with the least amount of hassle. Sometimes that's Terraform. Sometimes it's not.
And you know what? That's completely fine.
Amazon Web Services. (n.d.). AWS CloudFormation. https://aws.amazon.com/cloudformation/
Crossplane. (n.d.). The cloud native control plane framework. https://www.crossplane.io/
HashiCorp. (n.d.). Terraform by HashiCorp. https://www.terraform.io/
Pulumi. (n.d.). Pulumi - Infrastructure as Code in Any Programming Language. https://www.pulumi.com/
Red Hat. (n.d.). Red Hat Ansible Automation Platform. https://www.redhat.com/en/technologies/management/ansible
What's your experience with Infrastructure as Code tools? Have you made the switch from one tool to another? I'm interested in hearing about your journey—reach out to share your perspective.
Building Production-Ready Go APIs in Scratch Containers
A practical guide to creating ultra-lightweight, secure Go APIs using Gin, GORM, and MariaDB/TiDB, compiled to static binaries and deployed in scratch containers for maximum efficiency.
SDET: The Unsung Heroes of Enterprise Software Quality
Why Software Development Engineers in Test represent the future of quality assurance, and why enterprises that don't invest in SDET talent are setting themselves up for failure.