RSS

Terraform vs. CloudFormation: How to choose the right IaC tool

Terraform CloudFormation AWS Infrastructure As Code

What you’ll learn: You’ll get a practical Terraform vs CloudFormation comparison focused on the decisions that show up later around state, drift, previews, reuse, portability, and team workflows – plus a clear way to choose.

Most teams start this conversation with a single question: AWS-native or cloud agnostic? The buying decision feels obvious when you say it out loud. If all your cloud infrastructure lives inside the AWS ecosystem, why not use the native AWS service that was built for managing AWS resources? If you need multiple cloud platforms, why not pick the open source infrastructure as code (IaC) tool that has multiple providers and call it a day?

Then production happens.

You find out that deciding between AWS-only and multi-cloud support barely predicts the things that cause pain later:

Using Infrastructure as Code tools like Terraform and AWS CloudFormation automates the process of provisioning infrastructure, reducing manual errors and improving consistency, but automation just means you get to scale your mistakes too.

Both tools can be right, depending on what you treat as the source of truth for infrastructure, how you preview infrastructure changes, and how you run changes safely across teams and AWS accounts. By the end, you should be able to pick a default, name the exceptions without hand-waving, and avoid the most common failure modes, drift, state contention, and unreadable templates that nobody wants to touch.

CloudFormation vs. Terraform: a quick comparison

Before we get into the messy parts, it helps to put the usual CloudFormation vs. Terraform talking points in one place, because once you can see the surface area, you can start to predict the operational shape underneath.

Category Terraform AWS CloudFormation
Scope Multi-provider, works across many cloud providers and SaaS APIs via Terraform providers AWS-first, built to deploy AWS resources through a native AWS service
Authoring experience HCL in Terraform configuration files, plus modules and rich expression support CloudFormation templates in YAML or JSON, plus intrinsic functions and extensions
Preview model terraform plan produces an execution plan you can review and gate on Change sets preview stack updates, but do not guarantee success
State handling State file plus remote state storage and locking for teams Managed stack state owned by the CloudFormation service
Drift reasoning Drift exists whenever reality diverges from Terraform state and config, and remediation often flows through refresh, import, or reconcile Drift detection is built in for stacks and supported resources
Portability Many providers, but portability is not automatic because config still encodes provider-specific primitives Primarily limited to AWS resources, making it unsuitable for multi-cloud or hybrid environments
Day-2 ops Strong workflow tooling around plans, modules, and CI/CD, but you must operate state and guardrails Stack lifecycle, rollbacks, StackSets, and deep AWS integrations for org-scale rollout

One misconception to squash early is that Terraform supports many providers, but that does not mean the same template, identical resources, and the same Terraform code will be portable across clouds. The primitives and API calls you rely on in AWS rarely map cleanly onto Google Cloud or Microsoft Azure.

Terraform vs. CloudFormation comparison

First, let's explore the similarities.

Both Terraform and CloudFormation can create an S3 bucket, and both tools have similar learning curves, most likely becoming challenging when your infrastructure project becomes important.

The differences are in how each tool plans changes, where state lives, how drift is detected and reconciled, how reuse works when you have multiple environments, and how DevOps engineers ship changes under review without turning the AWS console into the real control plane.

If you want decision anchors that actually predict the future, you can usually place your situation into a handful of shapes:

Here is the direct comparison, focusing on the parts that tend to matter once the infra grows up.

Production concern Terraform AWS CloudFormation
Unit of change A workspace and its state, often mapped to an environment or component A stack, sometimes many stacks, plus StackSets for org rollout
Change preview Plan output is the core safety mechanism, and it’s easy to gate applies on it Change sets preview, but you still need to reason about failures and rollbacks
State management You operate it, secure it, lock it, back it up, and design around contention AWS manages the stack state as part of the service
Drift detection Usually externalized into workflow, plus refresh and import when reality diverges Drift detection is a first-class feature for stacks and resources that support it
Reuse Terraform modules are the default reuse unit, and they shape repo design Nested stacks, StackSets, and modules via the registry, each with trade-offs
Beyond AWS Terraform providers cover many cloud providers and services in one workflow CloudFormation is AWS-centric, with extensions for some third-party types
Governance at scale Usually enforced via CI/CD, policy checks, and workflow tooling IAM and service integrations, plus org-scale patterns like StackSets

The next step is understanding how each tool actually works, because the ergonomics you feel every day come directly from the underlying model.

How CloudFormation works

CloudFormation is a managed AWS service, and that managed nature is the feature, not a footnote. AWS is hosting the engine that reconciles the desired state against real infrastructure, stores the stack state, and runs the orchestration for create, update, and delete across your AWS resources.

