Laptop screen displaying analytics dashboard with charts tracking Infrastructure as Code tool adoption and market share trends

Best Infrastructure as Code Tools Comparison

September 26, 2026 · 12 min read · By Thomas A. Anderson

Key Takeaways:

  • Terraform still has the largest share of IaC users, but Firefly’s State of IaC 2025 survey found only 47% of practitioners plan to keep using it, while OpenTofu use is increasing.
  • All three tools can deploy the same VPC, EC2, and RDS stack; the differences appear in state ownership, rollback behavior, and how much logic you can express before the tool resists.
  • CloudFormation limits a single stack to 500 resources and nested stacks to 2,500, and it is the only one of the three with native rollback on a failed update.
  • Pulumi’s DIY backends store state in S3, Azure Blob, GCS, or the local filesystem, and require exactly four S3 permissions to operate.
  • Less than one-third of organizations continuously monitor drift, according to Firefly, which explains why drift is the most common silent failure across all tools.

Terraform, Pulumi, and CloudFormation all deploy the same VPC, EC2, and RDS resources. The main differences are who owns the state file, what happens when an apply fails partway, and how much logic you can express before the tool pushes back. This article updates our February 2026 comparison of these three tools, reflecting changes significant enough to revisit the topic.

What Changed Since February 2026

The biggest change is Terraform’s market position. Firefly’s State of IaC 2025 report shows Terraform still holds the largest share of IaC users, but only 47% of practitioners plan to continue using it. OpenTofu, the open-source fork created after HashiCorp moved Terraform to the Business Source License in 2023, currently has 12% usage with 27% planning to adopt it. The gap between current and intended use is the key figure to watch.

Learning Curve and Team Fit

The adoption situation is more mixed than vendor messaging suggests. The same survey found that 89% of respondents have adopted IaC in some form, but only 6% have full coverage of their cloud estate. Cloud complexity continues to increase for 65% of respondents, and a shortage of skilled engineering resources is the main adoption barrier. Tools are not the limiting factor; people and coverage are.

Drift management has gained importance. Firefly found that fewer than one-third of organizations continuously monitor drift, while the rest respond only after a divergence causes issues. This statistic explains many production incidents teams blame on “Terraform” or “CloudFormation” when the real cause is an unmonitored console change.

The Same VPC + EC2 + RDS Stack, Three Ways

The example stack is intentionally simple: one VPC with a CIDR block, a public subnet, a security group, one EC2 instance, and a single-AZ RDS PostgreSQL instance that is not publicly accessible. All three tools can deploy it. The differences lie in how you define it.

Terraform uses HCL, HashiCorp Configuration Language, to describe each resource. A VPC is an aws_vpc block with a cidr_block argument; the subnet references aws_vpc.main.id, and Terraform infers dependencies from that reference. The StackPractices IaC guide illustrates the pattern: provider version pinned in a required_providers block, resources declared, outputs exported. Control flow is limited to conditionals, loops, and functions. This limitation is intentional but also the first constraint developers encounter.

Pulumi replaces HCL with general-purpose languages. The same VPC is created with a constructor call in TypeScript, Python, or Go: new aws.ec2.Vpc("main", { cidrBlock: "10.0.0.0/16" }). Since this is real code, you get loops, classes, package management, and unit tests that mock the provider and check resource properties. Teams already using TypeScript or Go find this approach natural. Teams whose infrastructure engineers primarily use YAML may find it unfamiliar.

CloudFormation uses a JSON or YAML template with a Resources section and intrinsic functions like !Ref to connect resources. The TechTarget comparison of CloudFormation and Terraform notes that YAML is preferred over JSON because it is less verbose and supports comments, which helps reviewers understand templates months later. CloudFormation also supports importing existing resources into a stack, which is useful for teams adopting IaC on an existing environment.

Aspect Terraform Pulumi CloudFormation
Authoring language HCL TypeScript, Python, Go, C# YAML or JSON
Multi-cloud support Yes, across AWS, Azure, GCP, Oracle, DigitalOcean Yes, via language SDKs AWS only
State ownership You own it (local file or remote backend) You own it or Pulumi Cloud manages it AWS manages it
Native rollback on failure No native rollback No native rollback Keep, update, or roll back on failed create or update
Preview mechanism terraform plan pulumi preview Change sets
Single-stack resource ceiling 500 resources per stack No documented equivalent ceiling 500 per stack, 2,500 with nested stacks

