Why Infrastructure as Code Matters
The usual pitch for infrastructure as code is speed. I think that pitch is wrong, and I think leading with it is how the practice gets quietly abandoned three months in, because writing CDK really is slower than clicking through a console for the first environment and usually for the second, and by the time it stops being slower the team has already decided the tool was oversold. What I am actually buying is a diff.
Snowflakes compound quietly
Early in my career, standing up a server meant clicking through a console and hoping I remembered what I clicked. Every box was its own snowflake. Nobody could rebuild one from scratch, so nobody ever tried, so the drift compounded for years without anyone noticing it was happening.
The failure mode was never dramatic. No server fell over because someone had bumped a timeout eighteen months earlier. It surfaced the week a box died and three of us spent two days reconstructing its configuration from a wiki page last edited in 2019, a Slack thread, and one person's memory of a change they had made under time pressure.
The diff is the product
Every change to this site's infrastructure produces a plan before it produces a change. I read what CloudFormation is about to do while it is still hypothetical; so does anyone reviewing the PR.
That review is where the value lands, and not because a machine catches mistakes a human would miss, though sometimes it does. It is that a reviewer who has seen this particular thing go wrong before now has something concrete to argue with, six minutes before it happens rather than six hours after.
The clearest case I have is a stack I moved from us-east-1 to us-west-2 in this repo. It looked like a one-line config edit. CloudFormation stack identity is name plus region, though, so the plan described a delete and a recreate, and the stack in question owns the account's only GitHub OIDC provider, shared with two other applications. Deleting it would have broken CI auth for all three at once.
I want to be precise about what saved me there, because it was not my
own carefulness. I had already convinced myself the change was
trivial. What caught it was that the tool refuses to apply anything
without first describing it, and the description contained the word
Delete next to a resource I knew three systems depended
on. A console version of that change has no step where anybody reads
that sentence. The plan is not smarter than I am. It is just harder to
skip than a page of checkboxes I have clicked through fifty times.
Pin the stack name, pin the region in config, let it deploy in place. The whole fix took 20 minutes once the plan had made the problem visible, which is the ratio that keeps me writing infrastructure in a language rather than in a browser tab.
What I am not claiming
I am not claiming this prevents outages. Plenty of correct-looking plans apply cleanly and take production down, and no plan tells me that the change I am making is a bad idea in the first place.
I am also not claiming everything should be encoded on day one. The opposite: automating the parts that are still changing shape is how a team ends up with a thousand lines of infrastructure code describing a system nobody has committed to yet.
And the tools are not interchangeable. CDK, Terraform, and Pulumi make real tradeoffs against each other around state, drift detection, and how much of the provider surface they reach. My argument is about having a reviewable diff at all, not about which tool renders the nicest one.
Four things I would tell myself starting out
Automate less than I wanted to on day one. The first thing I reached for was always the most annoying manual step, which was reliably also the one still changing shape every other week.
Write tests for the infrastructure code. CDK and Terraform both ship assertion frameworks, and the tests that have paid for themselves in this repo are the ones pinning invariants no plan will show me, like which stack owns a shared resource.
Treat state files like production data, because that is what they are. A lost state file turns a reviewable diff back into the 2019-wiki-page problem, which is the specific thing I adopted the tool to stop having.
Write down why. This one has saved me more time than the other three combined and it is the one I skipped longest. A plan tells me what changed; it has never once told me which constraint made the change necessary, and a year later that was the only question anyone asked me about the us-east-1 move.
How I pitch it now
When I have pitched this since, I stopped opening with speed. I open by running a plan against a change the team is already nervous about, and then I stay quiet while somebody finds something in the output. That has worked all 3 times I have tried it, on changes I picked in advance because I suspected they were interesting.