A pile of scripts is not infrastructure as code
Almost every organization I have worked with already has some version of infrastructure automation in place before they call it infrastructure as code. A folder of shell scripts that provision a server, a handful of Terraform files that one engineer runs from their laptop, a runbook that gets copied and modified for each new environment. These are useful, but they are not the same thing as a governed infrastructure as code practice, and the gap between the two is where most of the real value sits.
I have run infrastructure as code maturity reviews across environments ranging from a handful of VMs to a full cloud platform supporting dozens of internal applications, and the gap shows up in the same five places almost every time, regardless of which cloud provider or which tool the team started with.
1. State needs to live somewhere shared and locked
Terraform running from individual laptops with local state files is the single most common problem I find during an infrastructure as code maturity review. Two engineers running an apply within minutes of each other can corrupt shared infrastructure without either one realizing it until something breaks. Moving state to a remote backend, an S3 bucket with DynamoDB locking on AWS, or Azure Storage with native state locking on Azure, is not an advanced practice. It is a baseline requirement before calling anything production ready.
2. Changes go through a pipeline, not a terminal
If infrastructure changes can still be applied by someone typing a command directly into their terminal, the code in the repository is a suggestion, not a source of truth. I push teams toward a pipeline, Atlantis if the team wants something that lives close to the pull request workflow, or a platform like Spacelift or env0 if they want more built-in governance, where every change goes through a pull request, an automated plan output that the team can review, and an apply step that only runs through the pipeline itself. This single change does more to build trust in the infrastructure code than almost any other practice, because the plan output becomes the record of what is about to happen, not a guess based on reading a diff.
3. Modules need owners and versioning, not copy and paste
Early infrastructure as code adoption often involves copying a working configuration from one project to the next, with small modifications each time. This works until a security requirement changes and the fix has to be applied manually across a dozen copies that have all drifted slightly apart. Building shared, versioned modules in a private registry, with semantic version tags and a clear owner, solves this, but it requires treating infrastructure code with the same discipline as application code, including code review and a deprecation process for old versions. A team that pins every consuming project to an exact module version, rather than a floating latest tag, is the one that can roll back cleanly when a module update breaks something.
4. Policy as code catches what review misses
Human review is good at catching logic errors, but it is inconsistent at catching policy violations such as an unencrypted storage bucket or a security group open to the entire internet. Adding automated policy checks to the pipeline, using a tool like Checkov or tfsec for static analysis, or Open Policy Agent and Sentinel for more customized organizational rules, catches these issues before they reach a plan review and removes the burden from reviewers who should not need to memorize every compliance rule from memory during a code review. I treat a failed policy check the same way a failing unit test is treated in application code: it blocks the merge, full stop, no manual override without a documented exception.
5. Drift detection closes the loop
Even a well-governed pipeline cannot stop someone from making a manual change directly in the cloud console during an incident, and that happens more often than most teams admit. Without drift detection, the infrastructure code silently stops reflecting reality, and the next planned change produces confusing results, sometimes threatening to revert a fix that was applied manually under pressure. I schedule regular drift checks, either through the pipeline’s built-in detection or a scheduled plan run, and treat any manual change as something that needs to be reconciled back into code within days, not left as a permanent exception that everyone quietly works around.
Maturity is a pipeline, not a tool choice
Teams sometimes ask which infrastructure as code tool is best, when the more useful question is whether the governance around whichever tool they choose actually enforces shared state, pipeline-only changes, versioned modules, policy checks, and drift detection. A team running an older tool with strong governance will have fewer incidents than a team running the newest tool with none of these practices in place. I have watched both setups in the same organization, running side by side on different platforms, and the governance gap explained nearly every incident that traced back to infrastructure rather than application code.
Leave a Reply