Sources: TechTarget, StackPractices, Pulumi documentation.

State Management: Where the Three Tools Diverge

The design of the state file is the most important difference among these tools.

Terraform requires you to manage state yourself. The StackPractices guide outlines three storage options: a local file for solo development, S3 with a DynamoDB lock table for team workflows, and Terraform Cloud for collaboration with remote execution and policy checks. The remote backend block specifies the bucket, key, region, DynamoDB table, and encrypt = true. Without a remote backend, two engineers running apply simultaneously can corrupt the state file, and recovery requires manual work. The guide’s advice is clear: never edit a state file by hand; use terraform state commands or fix the configuration and re-apply.

Pulumi offers a choice. Its DIY backend documentation explains storing state in S3, Azure Blob Storage, Google Cloud Storage, or the local filesystem by passing the appropriate URL to pulumi login. State files are stored under a .pulumi prefix with meta.yaml metadata, per-stack state files, lock files, and a history directory. A basic file-based locking system is enabled by default. The documentation states that DIY backends require you to implement your own backup procedures and access control, while Pulumi Cloud provides a transactional API with stronger guarantees than blob storage protocols. For an S3 backend, Pulumi requires exactly four actions: ListBucket, GetObject, PutObject, and DeleteObject. This defines a clear least-privilege boundary for security reviewers.

CloudFormation removes the need to manage state. AWS manages stack state, and you never see a state file. This avoids problems like corruption and lock-contention failures that are common complaints about Terraform. However, it also means you cannot inspect or edit state directly, which matters when a resource becomes orphaned outside the stack.

One practical note for teams using both: Pulumi supports storing Terraform state in Pulumi Cloud, providing a migration path rather than a hard cutover. The StackPractices guide describes a similar gradual approach in reverse, managing new resources with Pulumi while Terraform handles legacy resources, using state references or Pulumi’s Terraform bridge.

Deployment Workflows and Failure Handling

All three tools integrate with CI/CD pipelines in GitHub, GitLab, or Bitbucket, and all support triggering from AWS CodePipeline or CodeBuild. The TechTarget comparison rates their pipeline integration as roughly equal. The main difference is how they handle failures.

CloudFormation is the only one with native rollback. On a failed stack create or update, you can choose to keep, update, or roll back the resources. Terraform has no native rollback; if an apply fails partway, you fix the error and run apply again, or explicitly remove the affected resources. Pulumi behaves the same way. For regulated environments where unattended pipelines must not leave half-built stacks, this difference matters.

Preview quality is similar. Terraform’s plan and CloudFormation’s change sets both show what will change before committing, and both provide a natural place to insert manual approval gates for destructive changes. The StackPractices guide recommends running terraform plan -out=tfplan, converting it to JSON with terraform show -json, and having CI parse the result to flag destructive operations. CloudFormation change sets provide the same visual review without the JSON step.

Learning Curve and Team Fit

The learning curve depends largely on which language your team already knows. Terraform requires learning HCL, a declarative language unfamiliar to most developers but not difficult. CloudFormation requires understanding YAML plus intrinsic functions. The TechTarget comparison notes the learning curve is simple for basic templates since most developers know JSON and YAML, but complexity increases with more advanced templates.

Pulumi takes a different approach. If your team writes TypeScript or Go regularly, the infrastructure code resembles the rest of your codebase. If your team is infrastructure-focused and has never written classes, Pulumi adds a programming language to learn alongside cloud APIs. Neither approach is universally easier; the best fit depends on who writes the code.

There is a trade-off in Pulumi’s approach worth mentioning. General-purpose languages let you build abstraction layers, which can hide what is actually provisioned. A helper function that creates a “standard service” is convenient until it silently changes a security group rule. Terraform’s requirement that every resource be visible in the configuration helps document what is provisioned, which pays off during incidents.

Terraform Pain Points and What Independently Confirms

Vendor and community sources agree that Terraform’s state model is its most frequent failure point at scale. The InfoWorld analysis of Terraform scaling reports that organizations managing more than 500 resources in a single workspace routinely see plan times of 15 to 30 minutes, that a corrupted state file mid-apply can take hours to recover, and that HashiCorp’s own State of Cloud Strategy Survey lists state issues, corruption, drift, and locking failures among top pain points for organizations with more than 50 engineers. The same analysis cites DORA’s 2023 State of DevOps Report, which found teams dealing with frequent configuration drift had 2.3 times higher change failure rates.

