Evolving Infrastructure as Code: Lessons from CDK to Terraform
This post follows on from Fix Forward Thinking, which explored the mindset of moving forward with confidence rather than relying on rollback plans. Here, I take that same philosophy down to the platform level and how we rebuilt trust in our environments by evolving from a fragile CDK implementation to a more predictable, transparent Terraform-based foundation.
There’s a moment in every maturing platform when you realise the tools that helped you move fast early on are now holding you back. For us, that moment came with Infrastructure as Code.
When we first started building, the AWS Cloud Development Kit (CDK) felt like freedom. It was expressive, developer-friendly, and perfect for rapid prototyping. You could model infrastructure in TypeScript, commit it alongside your application code, and spin up an environment in minutes. It gave engineers a sense of flow. Infrastructure finally felt like part of the software.
But as the platform grew, so did the pain.
When Abstraction Becomes Friction
The trouble with CDK isn’t that it’s bad technology. It’s that it hides too much complexity. The abstraction works beautifully until you need to debug something in production or understand the actual AWS resources it generated.
As our estate expanded, the cracks started to show. Different teams wrote stacks slightly differently, even when aiming for the same architecture. CloudFormation templates became bloated and dependency graphs tangled. Reviews took longer. Debugging became guesswork. Every environment began to feel a little bit “Special”. And “Special” doesn’t scale.
When I took over the team, the CDK implementation was already in place but in poor shape. It hadn’t been run successfully for some time, and none of the environments could be fully trusted because we couldn’t prove how they’d been built. Some parts had been deployed from code, others changed manually through the console. It was an uneasy mix of automation and guesswork.
The codebase itself was a patchwork of CDK, CloudFormation, and SDK scripts. CDK v1 hadn’t offered L2 constructs for every resource we needed (things like VPC endpoints) so developers had filled the gaps with custom templates and ad-hoc SDK scripts to handle missing functionality such as adding users to inline policies. Over time, these workarounds created a fragile, monolithic structure that was difficult to maintain or extend.
When CDK v2 arrived, it was clear a migration would be painful. AWS quite rightly wouldn’t provide support, as the existing implementation didn’t align with their recommended best practices. That was a fair call on their part and a sign that we needed to rethink, not just upgrade.
The more I analysed it, the clearer it became that we weren’t just dealing with technical debt but with a design that had outgrown its purpose. The abstraction had hidden complexity rather than removed it. The environments lacked traceability/history and confidence. What we needed was a fresh start with a toolset built for clarity, modularity, and drift control.
Skills, Scale, and Supportability
Another factor was skills. CDK was still relatively niche. It’s a product aimed primarily at developers, not platform specialists, and finding experienced engineers who could work confidently with it proved challenging. Terraform, on the other hand, is a common language for people who live and breathe infrastructure. Almost every platform engineer we spoke to had used it before. From a recruitment and capability perspective, the move to Terraform was a no-brainer.
Beyond that, the existing pipelines were showing their age. They were single-stage so just one long deployment with no separation of concerns. If something failed halfway through, you were left hunting through logs to find the cause. Refactoring was risky, drift detection was weak, and manual console changes only made things worse. The operational overhead of maintaining confidence in the environments had become unsustainable.
Terraform addressed all of those pain points directly. Its state management, drift detection, and plan-before-apply model gave us the visibility and predictability we simply didn’t have before. It plugged a gap that CloudFormation never really filled.
Rewriting the Engine Mid-Flight
The transition from CDK to Terraform wasn’t glamorous. It was detailed, deliberate work carried out while the platform stayed live. We treated it like any other major refactor: incremental, reversible at the boundaries, and with tight feedback loops.
Terraform forced us to be explicit. There was no black magic running under the covers, no hidden translation layer. What you wrote was what got deployed. That clarity alone changed the conversation. Suddenly reviews made sense again. You could see the intent and the impact side by side in the plan.
We started by rebuilding shared foundations so the networking, IAM roles, monitoring, and logging etc as Terraform modules. Once those core patterns were proven, each service was migrated one at a time, reusing the shared modules for consistency. It wasn’t a quick fix as we took our time to make sure it was done properly but the end results were then functional and predictable.
Recommended by LinkedIn
As the migration progressed, the benefits became obvious.
The platform finally began to feel….well….like a platform should.
Beyond Syntax
The real shift wasn’t from TypeScript to HCL. It was from individual creativity to a shared structure.
CDK encouraged experimentation. Terraform required structure. It forced us to slow down, document, and design deliberately. It made us think about ownership boundaries and how shared patterns could evolve without breaking everything downstream. In short, it made us talk to each other again.
With Terraform in place, we started to treat infrastructure as a it should be – as a versioned, reviewed, and continuously improved product. It wasn’t something you hacked together; it was something you designed for others to build on.
Culture Eats Tooling (Again)
Looking back, the migration wasn’t really about tools. It was about maturity.
CDK suited the early stage of the journey where rapid learning, fast experimentation, getting ideas into the cloud quickly was needed. Terraform suited where we were going. Shared ownership, compliance, and reliability at scale etc. The tool changes simply reflected the organisational change already happening.
That’s the quiet truth about platform engineering. Every tool tells a story about what kind of team you are and what kind you want to be. Moving from CDK to Terraform wasn’t about fashion or preference. It was a statement about values: clarity over convenience, consistency over creativity, and sustainability over speed.
The Takeaway
Switching from CDK to Terraform wasn’t just a technical migration. It was an evolution in mindset.
From building fast to building right.
From infrastructure that worked to infrastructure that could be trusted.
That’s what good evolution looks like; not rejection of the past but refinement of it.
One well-structured module at a time.
Just finished reading your piece, excellent write-up. I liked how you framed the transition from CDK to Terraform not as a tooling debate but as an evolution of mindset, moving from developer-centric expressiveness to platform-level consistency and control. The point about IaC maturity being less about syntax and more about governance really resonated. Many teams underestimate how much drift, ownership confusion, and module sprawl can accumulate once environments scale. Curious, when you made that shift, did you find teams struggled more with unlearning the flexibility of CDK or adapting to Terraform’s stricter modular discipline?
Just fyi, Terraform code should not contain hardcoded configurations! Configuration values should be provided/inject by dimension set.
It's always a valuable exercise to audit those early IaC choices; that transition from abstraction convenience to demonstrable clarity often reveals where hidden complexity was truly lurking. I'm eager to see the specific architectural trade-offs discussed when navigating the inherent divergence between state management philosophies.