Skip to content
ExponaBLOG
TERRAFORM, IAC & CLOUD AUTOMATION · DAY 041 / 100

Terraform vs CloudFormation: what developers should know

Two infrastructure-as-code tools compared.

Expona Admin7 min read

Terraform vs CloudFormation: what developers should know. Two infrastructure-as-code tools compared.

After five years running infrastructure across multiple cloud providers, we've watched teams stumble when choosing between Terraform and CloudFormation—often picking one without understanding their fundamental differences. Both are mature, battle-tested tools, but they solve different problems in different ways. Knowing when to reach for each will save you months of refactoring and regret.

The core comparison: side by side

Let's cut through the marketing and talk about what matters. Terraform and CloudFormation both let you declare infrastructure as code and provision it predictably. They both track state, both support conditionals and loops, and both integrate with your CI/CD pipeline. But the details matter.

Factor Terraform CloudFormation
Cloud support AWS, Azure, GCP, Kubernetes, 2000+ providers AWS only
State management You manage (local, S3, Terraform Cloud) AWS manages (no manual state files)
Language HCL (custom DSL) JSON or YAML
New AWS features Community-driven (can lag 6-12 weeks) Often available within days
Learning curve Moderate (HCL is learnable) Steep (verbose JSON, AWS quirks)
Managed state Operational overhead Built-in (one less thing to worry about)

This table is useful, but it hides the real tradeoff: Terraform trades state complexity for portability and simplicity; CloudFormation trades management burden for AWS-native features and speed. Let's dig into when each choice makes sense.

Cloud support: the portability argument

Terraform vs CloudFormation starts here. If your team manages only AWS and has no plans to touch Azure, GCP, or Kubernetes, you're not buying much from Terraform's multi-cloud story. But if you're running workloads across clouds or integrating with third-party SaaS platforms—Datadog, GitHub, PagerDuty, Okta—Terraform becomes the obvious choice.

We once worked with a fintech team running primary infrastructure on AWS but needing to provision Kafka clusters on Confluent Cloud and databases on MongoDB Atlas. They tried CloudFormation first and hit a wall: CloudFormation has no native support for non-AWS resources. They could build a custom Lambda function to wrap the API calls, but it was brittle and slow. Switching to Terraform took three days and gave them a single source of truth across all three platforms.

hcl

# Terraform: one tool for AWS, Datadog, and GitHub
provider "aws" {
  region = "us-east-1"
}

provider "datadog" {
  api_key = var.datadog_api_key
}

provider "github" {
  token = var.github_token
}

resource "aws_lambda_function" "processor" {
  filename = "processor.zip"
  role     = aws_iam_role.lambda_role.arn
  # ...
}

resource "datadog_monitor" "lambda_errors" {
  type    = "metric alert"
  name    = "Lambda error rate"
  query   = "avg:aws.lambda.errors{function_name:processor} > 10"
}

resource "github_repository_deploy_key" "terraform" {
  repository = "infrastructure"
  title      = "Terraform CI/CD"
  key        = tls_private_key.terraform.public_key_openssh
  read_only  = false
}

That's powerful. CloudFormation simply cannot do it without gymnastics.

State management: the hidden complexity

Here's where many Terraform teams get bitten. Terraform stores state in a file—locally by default, but you'll want to move it to S3 with DynamoDB locking for a team. This state file is the source of truth for what Terraform thinks exists. Lose it, corrupt it, or get it out of sync with reality, and you're in a world of pain.

CloudFormation delegates state management to AWS. You never touch a state file. When you update a stack, AWS handles the diffing and applies only the changes. This is simpler operationally, and we've seen teams sleep better knowing they don't have to backup and version-control their state.

But Terraform's explicit state gives you power: you can inspect it, modify it (rarely), and understand exactly what Terraform thinks is deployed. CloudFormation's magic state is convenient until it isn't—you're debugging in the AWS console, not your source code.

hcl

# Terraform backend: you own this
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# To inspect or debug:
# terraform state list
# terraform state show aws_rds_cluster.primary
# terraform state pull | jq .

CloudFormation avoids this entirely. Stack metadata lives in AWS, versioned and accessible via the console or API. Trade-off: less visibility, but less operational burden.

Language: HCL vs JSON/YAML

Terraform uses HCL, a domain-specific language designed for infrastructure. It's more readable than JSON and includes loops, conditionals, and functions out of the box.