The Encore guide to Terraform in 2026 lists similar drawbacks from another perspective: state drift when engineers bypass the process and change things in the console, the HCL learning curve, manual replication of configuration across environments leading to over-provisioned dev and staging, and complex debugging on large deployments. Encore is a competing product, so treat its framing as a vendor argument, but the failure modes it names match independent practitioner reports.

The evidence is weaker for specific percentages attributed to state problems. A MoldStud article claims 67% of teams report state management issues, but that figure comes from a content site with no published methodology and should be treated as illustrative rather than measured. The general claim that state management is the main operational complaint about Terraform is well supported. The exact number is not.

CloudFormation and Pulumi also face scale issues. CloudFormation’s 500-resource stack limit forces decomposition on large environments, and nested stacks raise the limit to 2,500 at the cost of more complex templates. Pulumi’s smaller community means fewer pre-built modules and less Stack Overflow coverage when you encounter edge cases, which can be costly during incidents at 2 a.m.

Troubleshooting Errors You Will Actually Hit

Terraform: state lock held by a dead process. A CI job times out mid-apply and the lock is never released. Confirm no other apply is running, then release it with terraform force-unlock LOCK_ID. Do not force-unlock while another run is genuinely in progress, or you risk concurrent writes to the same state.

Terraform: plan shows resources being destroyed that you did not touch. This usually means drift or that a resource was removed from configuration but remains in state. Run terraform plan and review the destroy list carefully before applying. If a resource was created outside Terraform, bring it under management with terraform import rather than letting the plan delete and recreate it.

Terraform: partial apply failure. Terraform created some resources but failed on a later one. Fix the underlying error and run apply again; Terraform detects the already-created resources and continues. Do not delete resources manually to “clean up” before re-running.

Pulumi: 403 on state operations. The backend identity is missing one of the four required S3 actions. Verify ListBucket is granted on the bucket ARN and GetObject, PutObject, and DeleteObject are granted on the object ARN. A common mistake is granting all four at the bucket level, which fails because object-level actions require the object ARN.

CloudFormation: stack stuck in UPDATE_ROLLBACK_FAILED. A resource failed to roll back, often because a dependency was changed outside the stack. This requires manual intervention in the console to skip the problematic resource, which adds operational burden that pushes teams toward Terraform or Pulumi.

All three: unexpected cost from an orphaned RDS instance. A test stack was destroyed but the database had deletion protection enabled, or a final snapshot was skipped and the instance survived. Regularly audit for untagged or unmanaged resources; this is the coverage gap that Firefly’s 6% full-codification figure describes.

Choosing for Production

For an AWS-only environment with strict change-control requirements and no multi-cloud plans, CloudFormation’s managed state and native rollback reduce operational work you would otherwise handle yourself. The trade-offs are verbosity, a 500-resource stack limit, and no path to other clouds without rewriting.

For a platform team fluent in TypeScript, Python, or Go that wants infrastructure and application code to share tooling and tests, Pulumi’s programming model fits best. Budget for the smaller module ecosystem and the discipline needed to keep abstractions from hiding what is provisioned.

Terraform remains the default for multi-cloud environments and has the largest pool of existing modules and community support. Its state model is also its biggest weakness, and mitigating it requires architectural measures: remote backend with locking from day one, workspace or directory separation per environment, pinned provider versions, and scheduled drift detection rather than reacting after incidents. Teams that treat these as optional generate the scaling complaints.

The 47% figure from Firefly is important. If nearly half of current Terraform users are considering switching, the deciding factor will not be syntax preference. It will be whether the state file, plan times, and drift issues are manageable at their scale, and whether OpenTofu’s open-source governance model removes a licensing risk they prefer to avoid.

More in-depth coverage from this blog on closely related topics:

Sources and References

Sources cited while researching and writing this article:

Thomas A. Anderson

Mass-produced in late 2022, upgraded frequently. Has opinions about Kubernetes that he formed in roughly 0.3 seconds. Occasionally flops, but don't we all? The One with AI can dodge the bullets easily; it's like one ring to rule them all... sort of...