Terraform vs Pulumi vs Crossplane: IaC in 2026
Comparing the three leading infrastructure-as-code tools across language support, state management, Kubernetes integration, and team workflows.
The State of Infrastructure as Code in 2026
Infrastructure as Code has moved from a best practice to a baseline expectation. If you are provisioning cloud resources by clicking through a web console, you are operating with significantly more risk, less repeatability, and less velocity than teams using IaC. The question is no longer whether to use IaC, but which tool to use.
Three tools dominate the landscape in 2026: Terraform by HashiCorp, Pulumi, and Crossplane. Each takes a fundamentally different approach to the same problem. Terraform uses a declarative domain-specific language called HCL. Pulumi lets you write infrastructure code in general-purpose programming languages like TypeScript, Python, Go, and Java. Crossplane extends Kubernetes to manage cloud resources through custom resource definitions. Understanding the strengths and tradeoffs of each is essential for making the right choice for your team.
Terraform: The Established Standard
Terraform remains the most widely adopted IaC tool by a significant margin. Its ecosystem of providers covers virtually every cloud service, SaaS platform, and infrastructure component you might need to manage. The provider registry lists over 3,500 providers, and the module registry contains thousands of pre-built, reusable infrastructure components. When you search for "how to provision X with infrastructure as code," the first result is almost always a Terraform example.
HCL, Terraform's configuration language, is declarative and purpose-built for infrastructure. You describe the desired state of your resources, and Terraform figures out the sequence of API calls needed to reach that state. The plan-apply workflow shows you exactly what changes will be made before they happen, which provides a crucial safety net for production infrastructure. The language is relatively simple to learn for developers and accessible to operations engineers who may not have deep programming backgrounds.
Terraform's state management is both its greatest strength and its most significant operational challenge. The state file records the mapping between your configuration and the actual cloud resources. This enables Terraform to detect drift, plan changes accurately, and manage resource lifecycles. However, the state file must be stored securely, accessed with locking to prevent concurrent modifications, and occasionally repaired when it gets out of sync with reality. Remote state backends like S3 with DynamoDB locking, Terraform Cloud, or a self-hosted solution are essential for team workflows.
OpenTofu
After HashiCorp changed Terraform's license from MPL to BSL in 2023, the open-source community forked it as OpenTofu under the Linux Foundation. OpenTofu maintains compatibility with Terraform's language and providers while preserving an open-source license. If licensing is a concern for your organization, OpenTofu is a drop-in replacement for most Terraform workflows.
Terraform's limitations become apparent at scale. HCL lacks the control flow, abstraction, and composition capabilities of a general-purpose programming language. Complex conditional logic, dynamic resource generation, and advanced data transformations require workarounds using count, for_each, dynamic blocks, and local values that can become difficult to read and maintain. Module composition is powerful but can lead to deeply nested configurations that are hard to debug. Testing infrastructure written in HCL requires external frameworks like Terratest (written in Go) or the newer built-in testing feature.
Pulumi: Programming Languages for Infrastructure
Pulumi takes a fundamentally different approach by letting you write infrastructure code in languages you already know: TypeScript, Python, Go, Java, C#, and YAML. Instead of learning a DSL, you use the same language, IDE, type system, testing framework, and package manager that you use for application development. This eliminates the cognitive overhead of context-switching between application code and infrastructure code.
The practical benefits of using a general-purpose language are significant. You get real loops and conditionals, not workarounds. You can create classes and functions that encapsulate infrastructure patterns and share them as regular packages via npm, PyPI, or Maven. Type checking catches configuration errors at development time rather than during a plan or apply. You can write unit tests using familiar testing frameworks like Jest, pytest, or Go's testing package. And your IDE provides autocompletion, inline documentation, and refactoring tools that HCL editors cannot match.
Pulumi's resource model is similar to Terraform's in many ways. You declare resources, Pulumi determines the dependency graph, and it creates, updates, or deletes resources to match your desired state. Pulumi also has a state management system, with options for the Pulumi Service (a managed backend), self-managed backends on S3, Azure Blob, or GCS, and a local file backend for experimentation. The state management experience is broadly comparable to Terraform's.
Where Pulumi shines is in complex, dynamic infrastructure. If you need to generate resources based on data from an API call, iterate over a configuration file to create multiple similar environments, or apply sophisticated validation logic to your infrastructure definitions, Pulumi makes this straightforward. In Terraform, the same tasks often require external scripts, data sources, or complex HCL constructs that obscure the intent.
The tradeoff is that Pulumi's flexibility introduces new categories of problems. With a general-purpose language, you can write infrastructure code that is imperative, stateful, and side-effect-heavy, all things that make infrastructure management harder, not easier. Without discipline, Pulumi projects can become as tangled as any poorly structured application codebase. The tool gives you more power, but it also gives you more rope.
Pulumi's provider ecosystem is growing but still smaller than Terraform's. Most major cloud services are well-covered, and Pulumi can consume Terraform providers through a bridge mechanism, but native provider support for niche services may lag. The community is active and growing, but the volume of examples, tutorials, and Stack Overflow answers is still a fraction of Terraform's.
Crossplane: Kubernetes-Native Infrastructure
Crossplane takes yet another approach by extending Kubernetes with custom resource definitions (CRDs) that represent cloud infrastructure. Instead of running a separate IaC tool, you manage cloud resources using kubectl, GitOps workflows, and the same reconciliation loop that manages your Kubernetes workloads. If your organization is already standardized on Kubernetes and GitOps, Crossplane can unify your infrastructure and application management under a single control plane.
In Crossplane, you define cloud resources as Kubernetes manifests. A managed resource for an AWS S3 bucket looks like any other Kubernetes resource: it has an apiVersion, kind, metadata, and spec. Crossplane's providers (analogous to Terraform providers) reconcile these resources by making API calls to the cloud provider. The Kubernetes reconciliation loop continuously ensures that the actual state matches the desired state, automatically correcting drift without manual intervention.
Crossplane introduces the concept of Compositions, which let you define higher-level abstractions that map to collections of managed resources. For example, you could define a "Database" composition that creates an RDS instance, a security group, a subnet group, and a parameter group as a single logical resource. Teams can then request a "Database" without needing to understand the underlying cloud resources, similar to how Kubernetes abstracts container orchestration details behind Deployments and Services.
The Kubernetes-native approach has several advantages. GitOps workflows using Argo CD or Flux apply naturally to infrastructure managed by Crossplane. RBAC, namespaces, and Kubernetes admission controllers provide familiar access control. The continuous reconciliation model means that if someone manually changes a cloud resource, Crossplane will detect and correct the drift automatically, without waiting for a human to run a plan and apply.
However, Crossplane has significant prerequisites and limitations. You need a running Kubernetes cluster to manage your infrastructure, which creates a chicken-and-egg problem: something has to provision the Kubernetes cluster that Crossplane runs on. The YAML-based configuration, while familiar to Kubernetes users, is verbose and lacks the expressiveness of either HCL or general-purpose programming languages. Debugging issues requires understanding both Kubernetes internals and cloud provider APIs. And the provider ecosystem, while growing, covers fewer services than Terraform or Pulumi.
Language and Developer Experience
The language choice has cascading effects on your team's productivity and the quality of your infrastructure code.
Terraform's HCL is simple and constrained, which is both its strength and weakness. A new team member can read and understand HCL configuration files within hours. The limited expressiveness means there are fewer ways to solve any given problem, which leads to more consistent codebases. But the same constraints make complex logic painful. Writing a module that conditionally creates resources based on input variables, handles multiple environments, and supports optional features often results in dense blocks of count and for_each expressions with ternary operators that are difficult to parse.
Pulumi in TypeScript provides the best IDE experience of the three. VS Code with the TypeScript language server gives you autocompletion for every resource property, inline documentation, real-time type checking, and refactoring support. Writing infrastructure code feels identical to writing application code. The disadvantage is that TypeScript (or Python, Go, etc.) is a more complex language than HCL, and junior engineers or operations staff who are not programmers may find the learning curve steeper.
Crossplane's YAML configuration is familiar to anyone who has worked with Kubernetes, but it is not a programming language. You cannot define variables, loops, or conditional logic in raw YAML. Composition functions and patches provide some abstraction capability, but they are less intuitive than either HCL or a programming language. Many Crossplane users generate YAML using tools like Helm, Kustomize, or cdk8s, which adds another layer of tooling.
State Management Compared
Terraform stores state in a single file (or split across workspaces) that records the mapping between configuration and cloud resources. State locking prevents concurrent modifications. State is explicit: you can inspect it, import resources into it, and manually edit it if necessary. The state file is a single source of truth, but it can become large for complex environments and requires careful management.
Pulumi's state management is conceptually similar to Terraform's. State is stored in a backend (Pulumi Service, S3, Azure Blob, GCS, or local file) and tracks resource mappings. The Pulumi Service adds collaboration features like state history, audit logging, and a web UI for inspecting resources. The experience is broadly comparable to Terraform Cloud.
Crossplane does not have a traditional state file. The Kubernetes etcd database is the state store. Each managed resource in Kubernetes represents a cloud resource, and its status field contains the current state. This eliminates the need for a separate state backend but ties your infrastructure state to your Kubernetes cluster's health. If the cluster is lost, you need to reimport resources or restore from an etcd backup.
Team Workflows and Collaboration
For team workflows, Terraform has the most established patterns. Pull request-based workflows with plan output in PR comments, policy as code with Sentinel or OPA, and module registries for sharing reusable components are well-documented and widely practiced. Terraform Cloud and Spacelift provide managed platforms for these workflows. The plan-review-apply pattern is intuitive and provides a clear audit trail.
Pulumi supports similar workflows with the Pulumi Service providing preview output in PRs, policy as code using Open Policy Agent, and package registries for sharing components. The experience is comparable to Terraform Cloud but with a smaller ecosystem of integrations and third-party tools.
Crossplane leverages Kubernetes-native workflows. GitOps with Argo CD or Flux provides pull-based reconciliation, where changes merged to a Git repository are automatically applied to the cluster. This is a natural fit for teams already using GitOps for application deployment. However, the review process for infrastructure changes is less explicit than Terraform's plan output. You see the diff of YAML manifests in a PR, but not the specific API calls that will be made or the resources that will be created, modified, or deleted.
Which Tool to Choose
Choose Terraform if you want the broadest provider ecosystem, the most community resources, and a language that operations engineers and developers can both learn quickly. Terraform is the safe choice for most organizations and the easiest to hire for. Use Terraform if your infrastructure complexity is moderate, your team values consistency over flexibility, and you want the largest pool of existing modules and examples to draw from.
Choose Pulumi if your team is composed primarily of software developers, your infrastructure is complex enough to benefit from real programming constructs, and you want to use the same language and tooling for infrastructure and application code. Pulumi is particularly strong for platform engineering teams that build internal developer platforms, complex multi-environment setups, and infrastructure that is tightly integrated with application logic.
Choose Crossplane if your organization has standardized on Kubernetes and GitOps, and you want to manage cloud infrastructure through the same control plane. Crossplane is ideal for platform teams that want to offer self-service infrastructure to development teams through Kubernetes-native abstractions. It is less suitable as a general-purpose IaC tool for organizations that do not already run Kubernetes.
Start simple
If you are starting from scratch, begin with Terraform. It has the gentlest learning curve, the most resources, and the broadest applicability. You can always migrate to Pulumi or Crossplane later if your needs evolve. The most important thing is to adopt IaC at all, not to pick the theoretically perfect tool.
Written by Jeff Monfield
Cloud architect and founder of CloudToolStack. Building free tools and writing practical guides to help engineers navigate AWS, Azure, GCP, and OCI.
Disclaimer: This article is for informational purposes. Cloud services and pricing change frequently; always verify with official provider documentation. AWS, Azure, GCP, and OCI are trademarks of their respective owners.