The unit of deployment is the CloudFormation stack, which you can think of as a contract between a CloudFormation template and the CloudFormation service. Once a resource is in that contract, CloudFormation expects to be the thing that changes it.

That model creates a kind of gravity that AWS-first teams often love. The integration capabilities tend to be direct, the audit story tends to be easier to align with AWS-native controls, and day-one support for new AWS features often lands in CloudFormation before the equivalent support stabilizes in third-party tooling.

Put differently, AWS CloudFormation integrates tightly with AWS services and often supports new features faster than Terraform, which can be a serious advantage when you are adopting new native AWS service capabilities under a deadline.

The native preview mechanism is the change set, which lets you see proposed updates before you execute them. However, it's worth internalizing the sharp edge. AWS documents that change sets do not tell you whether the update will succeed, and they do not validate quota issues, unsupported updates, or insufficient permissions – all of which can still cause a stack update to fail even if the preview looked reasonable.

How Terraform works

Terraform's core workflow is explicit about planning as a distinct step. terraform plan produces an execution plan that previews the changes Terraform will make, and that separation between plan and apply becomes more valuable as the infrastructure gets more complex and the blast radius gets harder to reason about.

You can run plan in CI, attach it to a pull request, review the diff like you would application code, and then decide whether apply should be allowed, which is why so many teams end up treating plan review as the actual governance mechanism.

Terraform’s superpower is not that it can deploy resources, everyone can deploy resources, it's that it can use one workflow across many providers, which includes the big cloud providers, smaller cloud platforms, and third-party systems that expose APIs for things like DNS, monitoring, incident tooling, and identity.

That flexibility is also why portability is tricky, because a Terraform configuration that manages AWS infrastructure tends to encode AWS-specific decisions – from IAM semantics to VPC routing to the shape of managed services – and rewriting those for a different cloud provider is usually a migration, not a copy and paste.

Terraform also makes state management a first-class operational concern, because it tracks real resources against a state file, and teams quickly move to remote state storage and locking so multiple engineers can safely collaborate without corrupting or racing the state.

The key differences between Terraform and CloudFormation

Now that the underlying models are on the table, the differences that matter show up in a few predictable places:

  1. The authoring experience
  2. The preview and blast radius mechanics
  3. The state and drift story, modularity and reuse
  4. Portability across cloud providers
  5. The team workflows that determine whether infrastructure code changes feel routine or terrifying

The next sections walk through those in the order you usually feel them in real life, which starts with the moment you open the repo and try to read what your system is doing.

Syntax and authoring

Terraform uses HCL, the HashiCorp configuration language The reason people talk about it so much is not aesthetics, it's because HCL tends to scale better for large Terraform codebases where you need composition, reuse, and a reasonable way to express variations across multiple environments.

HCL provides a wide range of built-in functions, loops, and conditionals, offering more dynamic configuration options than AWS CloudFormation. That matters when you stop writing one-off configs and start writing patterns that must survive copy-paste pressure. When you do get something wrong, Terraform's structure usually makes it easier to isolate where the mistake is, because modules and variable boundaries become a kind of natural fault line.

CloudFormation templates are YAML or JSON, which is fine until it is not. A big template is still a big template, and the tools you reach for to make it bearable – intrinsic functions, mappings, conditions, and nested stacks – can slowly turn a declarative template into something that behaves like a programming language while refusing to admit it.

AWS provides escape hatches, and they are real.

Macros can transform templates, including built-in transforms like AWS::Include and AWS::Serverless for AWS SAM, which can be great when you want to standardize boilerplate or expand higher-level syntax into raw CloudFormation.

Custom resources let you run custom provisioning logic during stack operations, which is powerful, but it also means you are now maintaining code that runs inside your deployment engine, and you own its failure modes.

Those extensions can help, especially in mature AWS-first orgs where they can become the foundation of a strong internal platform. However, they can also become a DIY language layer that only two people understand, which makes previews and blast radius control the next thing you end up caring about.

Previews and blast radius

If authoring is about what you can express, previews are about what you can trust Once you have more than a few engineers touching infrastructure code, trust is the only thing standing between “reviewable change” and “deploy and pray.”

Terraform's plan is the centerpiece here.

The plan step reads current state, computes deltas, and proposes a set of actions to make real cloud resources match the desired configuration. Because it's a distinct artifact, you can produce it automatically and treat it as a gate before apply. Many Terraform teams talk about workflows more than syntax, because the plan output becomes the shared interface between devops engineers, reviewers, and the eventual apply.

CloudFormation's change sets play a similar role. You create a change set, you review what will change, and then you execute it when you are ready. The AWS console, CLI, and API surface that flow in a way that fits naturally into AWS-first operations.

The sharp edge is still there, though.

AWS explicitly notes that change sets don't indicate whether CloudFormation will successfully update the stack, and that gap forces you to think about failure handling and rollback behavior as part of your normal review.

