Infrastructure as code is a provisioning discipline that replaces manual server configuration with version-controlled definition files, giving engineering teams a reproducible, auditable record of every resource in their environment. The category has stabilized around six tools that cover the practical paradigms a platform team will encounter: declarative HCL with Terraform and its open-source fork OpenTofu, multi-language SDKs through Pulumi, AWS-native generation via the Cloud Development Kit, agentless configuration management with Ansible, and the Kubernetes-native control plane model Crossplane introduced. Picking among them is now less a question of features than of fit against cloud footprint, team language affinity, and how much state machinery the organization is willing to operate. Adjacent decisions sit one layer out from the provisioning question: the Kubernetes deployment guide for the runtime these tools target, the database selection comparison for the stateful resources they provision, the webhook versus polling comparison for how pipelines signal one another, and the full stack development learning path for the skills mix a platform team needs.
How IaC Provisioning Works
Infrastructure as code converts provisioning into a software supply chain. Engineers write definition files, commit them to a repository, and let the tool reconcile real cloud resources against the committed manifest. The pre-IaC baseline is familiar enough: an operator opens an SSH session, runs a sequence of CLI commands, edits a few config files in place, and leaves no machine-readable trail. The IaC baseline replaces that session with a manifest, a plan output, and a recorded apply.
Two root paradigms dominate. In a declarative model, you describe the desired state of the system and the tool computes a diff against the live environment, then executes only the changes needed to converge. In an imperative model, you write the step-by-step procedure yourself. Declarative tooling pairs with idempotent execution: running the same manifest ten times produces the same result as running it once, because each apply re-derives its actions from the gap between desired state and observed state. That property is what makes a Terraform or Pulumi plan safe to wire into a CI pipeline; an imperative shell script offers no such guarantee without defensive coding in every step.
Declarative vs Imperative Approaches
| Attribute | Declarative | Imperative |
|---|---|---|
| You define | The end state | The procedure |
| Tool decides | The execution order | Nothing; you ordered it |
| Example tools | Terraform, OpenTofu, Pulumi, AWS CloudFormation, Crossplane | Ansible playbooks (partially), Chef recipes, shell scripts |
| Failure behavior | Re-run reconciles to target | Re-run may double-apply or fail mid-sequence |
Ansible occupies a hybrid position worth flagging early: playbooks are task-ordered, but individual modules are written to be idempotent so a re-run of a playbook converges rather than duplicates. Most modern IaC leans declarative because the reconciliation loop is easier to reason about under failure.
The Six Leading IaC Tools

