Updated Oct 11, 202611 min read

Terraform vs CloudFormation in 2026

The AWS-committed team's real decision, with the tradeoffs on both sides

For a team already committed to AWS, the Terraform versus CloudFormation question is narrower than it looks. Both provision the same underlying AWS resources through the same APIs, so the fight is not about capability. It is about who manages the state, how quickly new services get covered, what language you write, and whether you want to keep the door open to running somewhere other than AWS.

Both tools have a real case here. CloudFormation is a first-party AWS service that quietly removes an entire category of operational work. Terraform brings a better language, a much larger community, and multi-cloud reach that a given team may or may not ever use. The sections below walk through the concrete differences with working templates for both, and finish with a plain recommendation for each situation. We also cover a third approach, Encore, which declares infrastructure in application code and provisions it in your own AWS or GCP account.

Terraform vs CloudFormation at a Glance

CloudFormationTerraformEncore
VendorAWS (first-party)HashiCorp (now IBM)Encore (open source)
CloudsAWS onlyAWS, GCP, Azure, and hundreds more via providersAWS and GCP, in your own account
LanguageYAML or JSON (or CDK synth)HCLTypeScript or Go application code
StateManaged by AWS, nothing to storeState file you store and lock yourself (S3 + DynamoDB, or a backend)Nothing to manage: the application code is the source of truth
New-service coverageOften on launch dayUsually close behind; occasionally ahead via Cloud ControlEncore's supported primitives; Terraform alongside for the rest
Rollback on failureAutomatic, built inManual; failed apply leaves partial stateNot covered here
Drift detectionBuilt-in (detect-stack-drift)terraform plan shows drift on next runNot covered here
Module ecosystemSmaller (registry + CDK constructs)Large public Registry, mature modulesTyped infrastructure primitives
LicenseProprietary AWS service, no cost for coreBSL 1.1MPL 2.0
Infrastructure in application codeNoNoYes
Local run against real infrastructureNoNoYes

State Handling

This is the single biggest operational divergence.

CloudFormation has no state file. When you deploy a stack, AWS records what it created and tracks the mapping between your template and the live resources inside the CloudFormation service. There is nothing for you to store, encrypt, back up, or lock. Two engineers cannot corrupt state by running deploys at the same time, because the service serializes stack operations for you. For a team that has ever spent an afternoon recovering a mangled Terraform state file, this is a real relief.

Terraform keeps a state file that maps your HCL to real resource IDs. You are responsible for storing it somewhere durable, usually an S3 bucket, and for locking it so two apply runs do not clobber each other, historically with a DynamoDB table (Terraform 1.10+ can also lock via S3 directly). Get this wrong and you can end up with drifted or duplicated resources. Get it right and it is invisible, but getting it right is setup and discipline that CloudFormation does not ask of you.

The flip side: because Terraform owns its state, you can inspect it, move resources between states, import existing infrastructure, and refactor with terraform state mv. CloudFormation's state is opaque. Reorganizing which stack owns a resource is more awkward, though import and stack refactoring support have improved.

Example: An S3 Bucket and a Lambda

Here is the same small piece of infrastructure in both tools: a private S3 bucket and a Lambda function that has permission to read from it.

CloudFormation (YAML)

AWSTemplateFormatVersion: "2010-09-09" Resources: DataBucket: Type: AWS::S3::Bucket Properties: BucketName: my-app-data-bucket PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true ProcessorRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: read-bucket PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: s3:GetObject Resource: !Sub "${DataBucket.Arn}/*" Processor: Type: AWS::Lambda::Function Properties: FunctionName: data-processor Runtime: nodejs20.x Handler: index.handler Role: !GetAtt ProcessorRole.Arn Code: S3Bucket: my-deploy-artifacts S3Key: processor.zip

Deploy it:

aws cloudformation deploy \ --template-file template.yaml \ --stack-name data-pipeline \ --capabilities CAPABILITY_IAM

If the Lambda creation fails, CloudFormation automatically rolls back the whole stack to its last good state. You do not get a half-created pipeline.

Terraform (HCL)