A practical way to keep reviews honest is to make reviewers capture evidence from previews in the pull request itself, and the simplest what-to-screenshot checklist is to grab the parts that show creates, updates, destroys, replacements, and permission changes – those five categories predict most outages and most surprise bills.

Change type What you are looking for
Creates New surface area, new costs, new permissions
Updates In-place vs disruptive updates, especially on stateful services
Destroys Anything that deletes data or breaks dependencies
Replacements “Update” that is actually delete-and-recreate, often the real blast radius
Permission changes IAM drift, widened roles, trust policy changes

Once you have a preview habit, the next failure mode you run into is drift, because previews assume your source of truth matches reality, and reality is rarely that polite.

State and drift

CloudFormation's story is conceptually simple.

CloudFormation manages the stack state as part of the service, and drift is what happens when something changes outside of CloudFormation management, which AWS notes can complicate stack update and deletion operations.

Drift detection lets you identify resources that differ from the expected template configuration, and the workflow usually looks like running a drift detection operation, inspecting which resources are MODIFIED or DELETED, and then reconciling by updating the resource back to match the template or updating the template to match the intentional change.

The key operational detail is that drift detection is an action you run. Teams that rely on it tend to schedule it or wire it into governance so drift becomes visible before it becomes catastrophic.

Terraform's drift is more flexible and more likely to cause issues down the line.

Terraform tracks your deployed infrastructure through a state file. The moment you have multiple humans applying changes, remote state storage and state locking stop being optional because you need a single source of truth, and you need to prevent concurrent applies from corrupting that truth.

Workspaces add another layer because they are effectively separate instances of state data, which is useful for multiple environments, but it also means you are responsible for naming, scoping, and preventing accidental cross-environment operations.

Bear in mind that drift is a process problem, not just a tool feature.

If engineers can change things through the AWS console, drift will happen, and the remediation path depends on the tool.

In CloudFormation, you usually bring reality back under stack control or update the stack definition to reflect the intentional change. In Terraform, you often decide between correcting the manual change via code and apply, importing or moving resources in state when ownership boundaries change, or explicitly accepting divergence if a resource is intentionally managed elsewhere.

Modularity and reuse

Once you have more than a couple of stacks or more than a couple of Terraform directories, reuse is what prevents your infrastructure code from becoming an archaeological dig, so you start thinking about how to ship patterns.

Terraform modules are the default reuse mechanism. You wrap a pattern, a VPC, an EKS cluster, a set of IAM roles into a module, and then you compose environments by wiring modules together with inputs and outputs.

That shapes repo structure in a predictable way, because teams often create a modules directory or separate module repos, then define environment folders that instantiate those modules with different variables, sometimes backed by Terraform workspaces, sometimes mapped to separate state backends, but always driven by the same basic idea that reuse is code, not copy-paste.

CloudFormation has three main reuse patterns. The right one depends on what you are trying to standardize.

When you want to break a large template into smaller templates and compose them nested stacks are the straightforward option. They work best when the boundaries are clean and you are comfortable with stack-level lifecycle management.

StackSets exist for organizational rollout, extending stacks so you can create, update, or delete stacks across multiple accounts and regions with a single operation – exactly what you want when baseline infrastructure must be consistent across an AWS org.

CloudFormation modules, introduced in late 2020, sit somewhere between a template fragment and a reusable building block registered in the CloudFormation registry, with benefits like traceability and manageability through versioning and account or regional availability.

The trade-off is that CloudFormation modules add a registry and versioning model you have to operate, and AWS notes that stack operations use whatever version is registered as the default in the account and region – a potential surprise if defaults diverge across accounts.

Portability and ecosystem

Terraform providers cover many different cloud providers and many different SaaS resources, and the “learn once, apply broadly” pitch is mostly true at the workflow level.

You learn how to structure Terraform configuration files, how to use modules, how state management works, and how to do a plan and apply safely. You can transfer those skills whether you are provisioning AWS infrastructure, Google Cloud, Microsoft Azure, or some third-party API.

The honest footnote is that implementations are provider-specific. Even within AWS, you'll run into edge cases where a resource behaves differently depending on the provider version, or on how AWS exposes update semantics. Portability is rarely about copying the same template – it’s more about adopting the same workflow and patterns across different cloud platforms.

CloudFormation's ecosystem is narrower, but deeper, and that depth matters for AWS-first teams. CloudFormation is part of the AWS control plane, fits naturally into IAM and Organizations, and often gets day-one support for new AWS services, as AWS can ship the resource type as part of the platform. For this reason, it’s especially attractive in regulated, AWS-native environments where deep integration is a requirement for how you pass audits and how you prove you are managing AWS resources with consistent guardrails.

