CloudFormation vs Terraform — Which IaC Tool Should You Choose?
Advertisement
Introduction
Why This Matters
Choosing between CloudFormation and Terraform is one of the most consequential infrastructure decisions a team makes. Both tools provision cloud resources from declarative templates, but they differ in provider support, language expressiveness, state management, and ecosystem maturity. Getting this choice right affects developer productivity, portability, and operational complexity for years.
Head-to-Head Comparison
| Aspect | CloudFormation | Terraform |
|---|---|---|
| Provider support | AWS only | 1,000+ providers |
| Language | JSON or YAML | HCL (HashiCorp Configuration Language) |
| State management | Managed by AWS (Stacks) | Explicit state file (local or remote) |
| Drift detection | Built-in stack drift detection | terraform plan shows drift |
| Learning curve | Moderate (verbose syntax) | Moderate (concise HCL) |
| Loops and conditionals | Limited (Conditions, FindInMap) | Full (for_each, count, dynamic) |
| Error messages | Often cryptic, stack events | Generally clearer with resource context |
| Cost | Free (pay for resources) | Free + Terraform Cloud paid tiers |
| Community | AWS-centric | Massive open source community |
| Module reuse | Nested stacks, StackSets | Registry modules, workspace |
CloudFormation Example
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: Web application stack
Parameters:
Environment:
Type: String
AllowedValues: [development, staging, production]
InstanceType:
Type: String
Default: t3.micro
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Resources:
WebServerInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: !FindInMap [AMIMap, !Ref AWS::Region, AMI]
InstanceType: !If [IsProduction, t3.medium, !Ref InstanceType]
SecurityGroupIds:
- !Ref WebSecurityGroup
Tags:
- Key: Name
Value: !Sub '${Environment}-web-server'
WebSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Web server security group
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub '/${Environment}/db-password'
GenerateSecretString:
PasswordLength: 32
Outputs:
InstancePublicIP:
Value: !GetAtt WebServerInstance.PublicIp
Export:
Name: !Sub '${Environment}-WebIP'# Deploy CloudFormation stack
aws cloudformation deploy \
--stack-name my-app-production \
--template-file template.yaml \
--parameter-overrides Environment=production \
--capabilities CAPABILITY_IAM
# View stack events
aws cloudformation describe-stack-events \
--stack-name my-app-production
# Delete stack
aws cloudformation delete-stack \
--stack-name my-app-productionTerraform Equivalent
# main.tf
variable "environment" {
type = string
default = "development"
validation {
condition = contains(["development", "staging", "production"], var.environment)
error_message = "Environment must be development, staging, or production."
}
}
locals {
instance_type = var.environment == "production" ? "t3.medium" : "t3.micro"
common_tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = local.instance_type
vpc_security_group_ids = [aws_security_group.web.id]
tags = merge(local.common_tags, {
Name = "${var.environment}-web-server"
})
}
resource "aws_security_group" "web" {
name = "${var.environment}-web-sg"
description = "Web server security group"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_secretsmanager_secret" "db" {
name = "/${var.environment}/db-password"
}# Terraform workflow
terraform init
terraform workspace new production
terraform plan -var="environment=production"
terraform apply -var="environment=production"
terraform destroy -var="environment=production"When to Choose CloudFormation
- AWS-only infrastructure: You have no current or planned multi-cloud requirements
- Tight AWS service integration: Need native support for new AWS services on day-one release
- No state management overhead: AWS manages stack state — no S3 bucket or DynamoDB table to maintain
- StackSets: Need to deploy identical infrastructure to multiple AWS accounts and regions
- Compliance requirements: Some organizations require AWS-native tooling for audit trails
When to Choose Terraform
- Multi-cloud or hybrid: Managing resources across AWS, GCP, Azure, and SaaS providers (Datadog, PagerDuty, etc.)
- Richer language features:
for_each,dynamicblocks, and complex expressions reduce template duplication - Existing expertise: Team already knows HCL or Terraform from previous projects
- Module ecosystem: Terraform Registry provides mature, tested modules for common patterns
- Readable plans:
terraform planoutput is generally clearer than CloudFormation change sets
Common Mistakes
- Mixing CloudFormation and Terraform for the same resources — cross-tool state creates complex dependency issues
- Using CloudFormation Nested Stacks without understanding the 500-resource limit per stack
- Committing
terraform.tfstateto Git — use S3 with DynamoDB locking for team environments - Not testing CloudFormation templates with
cfn-lintbefore deployment — syntax errors only surface at deploy time - Assuming CloudFormation rollback on failure is reliable — some resources partially create and require manual cleanup
Best Practices
- Standardize on one tool per team or organization — tool fragmentation increases operational overhead
- Use
cfn-lintfor CloudFormation andterraform validate+tflintfor Terraform in CI - Modularize both tools — CloudFormation Nested Stacks or Terraform modules prevent monolithic templates
- Always preview changes before applying —
--change-setfor CloudFormation,terraform plan -out=tfplanfor Terraform - Tag resources consistently — both tools support tagging; enforce via SCPs or Sentinel policies
Key Takeaways
- CloudFormation is AWS-native and requires no external state management — ideal for AWS-only organizations
- Terraform supports 1,000+ providers — the only choice for multi-cloud or hybrid infrastructure
- CloudFormation manages state automatically via Stacks; Terraform requires an explicit remote backend for team use
- Terraform HCL is more expressive —
for_each,dynamicblocks, and locals reduce repetition versus CloudFormation conditions - Both tools support infrastructure modules for reuse — CloudFormation Nested Stacks vs Terraform Registry modules
- New AWS services are supported in CloudFormation on or near launch day; Terraform community providers may lag
terraform planand CloudFormation change sets both preview changes before application — always use them- Switching IaC tools mid-project requires import tooling and is expensive — choose deliberately and standardize early
Advertisement
Related reading
Terraform Infrastructure Guide 2026 — Infrastructure as Code for AWS, GCP, and Azure6 min readTerraform Modules at Scale — Reusable, Versioned Infrastructure Components8 min readTesting Infrastructure as Code — Terratest, Policy-as-Code, and Shift-Left IaC8 min readPulumi With TypeScript — Infrastructure as Real Code, Not YAML7 min readTerraform at Scale — State Management, Module Versioning, and Team Workflows7 min readAI Tools for DevOps — Generate Dockerfiles, CI/CD Pipelines, and Kubernetes Manifests5 min read