Infrastructure as code tooling has consolidated around six options that cover the realistic range of organizational needs. Each profile below names the paradigm, state model, ecosystem reach, and licensing posture, then notes the scenario it wins.
Terraform
Terraform uses HCL, the HashiCorp Configuration Language, as its declarative DSL. State lives in a state file, kept locally for solo work or pushed to a remote state backend such as Terraform Cloud, an S3 bucket with DynamoDB locking, or a GCS bucket. The provider plugin registry ships over 1,000 providers covering AWS, Azure, GCP, Kubernetes, Datadog, GitHub, Cloudflare, and a long tail of SaaS APIs. The plan and apply workflow gives operators a diff preview before any mutation, which is the feature most teams cite when comparing it to CloudFormation. The weakness is structural: the state file is a single source of truth and a blast radius for concurrent operations whenever locking is misconfigured. Licensing shifted to BUSL-1.1 with the 1.6 release in August 2023, which is not OSI-approved and prompted the OpenTofu fork.
OpenTofu
OpenTofu is the community fork of Terraform, governed by the Linux Foundation and released under MPL-2.0. It tracks Terraform feature-for-feature through 1.5.x and has diverged from 1.6 onward, adding native end-to-end state encryption in 1.7. The state model is identical to upstream: a state file, the same remote state backend options, and provider compatibility through the OpenTofu registry. For procurement teams, the decision usually rests on license posture; the HashiCorp License FAQ describes the BUSL restrictions that the OpenTofu project exists to avoid.
Pulumi
Pulumi replaces the DSL with general-purpose programming languages: TypeScript, Python, Go, Java, and C# on .NET. The Pulumi engine reconciles desired state against a backend, which can be Pulumi Cloud, S3, Azure Blob, or GCS. Teams gain real loops, conditionals, functions, and unit tests without the workarounds HCL forces, plus IDE completion and type-checking on the resource graph. The cost is a steeper learning curve for operators who do not write application code daily, and a testing story that requires the host language’s framework rather than a built-in. The Pulumi: How Pulumi Works documentation describes the resource model and state engine in detail.
AWS CDK
The AWS Cloud Development Kit compiles imperative code in TypeScript, Python, Java, or .NET into CloudFormation templates, which CloudFormation then deploys and tracks as stacks. The construct library is its draw: L1 constructs map raw CFN resources one-to-one, L2 constructs add opinionated defaults, and L3 pattern constructs encode whole architectures such as a fronted Fargate service with a load balancer and IAM roles wired in. CDK is the right answer for AWS-only shops that want language expressiveness without leaving the CloudFormation drift model. It is also the wrong answer for any team that expects to provision Azure or GCP from the same pipeline.
Ansible
Ansible is an agentless configuration management tool that uses SSH and YAML playbooks. Modules are idempotent and playbooks run top-to-bottom, which gives it a foothold in both declarative configuration and procedural automation. Its strengths are operational: no agent install on target hosts, mature module coverage for patching, package management, service configuration, and application deployment. Where it falls short is primary cloud provisioning at scale; it lacks a native state file and offers no plan-style drift detection comparable to Terraform’s. Most teams that adopt Ansible end up pairing it with a declarative provisioner. That pairing is covered in depth in the Terraform vs Ansible vs CloudFormation breakdown.
Crossplane
Crossplane is a Kubernetes-native control plane that models cloud resources as Kubernetes custom resources. Desired state is declared in YAML manifests; Kubernetes controllers reconcile against cloud APIs through Crossplane providers. There is no external state file because the cluster’s etcd store carries the state of the world. That design fits cleanly into a GitOps workflow driven by Argo CD or Flux, since cluster reconciliation is already how Kubernetes operates. The prerequisite is the cluster itself: Crossplane assumes teams comfortable running production Kubernetes, a non-trivial bar covered in the Kubernetes deployment guide. The Crossplane Documentation details the composite resource model and provider configuration.
Comparing IaC Tools Across Six Axes
Infrastructure as code selection collapses to six axes once feature lists are set aside: language surface, state model, drift detection capability, multi-cloud reach, policy enforcement integration, and license posture. The table below summarizes how the six tools score on each.
| Tool | Language / DSL | State model | Drift handling | Multi-cloud | Policy integration | License |
|---|---|---|---|---|---|---|
| Terraform | HCL | Local or remote state backend (S3, GCS, TFC) | terraform plan | Yes (3,000+ providers) | Sentinel (paid), OPA/Conftest, Checkov | BUSL-1.1 |
| OpenTofu | HCL | Same as Terraform; native state encryption in 1.7 | tofu plan | Yes (Terraform-compatible providers) | OPA/Conftest, Checkov | MPL-2.0 |
| Pulumi | TypeScript, Python, Go, Java, C# | Pulumi Cloud or self-hosted (S3, Azure Blob, GCS) | pulumi preview / refresh | Yes | CrossGuard (OPA-based), Checkov | Apache-2.0 |
| AWS CDK | TypeScript, Python, Java, .NET | CloudFormation stacks | CloudFormation drift API | No (AWS only) | cdk-nag, CloudFormation Guard | Apache-2.0 |
| Ansible | YAML playbooks | None (stateless) | Check mode (limited) | Yes (via modules) | ansible-lint, OPA | GPL-3.0 |
| Crossplane | YAML manifests (CRDs) | Kubernetes etcd | Continuous reconciliation | Yes (per provider) | OPA Gatekeeper, Kyverno | Apache-2.0 |
The policy as code column deserves its own note. Sentinel is HashiCorp’s paid policy engine, available only with Terraform Cloud or Enterprise tiers. OPA, the Open Policy Agent, is the open-source alternative and a CNCF graduated project; the Open Policy Agent (OPA) Documentation covers its Rego language and the Conftest wrapper that evaluates HCL, JSON, and YAML against policy bundles. Checkov adds static analysis across HCL, CloudFormation, and Kubernetes YAML in a single linter. In practice, teams gate the IaC plan output in CI before the apply runs; if a plan violates a policy, the pull request never merges. For broader context on where IaC sits in a service architecture, our microservices communication guide walks through how provisioning ties into runtime traffic patterns, and our CI/CD pipeline programming languages piece covers the gate itself.
IaC Best Practices Every Team Should Apply

