Terraform vs CloudFormation vs CDK in 2026: When to Use Each and How to Migrate
Compare Terraform, AWS CloudFormation, and AWS CDK in 2026: syntax, state management, multi-cloud, testing, drift detection, and migration strategy from CloudFormation to Terraform.
Terraform vs CloudFormation vs CDK in 2026: When to Use Each and How to Migrate
Three tools dominate infrastructure as code for AWS in 2026: Terraform (HashiCorp/OpenTofu), AWS CloudFormation, and AWS CDK. Each has a legitimate place and each has failure modes when misapplied. Teams routinely pick the wrong tool, then spend months fighting it.
This post compares all three across the dimensions that actually matter: multi-cloud reach, state management, testing story, drift detection, and team onboarding. It also covers the practical migration path from CloudFormation to Terraform for teams that want more flexibility.
Quick Comparison
| Dimension | Terraform / OpenTofu | CloudFormation | AWS CDK |
|---|---|---|---|
| Language | HCL (DSL) | YAML / JSON | TypeScript, Python, Java, Go |
| Multi-cloud | ✅ Azure, GCP, etc. | ❌ AWS only | ❌ AWS only (uses CFN under the hood) |
| State management | External (S3 + DynamoDB) | Managed by AWS | Managed by AWS (via CFN stacks) |
| Drift detection | terraform plan | Stack drift detection | CDK diff (via CFN) |
| Preview changes | terraform plan | Change sets | cdk diff |
| Testing | Terratest, tftest | CloudFormation Guard, cfn-lint | CDK assertions (Jest) |
| Rollback | Manual (state manipulation) | Automatic on failure | Automatic (via CFN) |
| Resource coverage | ~3,000 providers | Every AWS service (day-zero) | Every AWS service (via CFN) |
| License | BSL (OpenTofu = MPL) | Free (AWS service) | Apache 2.0 |
| Learning curve | Medium | Medium | Low (if team knows TypeScript) |
Terraform: Best For Multi-Cloud and Large Teams
# terraform/modules/app-service/main.tf
# Variables-first, then locals, then resources
variable "name" { type = string }
variable "environment" { type = string }
variable "image_uri" { type = string }
variable "memory" { type = number; default = 512 }
variable "cpu" { type = number; default = 256 }
variable "subnet_ids" { type = list(string) }
variable "vpc_id" { type = string }
locals {
full_name = "${var.name}-${var.environment}"
common_tags = {
Name = local.full_name
Environment = var.environment
ManagedBy = "terraform"
}
}
resource "aws_ecs_task_definition" "app" {
family = local.full_name
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = var.cpu
memory = var.memory
execution_role_arn = aws_iam_role.task_execution.arn
container_definitions = jsonencode([{
name = var.name
image = var.image_uri
essential = true
portMappings = [{ containerPort = 3000, protocol = "tcp" }]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = "/ecs/${local.full_name}"
"awslogs-region" = data.aws_region.current.name
"awslogs-stream-prefix" = "ecs"
}
}
}])
tags = local.common_tags
}
resource "aws_ecs_service" "app" {
name = local.full_name
cluster = var.cluster_id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2
launch_type = "FARGATE"
network_configuration {
subnets = var.subnet_ids
security_groups = [aws_security_group.app.id]
assign_public_ip = false
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = var.name
container_port = 3000
}
tags = local.common_tags
lifecycle {
ignore_changes = [desired_count] # Managed by auto-scaling
}
}
Terraform shines when:
- Infrastructure spans AWS + Azure + GCP + third-party (Datadog, Cloudflare)
- Team size > 5 engineers with shared state
- You need plan/apply review gates in CI/CD
- Compliance requires drift detection on every deploy
☁️ Is Your Cloud Costing Too Much?
Most teams overspend 30–40% on cloud — wrong instance types, no reserved pricing, bloated storage. We audit, right-size, and automate your infrastructure.
- AWS, GCP, Azure certified engineers
- Infrastructure as Code (Terraform, CDK)
- Docker, Kubernetes, GitHub Actions CI/CD
- Typical audit recovers $500–$3,000/month in savings
CloudFormation: Best For AWS-Only, Smaller Teams
# cloudformation/app-stack.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Application stack with ECS Fargate"
Parameters:
Environment:
Type: String
AllowedValues: [dev, staging, production]
ImageUri:
Type: String
VpcId:
Type: AWS::EC2::VPC::Id
Resources:
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub "myapp-${Environment}"
NetworkMode: awsvpc
RequiresCompatibilities: [FARGATE]
Cpu: "256"
Memory: "512"
ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn
ContainerDefinitions:
- Name: app
Image: !Ref ImageUri
Essential: true
PortMappings:
- ContainerPort: 3000
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Sub "/ecs/myapp-${Environment}"
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ecs
EcsService:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref EcsCluster
TaskDefinition: !Ref TaskDefinition
DesiredCount: 2
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
Subnets: !Ref PrivateSubnets
SecurityGroups: [!Ref AppSecurityGroup]
Outputs:
ServiceArn:
Value: !Ref EcsService
Export:
Name: !Sub "${Environment}-AppServiceArn"
CloudFormation shines when:
- 100% AWS and AWS-native features (StackSets, Service Catalog, Control Tower)
- Automatic rollback on deploy failure is critical
- Small team without Terraform expertise
- AWS managed service integrations (Lake Formation, Config, etc.)
AWS CDK: Best For Developer-Led Infra in TypeScript
// lib/app-stack.ts
import * as cdk from "aws-cdk-lib";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ecsPatterns from "aws-cdk-lib/aws-ecs-patterns";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
export class AppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: AppStackProps) {
super(scope, id, props);
const vpc = ec2.Vpc.fromLookup(this, "Vpc", { vpcId: props.vpcId });
const cluster = new ecs.Cluster(this, "Cluster", { vpc });
// High-level pattern handles ALB + Fargate service + task definition
const service = new ecsPatterns.ApplicationLoadBalancedFargateService(
this,
"AppService",
{
cluster,
cpu: 256,
memoryLimitMiB: 512,
desiredCount: 2,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry(props.imageUri),
containerPort: 3000,
environment: {
NODE_ENV: props.environment,
},
},
publicLoadBalancer: true,
assignPublicIp: false,
}
);
// Auto-scaling
const scaling = service.service.autoScaleTaskCount({ maxCapacity: 10 });
scaling.scaleOnCpuUtilization("CpuScaling", {
targetUtilizationPercent: 70,
scaleInCooldown: cdk.Duration.seconds(60),
scaleOutCooldown: cdk.Duration.seconds(60),
});
new cdk.CfnOutput(this, "ServiceUrl", {
value: service.loadBalancer.loadBalancerDnsName,
});
}
}
// test/app-stack.test.ts — CDK assertions
import { App } from "aws-cdk-lib";
import { Template } from "aws-cdk-lib/assertions";
import { AppStack } from "../lib/app-stack";
test("ECS service has desired count 2", () => {
const app = new App();
const stack = new AppStack(app, "TestStack", {
vpcId: "vpc-12345",
imageUri: "123456789.dkr.ecr.us-east-1.amazonaws.com/app:latest",
environment: "test",
});
const template = Template.fromStack(stack);
template.hasResourceProperties("AWS::ECS::Service", {
DesiredCount: 2,
});
// Verify Fargate launch type
template.hasResourceProperties("AWS::ECS::Service", {
LaunchType: "FARGATE",
});
});
CDK shines when:
- Full-stack team already writes TypeScript/Python
- Complex conditionals and loops in infra (generating 20 Lambda functions from config)
- Reusable constructs shared across teams (internal NPM packages)
- Type safety for resource configuration
⚙️ DevOps Done Right — Zero Downtime, Full Automation
Ship faster without breaking things. We build CI/CD pipelines, monitoring stacks, and auto-scaling infrastructure that your team can actually maintain.
- Staging + production environments with feature flags
- Automated security scanning in the pipeline
- Uptime monitoring + alerting + runbook automation
- On-call support handover docs included
Migrating CloudFormation → Terraform
The safest approach: import existing resources into Terraform state without re-creating them.
# Step 1: Install and configure Terraform
terraform init
# Step 2: Write Terraform config matching existing CFN resources
# (Do NOT apply yet — just write the config)
# Step 3: Import existing AWS resources into Terraform state
# (No infrastructure is created — only state is updated)
terraform import aws_ecs_cluster.main arn:aws:ecs:us-east-1:123456789:cluster/my-cluster
terraform import aws_ecs_service.app arn:aws:ecs:us-east-1:123456789:service/my-cluster/my-service
terraform import aws_lb.main arn:aws:elasticloadbalancing:us-east-1:123456789:loadbalancer/app/my-alb/abc123
# Step 4: Verify no diff (plan should show no changes)
terraform plan
# Expected: "No changes. Your infrastructure matches the configuration."
# Step 5: Delete CFN stack (resources remain — they're now owned by Terraform)
aws cloudformation delete-stack --stack-name my-stack
Drift Detection
# Terraform: plan detects drift
terraform plan
# If someone manually changed a resource in console,
# plan shows the diff and proposes correction
# Set up automatic drift detection in CI
# .github/workflows/drift-check.yml:
# - name: Terraform Plan (drift check)
# run: terraform plan -detailed-exitcode
# # Exit code 2 = changes detected (drift!)
# # Notify Slack, create GitHub issue, etc.
# CloudFormation: built-in drift detection
aws cloudformation detect-stack-drift --stack-name my-stack
aws cloudformation describe-stack-drift-detection-status --stack-drift-detection-id <id>
OpenTofu: The Terraform Fork
Since HashiCorp changed Terraform's license to BSL in 2023, OpenTofu (the Linux Foundation fork) has become a drop-in replacement with MPL license:
# OpenTofu is a direct drop-in replacement for Terraform
# Same HCL syntax, same state format, same providers
# Install OpenTofu
brew install opentofu # macOS
# or: snap install opentofu
# Use tofu instead of terraform
tofu init
tofu plan
tofu apply
# All existing Terraform configs work without modification
For AWS-only shops evaluating long-term license risk, OpenTofu is now the recommended choice over HashiCorp Terraform.
Decision Framework
Multi-cloud or non-AWS resources?
→ Terraform / OpenTofu
AWS-only + team writes TypeScript + complex conditionals/loops?
→ AWS CDK
AWS-only + small team + simple infra + automatic rollback critical?
→ CloudFormation
AWS-only + large team + plan/apply review workflow needed?
→ Terraform / OpenTofu
Cost and Timeline
| Task | Timeline | Cost (USD) |
|---|---|---|
| CloudFormation → Terraform migration (single stack) | 1–3 days | $800–$2,500 |
| CDK construct library development | 1–2 weeks | $5,000–$10,000 |
| Multi-account Terraform setup | 1–2 weeks | $5,000–$10,000 |
| IaC audit + tool selection | 1 day | $600–$1,200 |
See Also
- Terraform State Management — Remote state, workspaces, locking
- AWS ECS Fargate Production — ECS Fargate with Terraform
- Kubernetes Helm Charts — K8s alternative to Terraform for app config
- AWS CloudWatch Observability — Monitoring infrastructure changes
Working With Viprasol
We help teams choose, set up, and migrate to the right IaC tool for their stack. From CloudFormation → Terraform migrations through multi-account CDK construct libraries, our cloud team has managed infrastructure across AWS, Azure, and GCP.
What we deliver:
- IaC tool selection workshop (half-day assessment)
- CloudFormation → Terraform migration without re-creating resources
- Terraform module library (VPC, ECS, RDS, Redis)
- CI/CD pipeline with plan review gates and drift alerts
- OpenTofu migration for teams concerned about BSL licensing
See our cloud infrastructure services or contact us to modernize your infrastructure management.
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need DevOps & Cloud Expertise?
Scale your infrastructure with confidence. AWS, GCP, Azure certified team.
Free consultation • No commitment • Response within 24 hours
Making sense of your data at scale?
Viprasol builds end-to-end big data analytics solutions — ETL pipelines, data warehouses on Snowflake or BigQuery, and self-service BI dashboards. One reliable source of truth for your entire organisation.