Skip to main content

Terraform vs OpenTofu vs CDKTF: IaC 2026

·PkgPulse Team
0

Terraform vs OpenTofu vs CDKTF: IaC for TypeScript Teams 2026

TL;DR

HashiCorp changed Terraform's license from MPL-2.0 to BSL 1.1 in August 2023 — restricting commercial use by competitors. This triggered a fork (OpenTofu) maintained by the Linux Foundation. Meanwhile, CDKTF lets TypeScript developers write Terraform configs in code instead of HCL. Terraform (BSL 1.1) is the most mature IaC tool with 3,000+ providers and the largest community, but the license change creates uncertainty for some organizations. OpenTofu (MPL-2.0) is a drop-in replacement for Terraform — same HCL, same providers, fully open-source, already at feature parity. CDKTF is the TypeScript abstraction layer on top of either Terraform or OpenTofu — write infrastructure in TypeScript, generate HCL, leverage the full Terraform provider ecosystem. For TypeScript teams who want to own their infra code: CDKTF. For HCL users who need open-source guarantees: OpenTofu. For teams already on Terraform without licensing concerns: Terraform.

Key Takeaways

  • OpenTofu 1.8+ is feature-compatible with Terraform 1.8 — migration is change one binary
  • CDKTF generates Terraform-compatible HCL — use all 3,000+ Terraform providers with TypeScript
  • Terraform BSL restricts hosting Terraform-as-a-service — doesn't affect internal use
  • OpenTofu has its own registry — providers work from both registry.opentofu.org and registry.terraform.io
  • CDKTF uses constructs — the same CDK model as AWS CDK (familiar if you've used CDK)
  • State management is identical — Terraform state files work between Terraform and OpenTofu
  • Pulumi vs CDKTF: Pulumi is a full rewrite; CDKTF generates HCL and uses Terraform providers

The Terraform Licensing Split

August 2023:
  Terraform (MPL-2.0) ──license change──> Terraform (BSL 1.1)
                                                 |
                         ┌──────────────────────┘
                         ▼
              OpenTofu (MPL-2.0) — Linux Foundation
              "The truly open-source Terraform"

What BSL 1.1 means:

  • ✅ Internal use is fine — DevOps teams can use Terraform normally
  • ✅ Consulting and implementation work is fine
  • ❌ You cannot offer Terraform as a hosted service to others
  • ❌ Products that compete with HashiCorp (Terraform Cloud) cannot use Terraform

For most teams: BSL 1.1 has zero practical impact. But enterprises with strict open-source compliance requirements increasingly choose OpenTofu.


Terraform: The Established Standard

Terraform defines infrastructure via HCL (HashiCorp Configuration Language) — a declarative DSL that describes desired state, and Terraform plans + applies the diff.

Installation

# macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Check version
terraform version

Basic AWS Provider Setup

# main.tf — Terraform configuration

terraform {
  required_version = ">= 1.6"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  # Remote state — store state in S3 with DynamoDB locking
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "production/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}

provider "aws" {
  region = var.aws_region
}

Variables and Outputs

# variables.tf
variable "aws_region" {
  type    = string
  default = "us-east-1"
}

variable "environment" {
  type    = string
  default = "production"
}

variable "app_name" {
  type = string
}

# outputs.tf
output "load_balancer_dns" {
  value       = aws_lb.app.dns_name
  description = "The DNS name of the load balancer"
}

output "rds_endpoint" {
  value     = aws_db_instance.main.endpoint
  sensitive = true
}

EC2 + RDS + S3 Stack

# infrastructure.tf

# VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name        = "${var.app_name}-vpc"
    Environment = var.environment
  }
}

# RDS PostgreSQL
resource "aws_db_instance" "main" {
  identifier        = "${var.app_name}-db"
  engine            = "postgres"
  engine_version    = "16.1"
  instance_class    = "db.t3.micro"
  allocated_storage = 20
  storage_encrypted = true

  db_name  = "appdb"
  username = "postgres"
  password = var.db_password  # From var, not hardcoded

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

  backup_retention_period = 7
  skip_final_snapshot     = false
  final_snapshot_identifier = "${var.app_name}-final-snapshot"

  tags = {
    Environment = var.environment
  }
}

# S3 bucket
resource "aws_s3_bucket" "assets" {
  bucket = "${var.app_name}-assets-${var.environment}"

  tags = {
    Environment = var.environment
  }
}

resource "aws_s3_bucket_versioning" "assets" {
  bucket = aws_s3_bucket.assets.id
  versioning_configuration {
    status = "Enabled"
  }
}

Terraform Workflow

# Initialize (download providers)
terraform init

# Review planned changes
terraform plan -var-file="production.tfvars"

# Apply changes
terraform apply -var-file="production.tfvars"

# Destroy infrastructure
terraform destroy -var-file="production.tfvars"

# Format code
terraform fmt -recursive

# Validate syntax
terraform validate

# Import existing resource into state
terraform import aws_s3_bucket.assets my-existing-bucket

# Workspace management (for multi-env)
terraform workspace new staging
terraform workspace select production

The terraform plan step is the most important part of the Terraform workflow. Unlike scripts that run imperatively, Terraform's plan shows you exactly what will be created, modified, or destroyed before any changes are made. This preview is particularly valuable in CI/CD pipelines — many teams require a plan approval step before any apply can run on production infrastructure.

Terraform's plan output includes change annotations that indicate whether a resource will be created (+), destroyed (-), modified in-place (~), or destroyed and recreated (-/+). The -/+ annotation is particularly important: some resource property changes require replacement rather than in-place modification, which means downtime for resources like RDS instances or EC2 instance types. Catching these replacement operations in the plan step — before they happen — is one of Terraform's core safety mechanisms.

Terraform in CI/CD Pipelines

The standard CI/CD pattern for Terraform is: run terraform plan on every pull request, require approval for the plan output, then run terraform apply after merge to main. This "plan on PR, apply on merge" workflow gives infrastructure changes the same review process as application code.

Tools like Atlantis (the open-source Terraform automation tool) implement this workflow with GitHub/GitLab/Bitbucket webhooks. When a PR modifies .tf files, Atlantis posts the plan output as a PR comment. Team members review the plan, approve it (by commenting atlantis apply), and Atlantis runs the apply automatically. For teams managing Terraform at scale, Atlantis is the standard self-hosted automation layer — before paying for Terraform Cloud or HCP Terraform.