Infrastructure as code earns its reliability dividend only when a handful of practices are non-negotiable across the team. The five below apply to every tool in the table above.
- Store state remotely with locking. Local state belongs on a laptop for experiments and nowhere else. An S3 bucket with DynamoDB, a GCS bucket with object versioning, or Terraform Cloud each provide the lock semantics that prevent two engineers from corrupting the state file with concurrent applies. A remote state backend also makes recovery from a botched apply a versioned-object restore rather than a Slack-driven post-mortem.
- Use modules for reuse. A module is a packaged unit of declarative configuration that encodes opinionated defaults: a VPC layout, an RDS instance with backups, a Kubernetes namespace with policies. Publishing internal modules to a private registry prevents shadow variants of the same resource from drifting apart across teams.
- Enforce immutable infrastructure. Patching a live server keeps the change off the IaC graph and creates configuration drift no plan can detect. Replacing the server from a known image, the model behind immutable infrastructure, turns every change into a pull request. NIST SP 800-190 recommends the same pattern for container runtimes, treating each release as a rebuilt artifact rather than a patched-in-place host.
- Gate plans in CI. Run the equivalent of terraform plan on every pull request, render the diff into the PR conversation, and block merge when the plan shows unexpected destroy operations. The plan output is the most underused review artifact in IaC; surfacing it forces a second pair of eyes before a delete touches production.
- Apply policy as code. Encode guardrails such as “no public S3 buckets,” “no unencrypted EBS volumes,” and “minimum two replicas in production namespaces” in OPA, Conftest, or Sentinel. Evaluate them against the plan output in CI. The point is to move compliance left, into the same merge gate as tests, instead of catching violations during a quarterly audit.
For configuration files that cross tool boundaries, JSON remains the lingua franca; IETF RFC 8259 is the authoritative specification, and most IaC states and provider API payloads conform to it.
Choosing the Right IaC Tool for Your Organization
Infrastructure as code selection comes down to four common org profiles. Each maps cleanly to one or two of the six tools, and the wrong pick usually costs a quarter of replatforming before anyone admits it.
- AWS-first, small team, no Kubernetes: start with Terraform or OpenTofu. The HCL DSL is learnable in a week, the AWS provider plugin is the most mature in the ecosystem, and remote state on S3 with DynamoDB locking is a one-hour setup. AWS CDK is a reasonable alternative if the team already writes TypeScript and prefers to stay inside the CloudFormation reconciliation model.
- Multi-cloud or hybrid footprint: Terraform or OpenTofu cover the broadest provider plugin surface across AWS, Azure, GCP, and the SaaS layer. Pulumi is the right pick when the platform team has strong software-engineering culture and wants real unit tests against the resource graph, not snapshot diffs.
- Kubernetes-native platform team: Crossplane fits when Kubernetes is already the operations substrate. A GitOps workflow with Argo CD makes drift detection continuous rather than scheduled, since the controller reconciles on every change to the manifest repository.
- Large enterprise with compliance requirements: OpenTofu paired with OPA and Conftest gives the broadest open-source posture for auditors. Terraform Enterprise or Pulumi Business Critical are the answers when paid policy enforcement, SSO, and vendor support contracts are non-negotiable line items.
When Ansible Complements Rather Than Replaces
Ansible versus Terraform is usually a false choice. Terraform or OpenTofu provision the cloud resource: the VPC, the EC2 instance, the load balancer, the security group. Ansible then configures the operating system and the application layer on the provisioned host: package installation, file templating, service restarts, certificate rotation, log shipping. The split keeps each tool inside its strong zone, idempotent execution and plan-driven provisioning for the declarative tool, agentless SSH reach and rich module coverage for the configuration management layer. For teams running CI gates on IaC changes, our CI/CD pipeline comparison covers how GitHub Actions, GitLab CI, and Jenkins each handle the plan-output review pattern.
Further reading
- NIST SP 800-190: Application Container Security Guide for the immutable artifact rationale.
- Open Policy Agent (OPA) Documentation for the Rego language and Conftest wrapper.
- OpenTofu (Linux Foundation) for project governance and version history.
- Crossplane Documentation for the Kubernetes-native composite resource model.
- Best API Documentation Tools: Swagger vs Postman vs GitBook
- Best Programming Languages for AI Development: Python, C++, and Rust Compared
- VS Code vs Sublime Text: Code Editor Comparison for Developers









