Skip to main content

AWS CDK vs Terraform: Which IaC Tool Should You Use in 2026?

Key Takeaway

AWS CDK vs Terraform compared for 2026. Programming languages vs HCL, L2 constructs vs modules, state management, multi-cloud, and when to choose each for AWS projects.

Table of Contents

AWS CDK lets you define infrastructure in TypeScript, Python, Java, or Go. Terraform uses HCL. Both deploy AWS resources — but the approach, ecosystem, and tradeoffs are very different.

Quick Comparison

FeatureAWS CDKTerraform
LanguageTypeScript, Python, Java, Go, C#HCL (declarative)
Underlying engineCloudFormationTerraform Core
Multi-cloud❌ AWS only✅ 3000+ providers
StateAWS-managed (CloudFormation)Self-managed (S3 + DynamoDB)
AbstractionsL2/L3 constructs (high-level)Modules (community registry)
TestingJest, pytest (unit tests)terraform test, Terratest
IDE supportFull (TypeScript autocomplete)Good (HCL plugins)
Learning curveLow if you know the languageLow (HCL is simple)
Rollback✅ Automatic (CloudFormation)❌ Manual
Drift detection✅ Built-in⚠️ terraform plan
Community modulesConstruct HubTerraform Registry
Escape hatchRaw CloudFormationN/A

Language: Programming vs Declarative

AWS CDK (TypeScript)

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as rds from 'aws-cdk-lib/aws-rds';

export class AppStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string) {
    super(scope, id);

    const vpc = new ec2.Vpc(this, 'AppVpc', {
      maxAzs: 3,
      natGateways: 1,
      // CDK automatically creates public/private subnets,
      // route tables, internet gateway, NAT gateway
    });

    const db = new rds.DatabaseInstance(this, 'Database', {
      engine: rds.DatabaseInstanceEngine.postgres({
        version: rds.PostgresEngineVersion.VER_16_3,
      }),
      vpc,
      instanceType: ec2.InstanceType.of(
        ec2.InstanceClass.R6G, ec2.InstanceSize.LARGE
      ),
      // CDK auto-creates security group, subnet group,
      // parameter group with sensible defaults
    });
  }
}

~20 lines → Creates VPC (3 AZs, public/private subnets, IGW, NAT) + RDS with security groups.

Terraform (HCL)

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "app-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b", "us-east-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  single_nat_gateway = true
}

resource "aws_db_instance" "main" {
  identifier     = "production"
  engine         = "postgres"
  engine_version = "16.3"
  instance_class = "db.r6g.large"

  vpc_security_group_ids = [aws_security_group.db.id]
  db_subnet_group_name   = aws_db_subnet_group.main.name

  # Must explicitly create security group, subnet group
}

resource "aws_security_group" "db" {
  vpc_id = module.vpc.vpc_id
  # Must define rules explicitly
}

resource "aws_db_subnet_group" "main" {
  subnet_ids = module.vpc.private_subnets
}

~40 lines → Same result, but you define every component explicitly.

CDK’s Killer Feature: L2 Constructs

CDK’s Level 2 constructs encode AWS best practices automatically:

// L2: Smart defaults + helper methods
const bucket = new s3.Bucket(this, 'Data', {
  encryption: s3.BucketEncryption.S3_MANAGED,
  versioned: true,
  removalPolicy: cdk.RemovalPolicy.RETAIN,
});

// Grant read access — CDK creates the IAM policy automatically
bucket.grantRead(myLambdaFunction);

// This one line creates:
// - IAM policy with s3:GetObject, s3:GetBucket*, s3:List*
// - Scoped to this specific bucket ARN
// - Attached to the Lambda's execution role

In Terraform, you write the IAM policy manually:

resource "aws_iam_role_policy" "lambda_s3" {
  role = aws_iam_role.lambda.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = ["s3:GetObject", "s3:GetBucketLocation", "s3:ListBucket"]
      Resource = [
        aws_s3_bucket.data.arn,
        "${aws_s3_bucket.data.arn}/*"
      ]
    }]
  })
}

Terraform’s Killer Feature: Multi-Cloud + Ecosystem

# One project: AWS + Cloudflare + Datadog + PagerDuty
resource "aws_instance" "web" { ... }
resource "cloudflare_record" "dns" { ... }
resource "datadog_monitor" "cpu" { ... }
resource "pagerduty_service" "web" { ... }

CDK can’t do this — it generates CloudFormation, which is AWS-only.

Testing

CDK: Unit Tests with Jest

import { Template } from 'aws-cdk-lib/assertions';

test('creates RDS instance with correct engine', () => {
  const app = new cdk.App();
  const stack = new AppStack(app, 'Test');
  const template = Template.fromStack(stack);

  template.hasResourceProperties('AWS::RDS::DBInstance', {
    Engine: 'postgres',
    DBInstanceClass: 'db.r6g.large',
  });
});

CDK tests run instantly (no cloud calls) because they test the synthesized CloudFormation template.

Terraform: terraform test

# tests/vpc.tftest.hcl
run "vpc_creates_correctly" {
  command = plan

  assert {
    condition     = aws_vpc.main.cidr_block == "10.0.0.0/16"
    error_message = "VPC CIDR should be 10.0.0.0/16"
  }
}

When to Choose AWS CDK

  • Your team writes TypeScript/Python daily and HCL feels foreign
  • You want high-level abstractions (L2 constructs with smart defaults)
  • You need unit testing against synthesized templates
  • AWS-only infrastructure
  • You value automatic IAM policies (bucket.grantRead())
  • You already use CloudFormation and want a better authoring experience
  • You want automatic rollback on failures

When to Choose Terraform

  • You manage multi-cloud or non-AWS resources (Cloudflare, Datadog, GitHub)
  • You want explicit control over every resource (no hidden defaults)
  • Your team prefers a declarative, purpose-built language (HCL)
  • You need the largest module ecosystem (Terraform Registry)
  • You want provider portability (AWS today, maybe Azure tomorrow)
  • You’re already a Terraform shop

Common Migration Path

Many teams start with CDK for rapid prototyping, then adopt Terraform for production:

Prototype (CDK) → Production (Terraform)
  - CDK for fast iteration with L2 constructs
  - Terraform for explicit, auditable production config

Or the reverse — Terraform teams adopt CDK for complex application stacks where bucket.grantRead() saves hours of IAM policy writing.

Hands-On Courses

Conclusion

CDK and Terraform are both excellent — they optimize for different things. CDK gives you programming language power, smart defaults, and automatic IAM. Terraform gives you explicit control, multi-cloud reach, and the largest IaC ecosystem. If you’re AWS-only and your team loves TypeScript, CDK is compelling. If you manage anything beyond AWS or want maximum transparency in your infrastructure code, Terraform is the standard.

🚀

Level Up Your Terraform Skills

Hands-on courses, books, and resources from Luca Berton

Luca Berton
Written by

Luca Berton

DevOps Engineer, AWS Partner, Terraform expert, and author. Creator of Ansible Pilot, Terraform Pilot, and CopyPasteLearn.