CloudFormation templates are JSON (sometimes YAML). JSON is universally understood but verbose and repetitive. CloudFormation YAML is slightly better, but the language itself has quirks: CloudFormation-specific functions like !Ref, !Sub, and !GetAtt make templates harder to read if you're not steeped in AWS culture.

hcl

# Terraform: loops are straightforward
resource "aws_security_group" "allow_apps" {
  for_each = toset(["web", "api", "worker"])

  name = "sg-${each.value}"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.main.cidr_block]
  }
}

CloudFormation can do this with intrinsic functions, but it's noisier. If you've never written HCL, it's easy to learn. If you prefer working in a general-purpose language, that's what AWS CDK is for.

New AWS features: the timing gap

AWS launches features constantly. CloudFormation usually supports them within days because AWS owns both the service and the IaC tool. Terraform's support depends on the community: Hashicorp maintains the AWS provider, but they're not always first in line when AWS adds a feature.

We've seen teams need to wait 8–12 weeks for Terraform to support a new RDS feature, while CloudFormation had it immediately. If you're on the bleeding edge of AWS innovation, this matters. If you're running stable, mature infrastructure, it rarely does.

When to choose Terraform

Choose Terraform if:

  • You manage infrastructure across multiple clouds or platforms.
  • You want a cleaner, more intuitive language than JSON/YAML.
  • You're comfortable owning state management (or using Terraform Cloud).
  • You value a large community and ecosystem of modules.
  • You need to provision non-AWS resources (SaaS, Kubernetes, third-party APIs).

Terraform is the default choice for teams that operate across cloud providers or need to manage hybrid infrastructure. It's mature, widely used, and the ecosystem is rich.

When to choose CloudFormation

Choose CloudFormation if:

  • Your entire infrastructure lives on AWS and you have no plans to change that.
  • You want AWS to manage state and versioning for you.
  • You need new AWS features immediately upon launch.
  • Your team prefers not to manage terraform state files.
  • You're already deep in the AWS ecosystem (Lambda, Step Functions, CodePipeline) and want tight integration.

CloudFormation works exceptionally well for AWS-only shops. The operational simplicity is real, and you get native support for every AWS feature within days.

The third option: AWS CDK

If you hate JSON/YAML but love CloudFormation's AWS-native speed and managed state, there's a middle path: AWS CDK (Cloud Development Kit). CDK lets you write infrastructure in TypeScript, Python, or Go. It compiles down to CloudFormation templates, so you get CloudFormation's benefits with a general-purpose language.

hcl

# CDK (TypeScript): feels like real programming
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';

export class MyStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const fn = new lambda.Function(this, 'MyFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda'),
    });

    new apigateway.LambdaRestApi(this, 'MyApi', {
      handler: fn,
    });
  }
}

CDK is powerful if your team already codes in TypeScript or Python. You get language abstractions, reusable constructs, and fast iteration. The downside: it adds a compilation step, and the generated CloudFormation can be harder to debug.

Common mistakes

Mistake 1: Choosing based on hype alone. Terraform is trendy, but that doesn't mean it's right for an AWS-only team. Evaluate your actual needs, not the zeitgeist.

Mistake 2: Not versioning terraform state. Your state file is production data. Store it in S3 with versioning enabled, use DynamoDB for locking, and encrypt it. Treat it like a database backup.

Mistake 3: Mixing manual AWS console changes with IaC. Whether Terraform or CloudFormation, drift happens when engineers click buttons in the console. Enforce a rule: all infrastructure changes go through code. Use AWS Config or drift detection to catch violations.

Mistake 4: Underestimating CloudFormation's power. Many teams dismiss CloudFormation as "just JSON" and move to Terraform without understanding CloudFormation's benefits. If you're AWS-only, CloudFormation + CDK is often the right choice.

Mistake 5: Not testing your IaC. Whether Terraform or CloudFormation, test your templates in a dev environment before pushing to prod. Use terraform plan, CloudFormation change sets, or CDK synth to preview changes.

Key takeaways

  • Terraform excels at multi-cloud and multi-platform infrastructure; CloudFormation is AWS-native and state-managed by AWS.
  • HCL is more readable than JSON/YAML, but CloudFormation features land faster on AWS.
  • Choose Terraform if you need portability or non-AWS resources. Choose CloudFormation if you're AWS-only and prefer managed state.
  • AWS CDK bridges the gap: CloudFormation backend with TypeScript/Python language.
  • Both tools require discipline: version your state, test changes, and prevent manual drift.
  • The "best" tool depends on your team's infrastructure footprint and operational comfort, not industry trends.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles