CloudFormation vs Terraform — Which IaC Tool Should You Choose?

Sanjeev SharmaSanjeev Sharma
5 min read

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

AspectCloudFormationTerraform
Provider supportAWS only1,000+ providers
LanguageJSON or YAMLHCL (HashiCorp Configuration Language)
State managementManaged by AWS (Stacks)Explicit state file (local or remote)
Drift detectionBuilt-in stack drift detectionterraform plan shows drift
Learning curveModerate (verbose syntax)Moderate (concise HCL)
Loops and conditionalsLimited (Conditions, FindInMap)Full (for_each, count, dynamic)
Error messagesOften cryptic, stack eventsGenerally clearer with resource context
CostFree (pay for resources)Free + Terraform Cloud paid tiers
CommunityAWS-centricMassive open source community
Module reuseNested stacks, StackSetsRegistry 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-production

Terraform 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, dynamic blocks, 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 plan output 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.tfstate to Git — use S3 with DynamoDB locking for team environments
  • Not testing CloudFormation templates with cfn-lint before 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-lint for CloudFormation and terraform validate + tflint for Terraform in CI
  • Modularize both tools — CloudFormation Nested Stacks or Terraform modules prevent monolithic templates
  • Always preview changes before applying — --change-set for CloudFormation, terraform plan -out=tfplan for 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, dynamic blocks, 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 plan and 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

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading