So this is the second time I’m writing an introduction for this article. It was supposed to be about what makes my favorite Infrastructure as Code (IaC) tool, CDKTF, so great. I actually released that article on the German online magazine Golem.de: Warum CDK für Terraform mein Lieblings-IaC-Tool ist. But a couple of days after the article got released, I saw this on my Hacker News front page:

Hashicorp (and IBM, which owns Hashicorp) has sud…
So this is the second time I’m writing an introduction for this article. It was supposed to be about what makes my favorite Infrastructure as Code (IaC) tool, CDKTF, so great. I actually released that article on the German online magazine Golem.de: Warum CDK für Terraform mein Lieblings-IaC-Tool ist. But a couple of days after the article got released, I saw this on my Hacker News front page:

Hashicorp (and IBM, which owns Hashicorp) has suddenly decided to deprecate CDKTF, as can be seen on its official GitHub repository:

Great… not only did I just write an article about it, but I also use CDKTF everywhere.
Seems like Hashicorp wants to focus their efforts on making Terraform better than OpenTofu (a Terraform fork which emerged after a controversial license change from Hashicorp). Sadly, the OpenTofu maintainers are also not interested in taking over maintaining this project (see this comment on GitHub). But there is some hope as the Open Construct Foundation has announced to “explore options for community-driven stewardship that would allow the project to continue under open governance.”.
With that in mind, you might not want to start using CDKTF for new projects. Nevertheless, I want to convince you why it’s great — and why it’s better than Terraform on its own.
Tooling for developing cloud infrastructure is undergoing a major shift — away from static configuration files and toward real programming languages. Almost everyone running production workloads in the cloud is familiar with Terraform. Many have also heard of the AWS CDK, which allows you to programmatically deploy cloud infrastructure instead of using configuration files.
With the Cloud Development Kit for Terraform (CDKTF), HashiCorp brings this idea into the Terraform ecosystem. It combines the robustness of Terraform with the flexibility of TypeScript, Python, and others. The result is reusability, real logic in code, and greater expressiveness in the IaC workflow.
So is CDKTF just a Terraform wrapper, or a real game changer? In this article, I want to explain why it has become my preferred Infrastructure-as-Code (IaC) tool — and why I choose it over both the AWS CDK and Terraform itself.
Terraform as the Foundation
Terraform has been the de facto standard for Infrastructure-as-Code for years — a configuration language and toolset that allows Cloud resources to be defined in plain text. It uses a declarative syntax to describe the desired state, ensuring consistency and traceability.
Thanks to its vast “provider” ecosystem — a provider being the bridge between Terraform and an external Cloud provider API such as AWS — almost any Cloud or on-premise resource (from AWS and Azure to Kubernetes) can be managed in a uniform way. Terraform really shines in multi-cloud scenarios: instead of using different tools for each platform, you get a shared model and language.
This is complemented by a massive ecosystem, modular reusability, and an active community that provides best practices, modules, and extensions. In short: Terraform has proven that infrastructure can be managed effectively, reproducibly, and under version control rather than manually.
To give a concrete example, a Terraform definition for an S3 bucket on AWS looks like this:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-central-1"
}
resource "aws_s3_bucket" "demo" {
bucket = "mein-bucket-12345"
}
The problem with Terraform: In larger projects, HCL (Terraform’s declarative configuration language) quickly becomes hard to manage. You end up with workarounds, duplicated code, and limited expressiveness. Very often, you wish you could simply write clean, layered abstractions with inheritance in a real programming language.
Complex logic, such as loops, dynamic dependencies, or conditional behavior, is particularly awkward to express. This is where CDKs come into play.
The Advantages of CDKTF
CDK stands for Cloud Development Kit and was originally developed by AWS as a programmatic alternative to CloudFormation. The idea is simple: AWS provides automatically generated bindings for CloudFormation structures in various programming languages (JavaScript, TypeScript, Python, Java, C#, Go). When this program is executed, no infrastructure is changed directly. Instead, a CloudFormation configuration is generated and deployed through the standard AWS provisioning pipeline.
CDKTF works in a very similar way. For many Terraform providers, there are ready-made — and sometimes even hand-maintained — CDKTF bindings. If needed, you can generate bindings yourself from virtually any Terraform provider by executing a command. The infrastructure code you write generates HCL JSON (or optionally standard HCL/.tf/Terraform), which is essentially Terraform code in the JSON format and can be used with standard Terraform tooling.
Here’s what the CDKTF TypeScript code for creating an S3 bucket looks like:
import { Construct } from "constructs";
import { App, TerraformStack } from "cdktf";
import { AwsProvider, s3 } from "@cdktf/provider-aws";
class MyStack extends TerraformStack {
constructor(scope: Construct, id: string) {
super(scope, id);
new AwsProvider(this, "aws", {
region: "eu-central-1",
});
new s3.S3Bucket(this, "demo", {
bucket: "mein-bucket-12345",
});
}
}
const app = new App();
new MyStack(app, "demo-stack");
app.synth();
The primary advantage of CDKTF lies in two areas: the ability to define infrastructure using familiar programming languages such as TypeScript or Python, and the application of established software engineering principles to infrastructure development.
Terraform modules are useful, but they quickly reach their limits. Anyone who has tried to build a generic “standard S3 bucket” module knows the result: a messy construct of variables, outputs, conditionals, and nested count or for_each expressions. It’s neither elegant nor easy to maintain.
With CDKTF, you can instead implement an abstraction as a class with a constructor, members, and inheritance, or use a functional approach. And because it’s TypeScript, you get type safety and editor autocomplete.
Here’s an example of an S3 bucket abstraction (a Construct). It creates encrypted buckets with lifecycle rules. In Terraform, this would require a module with variables and outputs. Here, it’s just a class that can also be extended using programmatic inheritance:
import { Construct } from "constructs";
import { s3 } from "@cdktf/provider-aws";
interface StandardBucketProps {
name: string;
}
export class StandardBucket extends Construct {
constructor(scope: Construct, id: string, props: StandardBucketProps) {
super(scope, id);
new s3.S3Bucket(this, "bucket", {
bucket: props.name,
serverSideEncryptionConfiguration: {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "AES256",
},
},
},
lifecycleRule: [
{
enabled: true,
expiration: { days: 365 },
},
],
});
}
}
CDKTF vs AWS CDK
The decisive advantage of CDKTF over the AWS CDK is that it is not limited to a single Cloud provider. While AWS CDK locks you into the AWS ecosystem, CDKTF keeps you fully inside the provider-agnostic Terraform world.
Every existing Terraform provider is also available for use with CDKTF — official, community-driven, or internally developed. Even if a provider isn’t directly supported, bindings can usually be generated with little effort. This enables true multi-cloud scenarios where resources from AWS, Azure, GCP, Kubernetes, and on-prem systems can coexist without code breaks or additional tools.
At the same time, the entire Terraform toolchain remains intact: terraform plan, CI/CD integrations (for example, via Spacelift), policy checks, drift detection, state management, and debugging all work exactly as before. This lowers the barrier to entry, since teams can keep their existing workflows while gradually migrating from modules to reusable CDKTF constructs.
In short: CDKTF brings programmability to the IaC world without sacrificing the stability and breadth of the Terraform ecosystem — a powerful combination.
Internal Mechanics and Limitations of CDKTF
It’s also interesting to see how CDKTF works internally. In CDKTF TypeScript, calling something like new s3.S3Bucket(...) creates an object in the so-called Construct Tree. This tree does not directly describe infrastructure, but rather the internal model of the CDKTF application — including all stacks, providers, resources, and custom abstractions.
Each node represents an object instantiated in code. Static values, like, "eu-central-1" are stored directly, while dynamic references create “tokens”, such as for bucket.arn. These tokens are placeholders that initially exist only in memory — in a debugger, you’ll see strings like TF_TOKEN[xyz].
Only during the synthesis step (cdktf synth) is the Construct Tree traversed, and tokens are resolved into Terraform-compatible expressions like ${aws_s3_bucket.demo.arn}. It’s important to remember that you’re never working with real values, even if the TypeScript type hints suggest otherwise. You’re always dealing with placeholders used to generate Terraform definitions.
Via the Fn namespace, Terraform built-ins like join, length, or element can also be used directly in code and will appear unchanged in the generated Terraform output.
CDKTF itself never changes infrastructure. It generates Terraform-compliant code, which Terraform then executes and manages as usual. Sometimes, however, automatically generated provider bindings don’t cover every feature.
In other cases, you may need special configuration that isn’t exposed by the classes. That’s what escape hatches are for: they allow you to directly influence the generated code and set or override arbitrary fields — essentially a last resort to fall back to raw Terraform definitions (more on that here).
Locking, Drift, and Refactoring Pain
As powerful as CDKTF is, it has limits. You remain within the Terraform ecosystem, including all its quirks: state management, locking, drift, and the well-known pain of refactoring resource names. You also need to be careful when moving resources between constructs, since constructs implicitly influence Terraform resource IDs.
Breaking changes in provider bindings can also cause issues in your codebase. Another limitation lies in abstraction itself: even though you’re writing a real programming language, everything ultimately has to compile down to valid Terraform JSON.
Anyone expecting to write arbitrary code will quickly hit the hard boundary of Terraform semantics. Early on, it can be difficult to internalize that you’re writing code that generates Terraform — not code that directly manipulates infrastructure. CDKTF also doesn’t shield developers from having to understand Terraform itself. To debug issues, you still need to understand the generated Terraform code.
Experimental Features and Documentation Gaps
CDKTF is also not as mature as Terraform itself. Some features feel experimental, and the documentation has noticeable gaps. Often, you’ll find little more than auto-generated API references or scattered examples in GitHub issues and blog posts. Consistently maintained guides and best practices are still largely missing.
Anyone who wants to go deeper often has to read generated source code or type definitions. This raises the entry barrier and requires a high level of initiative — especially compared to classic Terraform, which benefits from years of community knowledge and documentation.
Pulumi as an Alternative
Another interesting player is Pulumi. Similar to CDKTF, infrastructure is written in TypeScript, Python, Go, or C# — but without the Terraform intermediary step. Thanks to the Terraform Bridge framework, all existing Terraform providers can still be used, giving Pulumi nearly the same reach as Terraform, but without its own syntax.
The key difference lies in architecture: CDKTF is a generator for Terraform definitions, while Pulumi has its own engine and talks directly to providers. This makes Pulumi leaner and more flexible, while CDKTF inherits all the strengths and weaknesses of Terraform unchanged. Pulumi can deliver new features faster, but ties you more closely to its own ecosystem.
Conclusion
In the end, CDKTF represents a meaningful step forward in the evolution of Infrastructure-as-Code. It combines the stability and maturity of Terraform with the expressiveness of modern programming languages and brings real software engineering practices into the infrastructure world.
Yes, you remain bound to Terraform’s limits and occasionally need escape hatches. But in practice, the benefits outweigh the drawbacks: better reusability, clearer abstractions, real testability, and a significantly improved developer experience.
For teams that no longer see infrastructure as a necessary evil, but as an integral part of their software, CDKTF is a game changer — and for me personally, the new standard for how modern infrastructure projects should be built.