HashiCorp archived the Cloud Development Kit for Terraform on 10 December 2025. The repository is read-only. No further updates, no fixes, no compatibility updates. That last one decides your timeline. The stated reason is that it “did not find product-market fit at scale”.
If you have CDKTF in production, the good news is structural: you are less trapped than the announcement makes it feel.

What CDKTF actually was
CDKTF is a generator, not an engine. You write TypeScript, Python or Go; cdktf synth writes Terraform configuration into cdktf.out; cdktf deploy and cdktf destroy wrap terraform apply and terraform destroy. The state file underneath is ordinary Terraform state, written by ordinary Terraform against ordinary providers.
That is why this deprecation is survivable. Nothing proprietary holds your resources: the archived layer sits above the one that owns them. It is the same distinction that decides what actually runs your infrastructure when you use SST, and it is why leaving CDKTF costs less than leaving a tool with its own state model would.
The exit is one command
cdktf synth --hcl emits HCL rather than the default JSON. Commit that output, point Terraform at it, and delete the CDKTF layer. Your state is untouched, because the state was never CDKTF’s.
What you lose in that move is real and worth naming before you make it:
- Loops, conditionals and types. The reason to pick CDKTF was that
forreads better thancountandifis easier thantry(…). Synthesised HCL is the expansion, not the expression: every loop arrives flattened into its results. - Your abstractions. A construct that produced twelve resources becomes twelve resources. The intent that generated them lives only in the code you are deleting.
- Readable diffs. Generated HCL is verbose and machine-shaped. It works, and nobody enjoys reviewing it.
Synthesising is the exit, not yet the migration. It puts you on a maintained toolchain on day one and moves the rewrite to your schedule rather than the next compatibility break’s.
The three real options
- Standard Terraform with HCL. HashiCorp’s own recommendation, and the one with no new vendor and no new state format. You trade programming constructs for a language everyone on the team already reads and every tool already supports.
- AWS CDK, if your CDKTF code is already entangled with AWS CDK constructs. HashiCorp names this path explicitly. It is not a small move: CloudFormation is a different engine with a different state model, and non-AWS providers do not come with you.
- Pulumi. It keeps what you liked, a real programming language over the same providers, and asks you to adopt its state and its runtime in exchange. Pulumi published a migration path the same month, which is worth reading with the awareness that it was written by the party it benefits.
What I would do
Synth to HCL now, on the existing state, and stop depending on an archived toolchain today rather than in the quarter when a provider release breaks it. Then decide the rewrite separately, with the pressure off.
The deadline is not the archive date. It is the first provider version your pinned CDKTF cannot generate for. Because nobody is publishing compatibility updates, AWS or Google or Cloudflare set that date, not you.
Next
Counting lines before and after would measure verbosity, and verbosity is not the risk. The claim this post rests on is that your resources never move, because the state was never CDKTF’s. That claim has a test, and the test is binary.
Synth to HCL, point Terraform at the output, and plan against the same state. Plan the existing setup first, though: a project with drift already in it will produce a dirty plan either way, and you would blame the migration for something that was there before.
# Baseline: what the CDKTF setup plans today
terraform plan -out=old.tfplan
# The same project synthesised to HCL, against the SAME state
cdktf synth --hcl
terraform plan -out=new.tfplan
# Compare the two as data rather than as text
for p in old new; do
terraform show -json $p.tfplan \
| jq -S '[.resource_changes[]
| select(.change.actions != ["no-op"])
| {address, type, actions: .change.actions}] | sort_by(.address)' > $p.json
done
diff old.json new.json
A clean exit is diff printing nothing. Anything else names itself. A ~ means the generated HCL writes an attribute differently than the JSON did, which is usually cosmetic and occasionally not. A -/+ means a resource would be destroyed and recreated because a construct hands out different names once it is flattened, and that is the difference between a migration and an outage. A line count sees neither.
I have a CDKTF codebase in production to run this against. What I can publish from it is the shape of the answer rather than the infrastructure: whether the diff comes back empty, and if it does not, which kind of construct turned out to be load-bearing.
Have you got CDKTF running somewhere? I am curious whether the constructs you built are the kind that flatten cleanly, or the kind that were the whole point.
Sourced from the archive notice, the CDKTF CLI reference and the state model. No migration was run for this post.
Building something similar?
I write about setups I actually use. If you're working on something comparable, I'd be curious what your workflow looks like.