Two years ago, picking an Infrastructure as Code tool was straightforward. You used Terraform. Maybe you grumbled about HCL syntax, maybe you wished the state management was less painful, but Terraform was the default and everybody knew it.
Then HashiCorp switched to the Business Source License in August 2023, IBM bought HashiCorp for $6.4 billion in early 2025, and OpenTofu forked the project under the Linux Foundation. Meanwhile, Pulumi kept quietly building a genuine alternative using real programming languages instead of a domain-specific one.
The IaC market isn’t just competitive now — it’s fractured. And if you’re a platform team lead trying to make a decision that’ll stick for the next three to five years, the stakes feel higher than they should for what is, fundamentally, a tool that creates cloud resources.
I’ve been running all three in production across different projects. Here’s where each one actually makes sense — and where each one will make your life harder.
The Licensing Situation Actually Matters Now
Licensing kicked off this whole reassessment, so it’s worth addressing first.
Terraform moved to BSL 1.1 in August 2023. If you’re using Terraform internally to manage your own infrastructure — running it locally, in CI/CD pipelines, provisioning your own cloud resources — nothing changed for you. The BSL doesn’t restrict that usage. You can keep doing exactly what you’ve been doing.
Where BSL bites is if you’re building a product or service that competes with HashiCorp. If you’re offering Terraform as a managed service, embedding it into a SaaS platform, or reselling it — you need a commercial license. That’s what killed the ecosystem of third-party Terraform Cloud competitors and pushed companies like Spacelift and env0 to add OpenTofu support.
Now here’s the part that keeps platform engineers up at night: IBM completed the HashiCorp acquisition in February 2025. HashiCorp operates as a division of IBM Software, not under Red Hat (IBM’s open-source arm). That’s a signal. The BSL stance is more likely to harden under IBM than soften. If your organization’s legal team gets nervous about license risk, that nervousness isn’t going away.
OpenTofu is MPL 2.0 — genuinely open source, governed by the Linux Foundation, with over 140 corporate backers. The license can’t change on a whim because no single company controls it. That’s the whole point.
Pulumi uses Apache 2.0 for the engine and SDK. Pulumi Cloud (their managed service) is proprietary, but the core tool is open source. It’s a clean split — you can self-host everything if you want to.
For most teams using Terraform internally, the license change is a non-issue today. But “today” is doing a lot of work in that sentence. If you’re making a five-year tooling decision, the governance model matters.
HCL vs Real Programming Languages: The Core Trade-off
This is where Terraform/OpenTofu and Pulumi fundamentally diverge, and it’s the decision that affects your team every single day — not licensing, not pricing, not governance.
Terraform and OpenTofu both use HCL (HashiCorp Configuration Language). It’s declarative, purpose-built, and intentionally limited. You describe what you want, not how to get there. A basic AWS EC2 instance looks like this:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
}
}HCL’s strength is its constraints. There are no classes, no inheritance, no design patterns to argue about. A junior DevOps engineer can read a Terraform config written by a senior one and understand what it does. That readability isn’t accidental — it’s the design goal.
But those constraints become painful fast. Try writing conditional logic in HCL. Try iterating over a complex data structure. Try reusing a module that needs slightly different behavior in staging versus production. You end up with count hacks, ternary expressions nested three levels deep, and for_each gymnastics that would make a Perl golfer wince.
Pulumi says: just use a programming language you already know. TypeScript, Python, Go, Java, C#. The same EC2 instance in Pulumi with TypeScript:
const server = new aws.ec2.Instance("web", {
ami: "ami-0c55b159cbfafe1f0",
instanceType: "t3.micro",
tags: { Name: "web-server" },
});Need conditional logic? Use an if statement. Need to loop over a list? Use map. Need to share code between projects? Publish an npm package. Need to test your infrastructure? Use Jest or pytest. All the tools you already have.
Here’s my honest take: if your team is mostly infrastructure engineers who live in YAML and HCL all day, Pulumi’s “just use TypeScript” pitch can actually backfire. These folks don’t want to set up a Node.js project with tsconfig.json and package.json just to create some EC2 instances. HCL’s simplicity is a feature for them, not a limitation.
But if your team is mostly software engineers who got drafted into DevOps — and that’s increasingly common — Pulumi feels like coming home. You get IDE autocomplete, type checking, refactoring tools, and a testing story that actually works. You stop context-switching between “programming” and “infrastructure” because it’s all just code.
Where OpenTofu Actually Diverges from Terraform
OpenTofu started as a direct fork of Terraform 1.5.x, and in the early days the pitch was simple: “It’s Terraform, but open source.” That’s still true, but OpenTofu has been shipping features that Terraform doesn’t have, and the gap is widening.
State encryption is the headline feature. OpenTofu lets you encrypt your state file at rest using AES-GCM before it’s written to any backend — S3, Azure Blob, GCS, wherever. You can manage keys through AWS KMS, GCP KMS, or OpenBao, or use a PBKDF2 passphrase. The state is encrypted before it leaves your machine, so even if someone gains access to your S3 bucket, they can’t read sensitive values like database passwords or API keys that Terraform cheerfully stores in plaintext.
Terraform’s answer to this has always been “use backend-level encryption” — turn on S3 server-side encryption, use Azure Storage encryption, etc. That works, but it means anyone with bucket access can read your state. OpenTofu’s approach is defense in depth, and for teams in regulated industries, it’s often a compliance requirement that backend encryption alone doesn’t satisfy.
Provider-defined functions let provider authors ship custom functions that you can call in your HCL. This might sound minor, but it opens up a lot of flexibility that previously required external data sources or provisioners.
Dynamic provider configuration with for_each on provider blocks lets you iterate over providers. If you’re managing resources across multiple AWS accounts or regions, this eliminates a huge amount of boilerplate.
The current stable version is OpenTofu 1.11.x (supported through August 2026). Migration from Terraform is technically trivial — OpenTofu reads Terraform state files and uses the same HCL syntax. I’ve migrated projects where the only change was swapping terraform for tofu in the CI pipeline. Zero code changes.
One critical caveat: state migration is forward-only. Once you run tofu apply with OpenTofu 1.7+, the state file may include metadata that Terraform can’t read. You can go from Terraform to OpenTofu easily. Going back is painful. Test in a non-production environment first and make sure you’re committed.
Provider Ecosystem: Still Terraform’s Moat
Terraform’s provider ecosystem is massive — over 4,800 providers in the registry, with the major cloud providers (AWS, Azure, GCP) maintaining official providers that get same-day support for new services. This is Terraform’s real competitive advantage, and it’s one that neither OpenTofu nor Pulumi has fully matched.
OpenTofu inherits Terraform’s provider ecosystem because providers are compatible between the two. Any Terraform provider works with OpenTofu without modification. This is a huge advantage of the fork approach — OpenTofu got the entire ecosystem for free. The OpenTofu registry mirrors Terraform’s providers, and community providers continue to work with both.
Pulumi takes a different approach. It has its own native providers for major clouds, but it also has a bridge that wraps Terraform providers so you can use them from Pulumi. As of 2026, Pulumi lists around 1,800+ providers, though the number is growing. The bridge works well for most cases, but occasionally you’ll hit edge cases where a Terraform provider feature doesn’t translate cleanly through the bridge layer.
In practice, if you’re working with AWS, Azure, GCP, or Kubernetes — the providers are solid across all three tools. Where you’ll feel the ecosystem difference is with niche providers. Need to manage resources in a smaller SaaS platform that only has a Terraform provider? With Terraform or OpenTofu, you just use it. With Pulumi, you check if there’s a native provider first, then fall back to the bridge, and hope it works.
State Management: The Part Nobody Enjoys
State management is the achilles heel of all three tools, though they handle it differently.
Terraform and OpenTofu both use state files that track the mapping between your configuration and real cloud resources. You need a backend to store this state — typically S3 with DynamoDB locking for AWS shops. State files contain sensitive data in plaintext (unless you’re using OpenTofu’s encryption), and state conflicts in team environments are a recurring headache.
HCP Terraform (formerly Terraform Cloud) solves the operational burden by managing state for you. The enhanced free tier gives you 500 managed resources with unlimited users, which is generous for small teams. Beyond that, pricing starts at $0.10/resource/month for Essentials, $0.47 for Standard, and $0.99 for Premium. For a team managing 2,000 resources on the Standard plan, that’s roughly $940/month. Not trivial.
Pulumi Cloud handles state management too, and includes it in the free tier for individual use. The Team plan charges $0.37/resource/month. But here’s the thing Pulumi doesn’t advertise loudly: you can use self-managed backends. Store your Pulumi state in S3, Azure Blob, or a local file, and you don’t need Pulumi Cloud at all. You lose the nice dashboard and collaboration features, but you keep full control.
OpenTofu uses the same backend options as Terraform — S3, Azure, GCS, Consul, whatever. No managed service, no cloud dashboard. You’re on your own for state management, but you’re also not paying anyone a per-resource fee. For teams that already have the operational maturity to manage remote state backends, this is a non-issue. For teams that don’t, it’s a real gap.
Testing Infrastructure Code: Pulumi’s Clear Win
Pulumi has a clear advantage here.
Because Pulumi uses general-purpose programming languages, you get access to real testing frameworks. Write unit tests with Jest that mock cloud provider calls and verify your infrastructure logic in milliseconds. Write property tests that check resource configurations during deployment. Write integration tests that spin up ephemeral infrastructure and validate it end-to-end.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
test("Security group should not allow SSH from 0.0.0.0/0", async () => {
const sg = new aws.ec2.SecurityGroup("test-sg", {
ingress: [{
protocol: "tcp",
fromPort: 22,
toPort: 22,
cidrBlocks: ["10.0.0.0/8"],
}],
});
const ingress = await sg.ingress;
ingress?.forEach(rule => {
expect(rule.cidrBlocks).not.toContain("0.0.0.0/0");
});
});That’s a real unit test. It runs in under a second. No cloud resources are created. Compare that to testing a Terraform module, where your options are: terraform validate (syntax only), terraform plan (needs cloud credentials and takes minutes), or Terratest (spins up real infrastructure and can take 20+ minutes per test run).
OpenTofu added a test command that lets you write assertions in HCL, which is a step forward from Terraform’s testing story. But asserting things in HCL feels like writing poems in Morse code — technically possible, not exactly expressive.
If your team cares about testing infrastructure the same way you test application code — fast feedback, CI integration, good coverage — Pulumi is the only one that doesn’t require compromises.
CI/CD Integration: All Three Work, Different Flavors
All three tools integrate with GitHub Actions, GitLab CI, Jenkins, CircleCI, and the rest. The differences are in how much operational glue you need to write.
Terraform has the deepest CI/CD ecosystem simply because it’s been around longest. There are official GitHub Actions, pre-built GitLab templates, and a dozen third-party tools (Atlantis, Spacelift, env0, Terramate) that add pull request workflows, plan previews, and policy enforcement. If you want a terraform plan comment on every PR, there are five different ways to set it up.
OpenTofu works with most of the same CI/CD tooling. Atlantis added OpenTofu support early on. Spacelift, env0, and Scalr all support OpenTofu alongside Terraform. You’ll occasionally find a tool that hasn’t added OpenTofu support yet, but those cases are getting rare.
Pulumi has its own CI/CD story through Pulumi Deployments, which runs pulumi up in Pulumi Cloud’s managed infrastructure. It’s slick if you’re already using Pulumi Cloud, but if you’re self-hosting Pulumi state, you’ll set up your own CI/CD pipeline the same way you would with Terraform — run the CLI, capture the output, handle credentials.
The Migration Question: Can You Actually Switch?
From Terraform to OpenTofu: yes, and it’s the easiest migration you’ll ever do. Same HCL, same providers, same state format (with the forward-only caveat I mentioned). Rename terraform to tofu in your scripts, update your CI/CD config, done. I’ve done this on a project with 1,200+ resources and it took an afternoon, most of which was updating pipeline configs and running tofu plan to confirm zero changes.
From Terraform to Pulumi: this is a real migration. Pulumi has a pulumi convert command that translates HCL to your target language, and it handles maybe 80% of typical configs correctly. The remaining 20% — complex modules, dynamic blocks, edge-case provider configurations — needs manual translation. For a medium-sized project (200-500 resources), budget a few weeks. For large projects, it’s a quarter.
The hidden cost of the Terraform-to-Pulumi migration isn’t the code conversion — it’s retraining your team. HCL engineers don’t automatically become TypeScript engineers. You’re not just changing a tool; you’re changing how your team thinks about infrastructure. That cultural shift takes months, not weeks.
From Pulumi to Terraform/OpenTofu: Pulumi has an export-to-HCL feature, but going from general-purpose code back to HCL means losing all the abstractions, loops, and conditionals you built. The generated HCL is verbose and needs significant cleanup. I wouldn’t recommend this direction unless you have a strong reason.
Performance at Scale
This matters more than people think, especially as your infrastructure grows.
Terraform and OpenTofu have similar performance characteristics since they share the same core engine. Plan and apply times scale linearly with the number of resources. For large states (5,000+ resources), a plan can take 5-10 minutes as it refreshes the state of every resource. The common mitigation is splitting infrastructure into smaller state files (workspaces, separate root modules), which adds operational complexity.
Pulumi 4.0 introduced Incremental State Processing, which claims 60% faster deployment times for large-scale infrastructures. Instead of refreshing every resource on every run, it only processes what’s changed. In my testing, the improvement is real for applies but less dramatic for the initial plan phase. Still, if you’re managing 1,000+ resources in a single project, Pulumi is noticeably faster on day-to-day operations.
So Which One Should You Pick?
I’m not going to give you a flowchart. Your context matters more than any generic recommendation. But here’s how I think about it.
Stick with Terraform if your team already uses it, you’re invested in HCP Terraform or Terraform Enterprise, and the BSL license doesn’t affect your use case (it probably doesn’t if you’re using it internally). The ecosystem is the largest, the hiring pool is the deepest, and “nobody got fired for choosing Terraform” still applies. Just know that you’re betting on IBM’s stewardship being good, and keep an eye on how that plays out.
Switch to OpenTofu if you want Terraform without the licensing risk. If your organization’s legal or procurement team is uncomfortable with BSL, or if you’re building a platform that might hit BSL restrictions someday, OpenTofu removes that variable entirely. The migration is trivial, state encryption is a genuine differentiator, and the Linux Foundation governance gives you long-term confidence. According to recent surveys, about 38% of Terraform users are already evaluating or migrating to OpenTofu — you won’t be alone.
Choose Pulumi if your team is primarily software engineers, you care deeply about testing infrastructure code, or your infrastructure needs complex logic that HCL handles awkwardly. Pulumi is the best tool for teams that want infrastructure to be real software engineering — version controlled, tested, reviewed, and refactored with the same tools and practices they use for application code. The trade-off is a smaller ecosystem and a steeper migration path if you’re coming from Terraform.
One more thing. Whatever you choose, don’t treat this as a permanent, all-or-nothing decision. I know teams that use OpenTofu for core infrastructure and Pulumi for the complex application-level stuff where they need programming language flexibility. The tools don’t have to be mutually exclusive, and mixing them is sometimes the pragmatic answer.
Check the official docs for current pricing — it changes frequently enough that any numbers I quote here have a shelf life. What won’t change is the fundamental trade-offs: HCL simplicity versus programming language power, managed services versus self-hosted control, and the governance question of who gets to decide the future of the tool you’re building on.