Team workflows and CI/CD

In Terraform, GitHub pull requests appear to drive everything.

A DevOps engineer opens a PR with Terraform code changes, CI generates a plan so reviewers can see the proposed infrastructure changes, approvals gate whether apply is allowed, and then apply runs in a controlled way that writes back results and audit context.

Terrateam's workflow is a good example of this shape, because it automatically detects Terraform changes in a pull request and triggers a Plan operation, then supports an Apply operation triggered by commenting terrateam apply, keeping the deploy control plane inside the PR where review and audit already live.

If you want the apply step to be explicit and reproducible, Terrateam also documents applying against a stored plan file for changes in a pull request – the kind of detail that matters when you care about determinism.

In CloudFormation, teams often start inside the AWS console as it's convenient, then mature toward pipelines that create and execute change sets, with explicit approvals for production stacks and strong IAM boundaries around who can touch what.

A concrete workflow includes:

The biggest difference is not that one is more CI/CD, it's that Terraform's culture is plan-first – plan is a first-class artifact, and CloudFormation's culture is stack-lifecycle-first because the service itself is the orchestration engine.

Using Terraform and CloudFormation together

Mixing makes sense when you treat ownership boundaries as sacred. One common pattern is to call CloudFormation stacks from Terraform when a team wants to keep a stack-based deployment for a specific AWS service integration, or when an internal platform team has standardized on CloudFormation for baseline AWS account setup, while product teams use Terraform for application-level infrastructure and third-party providers.

Another pattern shows up during migrations, where you bridge by managing some components in CloudFormation and some in Terraform while you gradually consolidate, because rewriting everything at once is rarely realistic.

Just make sure you avoid overlapping ownership of the same resources, as two tools trying to manage identical resources is how you manufacture drift. Document boundaries explicitly, including which repo owns which cloud resources and which tool is the source of truth, and bake drift checks into the workflow so you find divergence early instead of discovering it during an incident.

CloudFormation drift detection and Terraform state discipline can complement each other at this point. CloudFormation can tell you when stack-managed resources changed outside the stack, and Terraform workflows can enforce plan review and apply controls so fewer changes happen outside code in the first place.

How to choose between Terraform and CloudFormation

A decision matrix is only useful if it reflects real constraints. The criteria below focus on what tends to change the outcome in production, not what looks good in a feature checklist.

Criteria Default toward Terraform Default toward CloudFormation
AWS-only footprint Possible, but you’ll still operate Terraform state A strong fit when AWS is the whole world
Multi-cloud environments A strong fit, providers across cloud platforms A weak fit, AWS-centric by design
Third-party SaaS resources A strong fit, many Terraform providers Possible via extensions, but not the happy path
Preview quality needs Plan-first workflows are the norm Change sets help, but do not guarantee update success
Governance and audit Usually enforced through CI/CD and policy tooling Strong alignment with AWS-native controls and service-managed lifecycle
Scale across accounts and regions Works, but you design the orchestration StackSets is purpose-built for org-scale rollout
Team skill mix Strong when teams like software-style workflows and modules Strong when teams live in AWS tooling and stack operations
State management appetite Do it well, including remote state and locking State is managed by the service
Template maintainability HCL and modules tend to stay readable as codebases grow Templates can grow complex, extensions can help, but add surface area

If you need multi-provider orchestration, whether that is multi-cloud, third-party SaaS resources, or just a single workflow that touches many different APIs, choose Terraform, because it was built to be that orchestration layer.

If you are operating solely within AWS and you value AWS-native lifecycle management, deep integration with Organizations, and stack-based control as the primary unit of governance, choose CloudFormation, especially when StackSets is the difference between deploying then and there and having to write a custom deployment system.

If you are AWS-only but your biggest risk is human workflow, not service coverage, a common exception is to default to Terraform anyway. The plan-driven model can be easier to operationalize under code review than a stack update flow that lives half in pipelines and half in the console.

Conclusion

The trade-off is simple, even if the details are not. CloudFormation shines when you want AWS-native management with stack-based control – which is especially true when you are managing AWS infrastructure across an AWS org, and you want the service to own state, rollbacks, and organizational rollout.

Terraform shines when you need multi-provider orchestration, Terraform modules for reuse, and a strong plan-driven workflow that makes infrastructure changes reviewable before they are real.

The hard part, once you pick Terraform or OpenTofu, is shipping changes safely and repeatably across a team without turning state management Terraform problems into a weekly fire drill.

Terrateam runs plans automatically on pull requests, supports apply via PR commands like terrateam apply, and supports the guardrails teams actually need at scale, policy checks, cost estimation, and drift detection – all inside a GitOps workflow that treats GitHub as the control plane instead of a place you paste screenshots after the fact.