// Stay in touch?
Products
Encore CloudEncore Cloud
Encore.tsEncore.ts
Encore.goEncore.go
PricingPricing
Book a DemoBook a Demo
Use Cases
AI-Powered DevelopmentAI-Powered Development
Event-Driven SystemsEvent-Driven Systems
Distributed SystemsDistributed Systems
Case StudiesCase Studies
ShowcaseShowcase
Resources
DocsDocs
InstallInstall
Example AppsExample Apps
Demo videoDemo video
ArticlesArticles
GitHub ReleasesGitHub Releases
Systems Operational
Company
About UsAbout Us
Swag ShopSwag Shop
ContactContact
JobsJobs
PressPress
TermsTerms
Privacy PolicyPrivacy Policy
Data Processing AgreementData Processing Agreement
Enterprise SLAEnterprise SLA
Encore
© 2026 EncoreAll rights reserved
© 2026 Encore All Rights Reserved
GitHubDiscordYouTube

Terraform vs CloudFormation in 2026

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

07/08/26
9 Min Read
Ivan Cernja
07/08/26

Terraform vs CloudFormation in 2026

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

Ivan Cernja
9 Min Read

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.

Terraform vs CloudFormation at a Glance

CloudFormationTerraform
VendorAWS (first-party)HashiCorp (now IBM)
CloudsAWS onlyAWS, GCP, Azure, and hundreds more via providers
LanguageYAML or JSON (or CDK synth)HCL
StateManaged by AWS, nothing to storeState file you store and lock yourself (S3 + DynamoDB, or a backend)
New-service coverageOften on launch dayUsually close behind; occasionally ahead via Cloud Control
Rollback on failureAutomatic, built inManual; failed apply leaves partial state
Drift detectionBuilt-in (detect-stack-drift)terraform plan shows drift on next run
Module ecosystemSmaller (registry + CDK constructs)Large public Registry, mature modules
LicenseProprietary AWS service, no cost for coreBSL 1.1

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.

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.

Declaring Infrastructure in Application Code

Both tools share the same shape: infrastructure lives in a separate language, in a separate codebase, applied by a separate tool, and your application code knows nothing about it. For an AWS-committed 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 by teams including Groupon) where you declare those resources directly in TypeScript or Go, and Encore Cloud provisions the real AWS services in your own account.

import { api } from "encore.dev/api"; import { SQLDatabase } from "encore.dev/storage/sqldb"; import { Bucket } from "encore.dev/storage/objects"; // Provisions RDS Postgres in your AWS account. const db = new SQLDatabase("app", { migrations: "./migrations" }); // Provisions a private S3 bucket in your AWS account. const uploads = new Bucket("uploads"); export const record = api( { method: "POST", path: "/events", expose: true }, async (req: { name: string }) => { await db.exec`INSERT INTO events (name) VALUES (${req.name})`; }, );

There is no S3 template, no IAM role YAML, and no state file. The bucket, database, and their 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.

Deploy with Encore

Provision Postgres and object storage in your own AWS account without writing a single CloudFormation template.

Deploy

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

Related Resources

  • AWS CDK vs Terraform
  • AWS CloudFormation explained
  • OpenTofu vs Terraform in 2026
  • Terraform drift, and how to manage it
  • Encore vs Terraform, CDK, Pulumi, and SST

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.

Contents
Terraform vs CloudFormation at a Glance
State Handling
Example: An S3 Bucket and a Lambda
CloudFormation (YAML)
Terraform (HCL)
New-Service Coverage
Drift, Rollback, and Day-Two Operations
Language and Ecosystem
When to Choose CloudFormation
When to Choose Terraform
Declaring Infrastructure in Application Code
Related Resources
Encore

The backend platform for humans and agents

Build backends in Go or TypeScript and run them on your own AWS or GCP, with the guardrails your team and AI agents need to ship safely.

Ready to build your next backend?

Encore is the Open Source framework for building robust type-safe distributed systems with declarative infrastructure.