Terraform Cloud (HashiCorp's managed platform, now called HCP Terraform) offers the same automated plan/apply workflow with a hosted UI, managed state storage, and team permission management. The free tier covers most small teams; paid tiers add SSO, audit logging, and policy enforcement via Sentinel. OpenTofu users can achieve the same automation with Atlantis or GitHub Actions workflows using the official setup-opentofu GitHub Action, which mirrors the setup-terraform action exactly with the OpenTofu binary substituted.

Terraform State Management

Terraform's state file is the source of truth for what infrastructure it manages. State can be stored locally (default) or remotely. Remote state with S3 + DynamoDB is the standard AWS production setup: S3 provides versioned storage with encryption, DynamoDB provides distributed locking to prevent two concurrent terraform apply runs from corrupting state.

State management becomes the central operational concern in large Terraform deployments. The terraform import command allows you to bring existing infrastructure under Terraform management without recreating it — essential for migrating manually-created resources into IaC. The terraform state mv command reorganizes state when refactoring module structures. For TypeScript developers accustomed to type-safe refactoring tools, Terraform's HCL-based state model requires more careful manual coordination.

Terraform Module Ecosystem

Terraform's biggest practical advantage is its module registry at registry.terraform.io. HashiCorp and community contributors have published modules for nearly every AWS, GCP, and Azure resource pattern. The AWS modules in particular are comprehensive — the terraform-aws-modules/vpc/aws module alone handles VPC, subnets, route tables, NAT gateways, and security groups with dozens of configurable variables. Using modules reduces complex infrastructure to a few declarative blocks.

The HashiCorp license change in August 2023 (from MPL-2.0 to BSL-1.1) affected the Terraform provider and core binary. Most community-published modules in the registry remain under MPL-2.0. The BSL restriction applies specifically to competing HashiCorp product offerings — for enterprise users running Terraform internally, the license change has no practical impact. The OpenTofu fork exists for organizations that need a guarantee of open-source licensing for the runtime itself.


OpenTofu: The Open-Source Drop-In

OpenTofu is a fork of Terraform 1.5.x maintained by the Linux Foundation. The CLI, HCL syntax, provider ecosystem, and state format are all compatible.

Installation

# macOS
brew install opentofu

# Or via the official installer
curl --proto '=https' --tlsv1.2 -fsSL https://get.opentofu.org/install-opentofu.sh | sh

tofu version
# OpenTofu v1.8.3

Identical HCL — Just Change the Binary

# Migrate from Terraform to OpenTofu:
# 1. Install tofu binary
# 2. Replace "terraform" with "tofu" in your commands
# That's it.

tofu init
tofu plan
tofu apply

# State files are 100% compatible — no migration needed

OpenTofu-Specific Features (Ahead of Terraform)

# OpenTofu 1.7+: Native state encryption
terraform {
  encryption {
    key_provider "pbkdf2" "my_key" {
      passphrase = var.state_encryption_passphrase
    }

    method "aes_gcm" "state_encryption" {
      keys = key_provider.pbkdf2.my_key
    }

    state {
      method = method.aes_gcm.state_encryption
      enforced = true
    }
  }
}
# OpenTofu 1.8+: Provider-defined functions
# Call functions defined in providers directly in HCL
provider::aws::arn_parse("arn:aws:s3:::my-bucket")

Registry Compatibility

# OpenTofu uses its own registry by default
# registry.opentofu.org mirrors all providers from registry.terraform.io

# Explicit registry source (usually not needed — OpenTofu finds providers automatically)
terraform {
  required_providers {
    aws = {
      source  = "registry.opentofu.org/hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

CDKTF: Terraform with TypeScript

CDKTF (Cloud Development Kit for Terraform) lets you write infrastructure in TypeScript, Python, Java, C#, or Go — generating Terraform HCL under the hood.

Installation

npm install -g cdktf-cli
cdktf init --template=typescript --providers=aws

# This creates:
# cdktf.json        — project config
# main.ts           — your infrastructure code
# package.json      — TypeScript dependencies

Basic Stack in TypeScript

// main.ts
import { App, TerraformStack, TerraformOutput } from "cdktf";
import { AwsProvider } from "@cdktf/provider-aws/lib/provider";
import { Instance } from "@cdktf/provider-aws/lib/instance";
import { S3Bucket } from "@cdktf/provider-aws/lib/s3-bucket";
import { S3BucketVersioningA } from "@cdktf/provider-aws/lib/s3-bucket-versioning";
import { DbInstance } from "@cdktf/provider-aws/lib/db-instance";

class MyAppStack extends TerraformStack {
  constructor(scope: App, id: string) {
    super(scope, id);

    new AwsProvider(this, "AWS", {
      region: "us-east-1",
    });

    // S3 bucket — TypeScript autocomplete for all properties
    const assetsBucket = new S3Bucket(this, "assets", {
      bucket: "my-app-assets-production",
      tags: { Environment: "production" },
    });

    new S3BucketVersioningA(this, "assets-versioning", {
      bucket: assetsBucket.id,
      versioningConfiguration: { status: "Enabled" },
    });

    // RDS instance
    const db = new DbInstance(this, "database", {
      identifier: "my-app-db",
      engine: "postgres",
      engineVersion: "16.1",
      instanceClass: "db.t3.micro",
      allocatedStorage: 20,
      dbName: "appdb",
      username: "postgres",
      password: process.env.DB_PASSWORD!,
      skipFinalSnapshot: false,
      finalSnapshotIdentifier: "my-app-final-snapshot",
      storageEncrypted: true,
    });

    // Outputs
    new TerraformOutput(this, "db-endpoint", {
      value: db.endpoint,
      sensitive: true,
    });

    new TerraformOutput(this, "assets-bucket", {
      value: assetsBucket.bucket,
    });
  }
}

const app = new App();
new MyAppStack(app, "my-app-production");
app.synth();

Reusable Constructs

// constructs/web-app.ts — reusable infrastructure pattern
import { Construct } from "constructs";
import { TerraformOutput } from "cdktf";
import { Lb } from "@cdktf/provider-aws/lib/lb";
import { LbListener } from "@cdktf/provider-aws/lib/lb-listener";
import { EcsService } from "@cdktf/provider-aws/lib/ecs-service";
import { EcsCluster } from "@cdktf/provider-aws/lib/ecs-cluster";

interface WebAppConfig {
  name: string;
  imageUri: string;
  cpu: number;
  memory: number;
  port: number;
  vpcId: string;
  subnetIds: string[];
  desiredCount?: number;
}

export class WebApp extends Construct {
  public readonly loadBalancerDns: string;

  constructor(scope: Construct, id: string, config: WebAppConfig) {
    super(scope, id);

    const cluster = new EcsCluster(this, "cluster", {
      name: `${config.name}-cluster`,
    });

    const lb = new Lb(this, "lb", {
      name: `${config.name}-lb`,
      internal: false,
      loadBalancerType: "application",
      subnets: config.subnetIds,
    });

    const service = new EcsService(this, "service", {
      name: config.name,
      cluster: cluster.id,
      desiredCount: config.desiredCount ?? 2,
      launchType: "FARGATE",
      // ... task definition, network config
    });

    this.loadBalancerDns = lb.dnsName;

    new TerraformOutput(this, "lb-dns", {
      value: lb.dnsName,
    });
  }
}

// In your main stack — reuse the construct
import { WebApp } from "./constructs/web-app";

class ProductionStack extends TerraformStack {
  constructor(scope: App, id: string) {
    super(scope, id);
    // ...provider setup...

    const webApp = new WebApp(this, "web-app", {
      name: "my-api",
      imageUri: "123456789.dkr.ecr.us-east-1.amazonaws.com/my-api:latest",
      cpu: 256,
      memory: 512,
      port: 3000,
      vpcId: "vpc-12345",
      subnetIds: ["subnet-1", "subnet-2"],
    });

    console.log("Load balancer:", webApp.loadBalancerDns);
  }
}

CDKTF Workflow

# Synthesize HCL (generate Terraform config from TypeScript)
cdktf synth
# Outputs to cdktf.out/stacks/my-app-production/cdk.tf.json

# Plan (runs terraform plan under the hood)
cdktf plan my-app-production

# Deploy
cdktf deploy my-app-production

# Destroy
cdktf destroy my-app-production

# Use OpenTofu instead of Terraform
CDKTF_HOME=$HOME/.cdktf cdktf deploy  # Configure TERRAFORM_BINARY_NAME=tofu

Why CDKTF Appeals to TypeScript Teams

CDKTF's core promise is using TypeScript's type system to make infrastructure refactoring safe. When you rename an S3 bucket construct or change a property name, TypeScript compilation errors surface immediately rather than at terraform apply time. For teams already fluent in TypeScript, the cognitive overhead of learning HCL is eliminated — infrastructure is just another module in your codebase, testable with Jest and navigable with the same tooling as application code.

The reusable construct pattern is the most powerful feature CDKTF offers over raw HCL modules. In HCL, a module is a directory of .tf files with defined inputs and outputs — functional but opaque. In CDKTF, a construct is a TypeScript class that extends Construct. It can have constructor arguments with TypeScript types, computed properties, getters that expose child resource attributes, and inheritance for extending shared patterns across multiple deployment contexts.

CDKTF's main practical downside is build time. Because CDKTF synthesizes TypeScript to HCL on every cdktf synth call, you're running TypeScript compilation before every infrastructure plan. In monorepos with complex dependency graphs, this adds latency to the inner dev loop. Teams that run CDKTF in CI/CD typically cache the synth output and only re-synthesize when infrastructure code changes.

CDKTF and OpenTofu

A practical advantage of CDKTF's architecture: because it generates standard HCL, you can switch the underlying Terraform-compatible runtime between Terraform and OpenTofu by changing the TERRAFORM_BINARY_NAME environment variable. This makes CDKTF particularly appealing for organizations that want to hedge between Terraform and OpenTofu while the open-source ecosystem around both stabilizes. The constructs and TypeScript code are identical; only the runtime changes.

For teams building internal developer platforms (IDPs) or platform engineering tooling, CDKTF constructs can be published to npm as internal packages. Your platform team defines vetted, security-approved infrastructure patterns as constructs; application teams consume them as npm dependencies. This distributed architecture is more maintainable than copy-pasted HCL modules across dozens of repositories.


Feature Comparison

FeatureTerraformOpenTofuCDKTF
LanguageHCLHCLTypeScript / Python / Go
LicenseBSL 1.1✅ MPL-2.0Apache 2.0
Provider count✅ 3,000+✅ 3,000+ (same)✅ Uses TF providers
State management✅ Full✅ Full + encryptionDelegates to TF/OTF
TypeScript supportHCL onlyHCL only✅ Native
Type safetyLimitedLimited✅ Full TypeScript
Reusable constructsModules (HCL)Modules (HCL)✅ Class-based
TestingTerraform testOpenTofu test✅ Jest/Vitest
Native state encryption✅ Built-inDelegates to backend
Terraform CloudHCP Terraform
Community✅ LargestGrowingMedium
GitHub stars43k23k4.7k

When to Use Each

Choose Terraform if:

  • Your team is already on Terraform and BSL doesn't affect your use case
  • Terraform Cloud or HCP Terraform is part of your workflow
  • You need access to the most mature provider versions first
  • Enterprise support from HashiCorp matters to your organization

Choose OpenTofu if:

  • Open-source licensing is a compliance requirement
  • You want to avoid BSL uncertainty in your infrastructure toolchain
  • You're starting a new project and want the most future-proof option
  • Native state encryption without third-party tools is needed

Choose CDKTF if:

  • Your team prefers TypeScript over HCL (strong typing, IDE completion)
  • You want reusable infrastructure patterns as npm packages
  • Testing infrastructure code with Jest/Vitest is a priority
  • You're already using AWS CDK and want similar patterns for multi-cloud

Ecosystem and Community

Terraform has the largest IaC community by a wide margin — 43,000 GitHub stars, a registry with over 3,000 providers and 13,000+ modules, and a thriving ecosystem of tools like Terragrunt, Atlantis, Spacelift, and env0. The Terraform community on Reddit (r/Terraform), Discord, and Stack Overflow remains the most active place to find answers for infrastructure questions. HashiCorp's certification program (Terraform Associate) is recognized by cloud platform teams at major enterprises.

OpenTofu reached v1.8 in 2025, matching Terraform's feature set, and has grown to 23,000 stars remarkably quickly for a project that started as a response to a licensing change. The Linux Foundation governance gives enterprise legal teams confidence in the open-source pedigree. OpenTofu's CI/CD is notable — the project runs over 3,000 tests in its pipeline, and the community has committed to maintaining backward compatibility with Terraform configurations. The OpenTofu Slack has several thousand active members.

CDKTF occupies a niche but important position in organizations that are heavy TypeScript shops. The AWS CDK community (50,000 stars on the main CDK repo) overlaps significantly with CDKTF users, since both use the constructs programming model. CDKTF constructs can be published to npm and shared across teams — this is its strongest differentiator over HCL modules. See Best Monorepo Tools 2026 for how to organize CDKTF constructs in a monorepo.


Real-World Adoption

Terraform is the IaC standard at the vast majority of enterprise cloud shops. GitLab, GitHub, Cloudflare, and many major SaaS companies have published their infrastructure management practices, and essentially all involve Terraform or an OpenTofu migration. The Terraform Cloud/HCP Terraform managed service (run plans in the cloud, state stored centrally, team-based access) has made Terraform the dominant choice for teams that want managed state storage and audit logs.

OpenTofu has seen fast adoption in Europe, where open-source compliance requirements are more strictly enforced and the BSL license creates real legal friction. German and French engineering teams in particular have been vocal about migrating to OpenTofu. The OpenTofu registry is production-ready and hosts mirrors of all major Terraform providers, so the migration is genuinely binary-swap-level in most cases.

CDKTF is most common in organizations already using AWS CDK for their AWS-specific infrastructure, who then want to extend the same programming model to multi-cloud resources. HashiCorp has also built official CDKTF constructs for many AWS services, making the provider coverage competitive. Testing infrastructure code with Jest is a genuine advantage over HCL, where testing tools like Terratest require Go knowledge.


Developer Experience Deep Dive

Terraform's HCL has good IDE support through the HashiCorp VSCode extension, which provides syntax highlighting, hover documentation, and basic refactoring. The terraform fmt and terraform validate commands provide automatic formatting and syntax checking. HCL's declarative nature is easy to read but hard to abstract — reusable HCL modules lack the expressive power of a real programming language, which is the fundamental motivation for CDKTF.

OpenTofu inherits Terraform's HCL DX entirely. The tofu CLI works identically to terraform in practice, and VSCode's HashiCorp extension works with OpenTofu configs without modification. OpenTofu has started shipping its own documentation site (opentofu.org) with examples that match the renamed CLI commands. One DX improvement: OpenTofu's error messages have been improved and are clearer about state encryption configuration issues.

CDKTF's TypeScript DX is its major selling point. Every provider resource has full TypeScript types — you get autocomplete for every AWS resource property, type errors when you pass the wrong type, and IDE navigation into provider source. The cdktf synth step that generates HCL adds 10-30 seconds to the development loop, which is the main friction point. CDKTF's watch mode and the fact that synth output is deterministic (same TypeScript code always generates same HCL) helps mitigate this.


Migration Guide

Migrating Terraform to OpenTofu is the simplest migration in IaC. Install the tofu binary, change terraform to tofu in your shell scripts and CI/CD, and run tofu init. Your state files are 100% compatible. The only complications arise if you use Terraform Cloud (which has no OpenTofu equivalent, so you'd need to migrate to another state backend like S3 or Spacelift) or if you use very new Terraform features that OpenTofu hasn't implemented yet.

Adopting CDKTF in an existing Terraform project starts with generating TypeScript bindings for your existing providers using cdktf get. The cdktf convert command can translate HCL to CDKTF, though the output requires cleanup. A practical approach is to keep existing Terraform modules as-is and use CDKTF for new infrastructure modules, gradually migrating as modules are revised.

Moving from CDKTF to Pulumi happens when teams want a language-native IaC experience without the HCL intermediate step. Pulumi uses the same TypeScript but produces its own state format and runtime semantics. The tradeoff is losing access to Terraform provider ecosystem breadth — Pulumi has excellent providers but fewer than Terraform's 3,000+.


Final Verdict 2026

For teams already on Terraform with no licensing concerns, staying on Terraform is the rational choice — the ecosystem depth, module registry, and tooling around it are genuinely superior. For teams starting fresh or facing compliance questions around BSL, OpenTofu is the obvious answer. The migration is trivial and the future is more certain on an Apache/MPL-licensed tool.

CDKTF makes the most sense for TypeScript-heavy organizations where infrastructure code lives alongside application code, gets code-reviewed by developers (not just DevOps), and is tested with the same tooling. Writing new S3Bucket(this, "assets", { bucket: "..." }) with full TypeScript types and IDE completion is genuinely better than writing HCL for teams comfortable with TypeScript. The limitation is that CDKTF is an abstraction layer — debugging requires understanding both the TypeScript constructs and the generated HCL.

Testing Infrastructure Code

One of CDKTF's most compelling advantages over Terraform and OpenTofu is the ability to write automated tests for your infrastructure definitions using standard JavaScript testing tools like Jest or Vitest.

With Terraform HCL, testing options are limited: Terraform's native test blocks (added in v1.6) provide basic assertions but lack the expressiveness and tooling integration of a real testing framework. Go-based tools like Terratest exist but require Go knowledge and add complexity to TypeScript-focused teams.

CDKTF enables writing proper unit tests for infrastructure constructs. You can assert that a construct creates the expected resources, that security groups have the correct ingress rules, or that S3 buckets have versioning enabled — all without deploying to a real cloud account. The test approach uses CDKTF's Testing.synthScope to generate the Terraform JSON and then assert against its structure.

// constructs/web-app.test.ts — Jest test for the WebApp construct
import { Testing } from "cdktf";
import { WebApp } from "./web-app";

describe("WebApp construct", () => {
  test("creates an ECS cluster", () => {
    const output = Testing.synthScope((scope) => {
      new WebApp(scope, "test-app", {
        name: "my-api",
        imageUri: "123456789.dkr.ecr.us-east-1.amazonaws.com/my-api:latest",
        cpu: 256,
        memory: 512,
        port: 3000,
        vpcId: "vpc-test",
        subnetIds: ["subnet-1"],
      });
    });

    expect(output).toHaveResourceWithProperties("aws_ecs_cluster", {
      name: "my-api-cluster",
    });
  });

  test("creates a load balancer", () => {
    const output = Testing.synthScope((scope) => {
      new WebApp(scope, "test-app", {
        name: "my-api",
        imageUri: "123456789.dkr.ecr.us-east-1.amazonaws.com/my-api:latest",
        cpu: 256,
        memory: 512,
        port: 3000,
        vpcId: "vpc-test",
        subnetIds: ["subnet-1", "subnet-2"],
      });
    });

    expect(output).toHaveResourceWithProperties("aws_lb", {
      load_balancer_type: "application",
      internal: false,
    });
  });
});

This testing capability means infrastructure changes can be caught before terraform plan runs, reducing the risk of infrastructure regressions during refactoring. For teams with complex reusable constructs (networking configurations, ECS service patterns, RDS cluster setups), construct tests provide a safety net that HCL modules simply cannot offer. The ability to run these tests in CI without cloud credentials makes them fast and cost-free to execute on every pull request.


Methodology

Data sourced from official Terraform and OpenTofu documentation, HashiCorp BSL license FAQ (hashicorp.com), OpenTofu changelog (github.com/opentofu/opentofu), CDKTF documentation (developer.hashicorp.com/terraform/cdktf), and community discussions from the OpenTofu Discord, r/devops, and HashiCorp forums. GitHub star counts as of February 2026.


Related: Best Monorepo Tools 2026, Turso vs PlanetScale vs Neon Serverless Database 2026, Hono vs Elysia 2026

Also related: Pulumi vs SST vs CDKTF for JavaScript-first IaC alternatives, or SST v3 vs Serverless Framework vs AWS CDK for serverless-focused infrastructure tools.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.