
For years, the rallying cry of modern infrastructure has been “Infrastructure as Code.” IaC promised a utopia: version-controlled, repeatable, consistent environments that could be spun up with a single command. Tools like Terraform, AWS CloudFormation, Ansible, and Pulumi became the new bedrock of DevOps, hailed as the antidote to snowflake servers and manual configuration drift. But a quiet, pervasive disillusionment is growing. Beneath the glossy surface of automation, a more troubling reality is emerging. We must confront an uncomfortable truth: for many teams, these tools are creating a new class of problems—complexity, fragility, and cognitive load—that often outweigh the benefits they were designed to deliver.
The Illusion of Simplicity and the Weight of Abstraction
At its core, IaC sells simplicity. Write some declarative code, run a command, and your infrastructure appears. This abstraction is powerful, but it’s also a leaky one. Developers and operators are increasingly insulated from the actual cloud APIs, networking layers, and operating system nuances. When the abstraction works, it’s magic. When it fails—and it does—you are plunged into a debugging nightmare several layers removed from the actual problem.

Consider a simple Terraform module to deploy a virtual network. The code is clean and declarative. But when deployment fails, you aren’t debugging a cloud API call; you’re debugging Terraform’s state file, its provider plugin, the module’s internal logic, and the interaction between resources you’ve declared. The tool hasn’t eliminated complexity; it has redistributed and often multiplied it. You now need deep expertise not just in AWS or Azure, but in the specific idioms, state management, and lifecycle quirks of your chosen IaC tool.
The Stateful Quagmire
The concept of state is IaC’s original sin. Tools like Terraform maintain a state file that is meant to be the single source of truth. This file becomes a critical point of failure—a binary blob that must be meticulously protected, locked, and backed up. State drift occurs when changes are made outside the tool (a quick console fix, a manual scaling adjustment), rendering the state file obsolete and the next deployment a potential destroyer of production environments. The elaborate CI/CD pipelines and remote state backends we build to manage this are not value-add features; they are costly, complex workarounds for a problem the tool itself introduced.
Configuration Sprawl and the “Code” That Isn’t
Calling it “Code” implies a certain rigor: modularity, reusability, testability. In practice, most IaC codebases rapidly devolve into sprawling, monolithic directories of configuration files. Reusable modules become so parameter-heavy they are incomprehensible. Teams copy-paste huge blocks of YAML or HCL, creating a maintenance burden worse than the old bash scripts they replaced. This isn’t software engineering; it’s configuration management at a massive, brittle scale.
- Testing is a Farce: Properly testing infrastructure changes requires spinning up real or near-real environments, which is slow and expensive. Unit tests for IaC are often trivial and miss the integration failures that are most common.
- Review is Perfunctory: How do you meaningfully review a 500-line Terraform diff that declares 20 resources? Reviewers often glaze over, approving changes they cannot possibly comprehend the full impact of.
- The Documentation Lie: The code is supposed to be the documentation. But without understanding the implicit dependencies and provider behaviors, the code is merely a cryptic recipe.
The Vendor Lock-In Paradox
One promised benefit of tools like Terraform was multi-cloud portability—write once, deploy anywhere. This is a fantasy for anything but the most trivial setups. Cloud providers have deeply differentiated services. To do anything useful, you inevitably use provider-specific resources and data sources. Your Terraform code for an AWS EKS cluster with IAM roles, load balancers, and autoscaling is utterly non-portable to Azure AKS. You are now locked in twice: to your cloud vendor and to the IaC tool’s specific implementation of that vendor’s API. Switching either becomes a monumental rewrite project.

The Velocity Trap and Brittle Deployments
IaC is meant to increase deployment velocity. Instead, it often creates a brittle deployment pipeline that teams fear to trigger. A “terraform apply” can take 20 minutes to plan and execute, only to fail on the 47th resource due to a transient cloud API error or a quota limit no one knew about. The rollback story is frequently non-existent or destructive. This encourages batching changes into huge, risky deployments—the exact opposite of the agile, incremental change we seek. The fear of breaking the state file leads to paralysis and workarounds, eroding the very consistency the tool was meant to ensure.
The Skillset Chasm
The industry now demands a mythical “full-stack engineer” who is an expert in application code, container orchestration, and the arcane syntax of three different IaC languages. This creates a skillset chasm. Developers who want to iterate on application logic are blocked by infrastructure complexities they didn’t create. Platform teams become bottlenecks, buried under tickets to adjust security groups or add disk space. The tooling, meant to empower, instead creates friction and silos.
Is There a Way Out? A Call for Pragmatism
This is not a call to abandon automation. It is a call for radical pragmatism. The problem isn’t the idea of programmable infrastructure, but the heavyweight, all-or-nothing frameworks we’ve adopted as dogma.
- Embrace Managed Services Fully: The best infrastructure code is no infrastructure code. Use fully managed services (serverless functions, managed databases, platform-as-a-service) that eliminate the need for vast swathes of provisioning code. Let the cloud vendor own the undifferentiated heavy lifting.
- Right-Size Your Tooling: Do you need a full-blown IaC framework? For many tasks, cloud-native tools like AWS CDK or Azure Bicep (which compile to CloudFormation/ARM) offer a better developer experience by using real programming languages. For configuration, consider simpler, agent-based tools that are better suited for post-provisioning setup.
- Contain Your Scope: Use IaC only for the stable, foundational layers: networking, identity, core policies. For dynamic application infrastructure (especially in Kubernetes environments), the IaC model often fights the platform’s own declarative API (like Kubernetes manifests). Let each layer use its native tooling.
- Admit Imperfection: Sometimes, a well-documented, manual process for a one-off, complex infrastructure item is cheaper and safer than attempting to codify it. Not everything needs to be in version control.
Conclusion: Beyond the Hype Cycle
Infrastructure as Code tools solved critical problems of the past: manual inconsistency and lack of audit trails. But in their zealous adoption, we’ve embraced them as a universal solvent for all infrastructure woes. They are not. They are complex software projects in their own right, with their own bugs, learning curves, and failure modes. They have created a new universe of complexity—state management, provider abstraction, and configuration sprawl—that can stifle productivity and innovation.
The path forward is not more tooling, but more thoughtfulness. We must critically evaluate the true cost of our abstractions, use the right tool for the right layer, and relentlessly seek to delete code rather than write it. The goal was always better, more reliable infrastructure. It’s time to admit that for many, the heavyweight IaC toolkit has become the obstacle, not the path. The real innovation will come from simplifying our systems, not from adding another layer of clever code to manage them.