resource "aws_s3_bucket" "data" { bucket = "my-app-data-bucket" } resource "aws_s3_bucket_public_access_block" "data" { bucket = aws_s3_bucket.data.id block_public_acls = true block_public_policy = true ignore_public_acls = true restrict_public_buckets = true } resource "aws_iam_role" "processor" { name = "data-processor-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } Action = "sts:AssumeRole" }] }) } resource "aws_iam_role_policy" "read_bucket" { role = aws_iam_role.processor.id policy = jsonencode({ Version = "2012-10-17" Statement = [{ Effect = "Allow" Action = "s3:GetObject" Resource = "${aws_s3_bucket.data.arn}/*" }] }) } resource "aws_lambda_function" "processor" { function_name = "data-processor" runtime = "nodejs20.x" handler = "index.handler" role = aws_iam_role.processor.arn s3_bucket = "my-deploy-artifacts" s3_key = "processor.zip" }

Deploy it:

terraform init terraform plan terraform apply

Two things stand out. First, HCL lets you reference resources with expressions like aws_s3_bucket.data.arn and use functions like jsonencode, which most engineers find more pleasant than CloudFormation's !GetAtt and !Sub intrinsic functions. Second, terraform plan shows you a precise diff before anything changes, which is better than CloudFormation change sets for readability, though change sets have closed much of that gap.

Notice also that Terraform split the public-access block into its own resource. This is common: the AWS provider tends to break AWS API surfaces into more granular resources than a single CloudFormation type, which is more verbose but often maps more directly to the underlying API calls.

New-Service Coverage

A long-standing argument for CloudFormation is that AWS ships CloudFormation support for new services at or near launch, because it is a first-party product. This is largely true and matters if you adopt new AWS services aggressively.

The gap is narrower than it used to be. The AWS Terraform provider is actively maintained and usually adds new resources within weeks of a service GA. HashiCorp also ships the awscc provider built on the AWS Cloud Control API, which auto-generates resources from the same models CloudFormation uses, so some resource types appear there first. In practice, mainstream services are covered well by both. If you are an early adopter of niche AWS services on launch day, CloudFormation still has a slight edge.

Drift, Rollback, and Day-Two Operations

Rollback. CloudFormation's automatic rollback on a failed deployment is a real advantage. A failed terraform apply leaves you with partially applied changes and state that no longer matches reality, and you clean it up yourself. CloudFormation returns the stack to its last healthy state without intervention.

Drift. Both handle out-of-band changes, differently. CloudFormation has explicit drift detection you trigger with aws cloudformation detect-stack-drift, which reports resources that were changed outside the stack. Terraform surfaces drift implicitly: the next terraform plan shows anything that no longer matches your configuration. Terraform's model means you see drift every time you plan, which many teams prefer. See our guide to Terraform drift for how to manage it.

Blast radius. CloudFormation stacks have hard limits (resource counts per stack, nested-stack depth) that push large estates toward many stacks with cross-stack references, which gets fiddly. Terraform's module system and workspaces give you more flexible ways to slice a large estate, at the cost of you designing that structure yourself.

Language and Ecosystem

HCL is purpose-built for infrastructure and most engineers ramp on it quickly. Raw CloudFormation YAML becomes hard to manage past a few hundred lines, which is exactly why AWS built the CDK: you write TypeScript, Python, Go, or Java, and it synthesizes CloudFormation templates. If you are staying on the CloudFormation engine, CDK is usually the better authoring experience. Note that CDK still deploys through CloudFormation, so every runtime tradeoff above (managed state, automatic rollback, AWS-only) still applies. For a deeper look see AWS CDK vs Terraform.

On modules, Terraform's public Registry is larger and more mature than CloudFormation's equivalents. For common patterns (a VPC, an EKS cluster, an RDS instance with sane defaults) there is usually a well-worn Terraform module. CloudFormation's reuse story runs through CDK constructs and the (smaller) public registry.

Encore: Infrastructure in Application Code

Encore

Both of the other tools are similar in that infrastructure lives in a separate language, in a separate codebase, applied by a separate tool, and your application code knows nothing about it. For a team whose infrastructure is mostly application-level primitives (a Postgres database, a queue, a cron job, object storage) there is a third option that skips the template layer entirely.

Encore is an open-source backend framework (around 12k GitHub stars, used in production across hundreds of companies, including Coinbase (Echo) and Groupon) where you declare those resources directly in TypeScript or Go, and the Encore platform provisions the real AWS or GCP services in your own account.

todo/db.ts
import { SQLDatabase } from "encore.dev/storage/sqldb";

const db = new SQLDatabase("todo", {
  migrations: "./migrations",
});
AWSAWSAmazon RDS
GCPCloud SQL

Managed PostgreSQL with migrations, pooling and IAM auth

Each primitive is declared in application code, runs locally under encore run, and maps to the matching managed service when you deploy to AWS or GCP.

Learn more about how Encore automates infrastructure →

There is no S3 template, no IAM role YAML, and no state file. Each resource and its permissions are derived from the code. Because the infrastructure lives in the application code, a coding agent reads it the same way it reads the rest of your program. Encore adds an MCP server and editor rules (encore llm-rules init) that give agents your schema, traces, and architecture as context, so agent-written backends follow consistent conventions and stay deployable.

This does not replace CloudFormation or Terraform for teams managing VPCs, networking, and IAM at scale, and that boundary is real. Those tools stay in place for anything outside Encore's backend primitives. What it removes is the template sprawl that accumulates around a typical backend, and it is most compelling on new projects where you are not already invested in an IaC estate.

Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

The quick start walks through a first deploy in a few minutes.

How to Choose

Start at the top and follow the first question you can answer with a yes:

Question 1

Is infrastructure becoming the delivery bottleneck as agents write more application code?

Yes →
EncoreInfrastructure declared in application code, provisioned in your own AWS or GCP account
Question 2

Do you want infrastructure to ship in the same pull request as the feature, without a separate state file?

Yes →
EncoreInfrastructure ships in the same pull request, with no separate state file
Question 3

Is there a realistic chance you will run on more than one cloud, or manage non-AWS resources from the same tool?

Yes →
TerraformAny provider, with a large module ecosystem
or
EncoreThe same code deploys to AWS or GCP
Question 4

Will you stay AWS-only and want managed state, automatic rollback, and launch-day coverage?

Yes →
CloudFormationFirst-party AWS, with no state file to manage
Question 5

Do you have a large platform team that can tune and optimize each resource by hand?

Yes →
TerraformExplicit control over every resource and its wiring

In more detail:

When to Choose CloudFormation

  • You are confident you will stay AWS-only for the foreseeable future.
  • You want to eliminate state-file operations entirely and never think about locking, storage, or corruption.
  • Automatic rollback on failed deployments is worth a lot to your team.
  • You adopt new AWS services early and want launch-day coverage.
  • You are happy authoring with CDK rather than raw YAML.
  • Your org has compliance or procurement reasons to stay inside first-party AWS tooling.

When to Choose Terraform

  • There is any realistic chance you will run on more than one cloud, or manage non-AWS resources (Cloudflare, Datadog, GitHub, PagerDuty) from the same tool.
  • You want the larger module ecosystem and the HCL developer experience.
  • You value inspectable, refactorable state (state mv, import, targeted plans).
  • Your team already knows Terraform and the switching cost is not worth it.
  • You are not blocked by the BSL license in your context. If you are, see OpenTofu vs Terraform for the MPL-licensed fork.

A common and reasonable answer is both: Terraform as the primary tool, with aws_cloudformation_stack or a CDK stack wrapped in for the handful of resources where CloudFormation coverage is ahead.

Frequently asked questions

Is Terraform or CloudFormation better for an AWS-only shop?

If you are certain you will stay AWS-only and want zero state-file operations, CloudFormation is the lower-maintenance choice because AWS tracks state for you and rolls back failed deployments automatically. Terraform wins if you want the larger module ecosystem, a nicer language, or any chance of multi-cloud.

Does CloudFormation have a state file like Terraform?

No. CloudFormation stores stack state inside the AWS service itself, so there is no state file to store, lock, or secure. Terraform keeps a state file that you must store in a backend such as S3 with DynamoDB locking.

Is CloudFormation always up to date with new AWS services faster than Terraform?

Usually but not always. New AWS services often ship with CloudFormation support on or near launch day. The AWS Terraform provider is generally close behind, and some resources have historically landed in Terraform first through the AWS Cloud Control provider.

Can I use Terraform and CloudFormation together?

Yes. Teams commonly manage most infrastructure in Terraform and wrap a CloudFormation stack with the aws_cloudformation_stack resource when a service only has CloudFormation coverage, or use CDK (which compiles to CloudFormation) for parts of the estate.

Is CloudFormation free?

CloudFormation itself has no charge for the core service and for AWS resource types. You pay for the AWS resources it provisions. Extension resource types and third-party registry types are billed per handler operation.

Should I write raw CloudFormation YAML or use CDK?

Most teams that stay on the CloudFormation engine now author with AWS CDK in TypeScript or Python and let it synthesize the templates, because raw YAML gets unwieldy past a few hundred lines. CDK still deploys through CloudFormation, so the runtime tradeoffs in this article still apply.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders