Back to Blog

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.

Viprasol Tech Team
February 8, 2027
13 min read

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

DimensionTerraform / OpenTofuCloudFormationAWS CDK
LanguageHCL (DSL)YAML / JSONTypeScript, Python, Java, Go
Multi-cloud✅ Azure, GCP, etc.❌ AWS only❌ AWS only (uses CFN under the hood)
State managementExternal (S3 + DynamoDB)Managed by AWSManaged by AWS (via CFN stacks)
Drift detectionterraform planStack drift detectionCDK diff (via CFN)
Preview changesterraform planChange setscdk diff
TestingTerratest, tftestCloudFormation Guard, cfn-lintCDK assertions (Jest)
RollbackManual (state manipulation)Automatic on failureAutomatic (via CFN)
Resource coverage~3,000 providersEvery AWS service (day-zero)Every AWS service (via CFN)
LicenseBSL (OpenTofu = MPL)Free (AWS service)Apache 2.0
Learning curveMediumMediumLow (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

TaskTimelineCost (USD)
CloudFormation → Terraform migration (single stack)1–3 days$800–$2,500
CDK construct library development1–2 weeks$5,000–$10,000
Multi-account Terraform setup1–2 weeks$5,000–$10,000
IaC audit + tool selection1 day$600–$1,200

See Also


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.

Share this article:

About the Author

V

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.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need DevOps & Cloud Expertise?

Scale your infrastructure with confidence. AWS, GCP, Azure certified team.

Free consultation • No commitment • Response within 24 hours

Viprasol · Big Data & Analytics

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.