Build Stack Review

Infrastructure as Code Tool Evaluation for Platform Teams

Platform teams need drift control, reproducibility, and developer empowerment before picking a tool.

· 10 min read
Cover illustration for “Infrastructure as Code Tool Evaluation for Platform Teams”
Platform Engineering and Developer Infrastructure · October 8, 2026 · 10 min read · 2,355 words

Infrastructure as code has moved from a niche discipline to the default operating model for any team that needs repeatability, auditability, and governance across cloud, on-prem, and mixed hardware. This piece argues that choosing a tool for that model is less about comparing feature lists and more about checking whether a tool actually enforces drift control, cross-environment reproducibility, and a sane model of developer empowerment.

Why IaC evaluation has gotten harder for platform teams

When you configure things manually, clicking through a console by hand, you get inconsistency that stays invisible until it breaks something. A firewall rule set by hand in one account, a runtime version bumped manually on one node, a network policy tweaked during an incident and never written down: each of these drifts quietly away from whatever the team believes is running, until a production failure or an audit finding forces the gap into view. Infrastructure as code answered this by putting the definition of the system into version control. A rollback became a git revert. A misconfigured security group surfaced as a comment on a pull request, caught before it ever became a line item in a postmortem.

That answer, though, has stopped being simple. The tool landscape that supports it has multiplied and split at the same time. A newer model, often called infrastructure from code, now asks whether application-scoped resources need a separate infrastructure layer at all, or whether they can be declared directly alongside the application. License and governance changes inside the Terraform ecosystem have pushed different teams in different directions: some moved to OpenTofu, some to Pulumi, and some are reconsidering whether they need a distinct provisioning tool.

Faced with that many options, the natural move is to build a feature checklist and start scoring tools against it. That move answers the wrong question. A checklist can tell you what a tool can do. It cannot tell you whether a tool enforces the properties your platform actually needs to guarantee, which is a different question than which tool is best. The question a platform team should be asking is what properties the platform must guarantee, and which of the available tools actually enforce them.

The three properties a platform team must guarantee before picking any tool

Every infrastructure tool makes implicit trade-offs on three axes: drift control, reproducibility across environments, and the model of developer empowerment it supports or undermines. Naming these explicitly, before looking at a single vendor page, is what turns a tool evaluation into a defensible decision grounded in the team's own priorities.

Drift control is the gap between what's defined and what's actually running. That gap causes a large share of production incidents and compliance failures, because the gap is where reality and intent diverge without anyone noticing. An urgent console fix applied during an incident creates exactly this gap: the discipline that closes it is a rule that no manual change ships without a follow-up pull request, paired with scheduled drift detection that checks the live state against the declared one. Tools differ meaningfully in how they handle this. Some detect drift passively, comparing state on a schedule. Some enforce it actively, blocking out-of-band changes before they take effect. Some offer no native drift detection at all, leaving the discipline entirely up to the team. A platform team that fails to name drift control as a first-class requirement tends to pick a tool optimized for how fast it can stand up resources, then discovers that drift has accumulated unnoticed, usually under the worst possible timing.

Reproducibility across environments means staging, production, dev, and disaster recovery all come from the same definition, with different inputs feeding that one definition rather than different configurations maintained in parallel. "They're configured differently" should never again be an acceptable line in a postmortem. This isn't a comfort feature for developers who dislike surprises. Reproducibility is what makes CI/CD and production deployments trustworthy, and it's the mechanism that makes automated recovery actually work: when environment parity is enforced at the tool level, recovering from an outage becomes redeploying a known-good definition instead of manually tracing which setting drifted where. Durable reproducibility depends on pinned references and input-derived hashes, not loose version tags, because a tag can move but a hash can't.

Developer empowerment describes how a tool distributes infrastructure decisions: who gets to do what, and with which guardrails. DevOps handed developers operational ownership, and that came with a cognitive cost, since teams suddenly had to manage Terraform modules, Helm charts, and monitoring setups themselves on top of writing application code. Platform engineering is the correction: the platform team absorbs the complexity, developers interact through a defined interface such as a golden path or a self-service template, and a tool either supports that split or actively fights it. A tool that requires every application developer to understand the internals of a cloud provider's API undercuts the empowerment goal no matter how capable it is technically.

These three properties carry equal weight. Skipping any one of them while evaluating a tool tends to produce a decision the team regrets once the tool is already load-bearing.

Diagram: Three Properties Every Platform Team Must Guarantee. Visualizes: Visualize three equal-weight evaluation criteria that a platform team must define before selecting any IaC tool: (1) Drift Control — the gap between what's defined and what's…

How IaC tool categories map to the three criteria

No category of IaC tool scores well on all three criteria at once, so mature platform teams end up composing several tools to cover the gaps between them.

General-purpose provisioning engines, Terraform, OpenTofu, and Pulumi among them, remain the core of most stacks. OpenTofu is the Linux Foundation-stewarded fork of Terraform; migrating to it is typically a binary swap, and it has since diverged with its own features, including state and plan encryption and earlier variable evaluation, addressing two of the most common complaints leveled at its predecessor. Pulumi takes a different route, letting teams write infrastructure in general-purpose languages, including TypeScript, JavaScript, Python, Go, the.NET languages, and Java, plus YAML and HCL. Loops, functions, unit tests, and shared libraries apply to infrastructure code the same way they apply to application code. On reproducibility, state file management is still a sore point across Terraform and its compatible tools: state lock errors, drift between the state file and what's actually deployed, and outright corrupted state files are known failure modes once a stack reaches scale. HCL has a learning curve, and understanding provider internals adds cognitive load for application developers who didn't sign up to learn a cloud provider's resource model, so Pulumi lowers that bar if your team is already comfortable in one of its supported languages.

Terragrunt sits a layer above these engines as an orchestration and governance tool. It's a thin wrapper around Terraform and OpenTofu that keeps configuration from repeating itself across environments, manages remote state automatically, and orchestrates multi-module deployments through dependency graphs. It scores well on drift control, since it enforces consistency across environment copies by construction, but it adds another layer of abstraction that developers have to learn on top of the provisioning engine underneath it.

Checkov covers a different need entirely: IaC security scanning. It's an open-source policy-as-code scanner that catches misconfigurations before they ship, covering Terraform, CloudFormation, Kubernetes, Helm, ARM templates, Serverless, AWS CDK, and Docker, and it plugs into CI/CD so problems appear as pull-request comments before deployment. Checkov closes a gap the provisioning tools leave open: it enforces the compliance and security side of drift control by stopping bad definitions before they merge, though it has no visibility into drift that happens later, once the infrastructure is already live.

Configuration management tools, Ansible, Chef, Salt, and Puppet, were built for a different job: defining the desired state of machines and services, meaning installed packages, files, and system settings, while cloud resource provisioning was left to other tools. The pattern that recurs in large organizations is a provisioning engine handling cloud resources paired with a configuration management tool handling OS hardening and application configuration, each one doing the job it was actually designed for.

The newest entrant, infrastructure from code, eliminates the separation between application and infrastructure definitions: infrastructure needs are declared directly in the application codebase, and the framework handles provisioning from there, using only the application repository and its existing codebase conventions. That scores strongly on developer empowerment, and it removes the two-codebase coordination problem that provisioning engines impose. It's scoped to resources an application actually owns, such as databases, queues, and caches, and it has nothing to say about networking infrastructure, DNS, CDN rules, or the compliance-driven security group setups that live outside any single application's boundary. IaC tools still hold that ground, because the cross-cutting infrastructure belongs to no application by itself.

Read against the three criteria, each category has a natural home on the map. If a platform team is clear on its priorities, it can place these tools quickly, without working through an exhaustive feature comparison first.

Developer portals and golden paths as the empowerment layer the tools alone cannot provide

Provisioning tools define what infrastructure exists. They don't define how a developer who isn't a platform specialist is supposed to interact with that infrastructure, and without that second layer, even a well-chosen provisioning tool ends up pushing cognitive load onto developers that the platform team then absorbs as toil.

Golden paths close that gap: pre-approved, opinionated, self-service templates for common engineering tasks. A platform team uses them to encode organizational knowledge, compliance requirements, and environment standards into something a developer can invoke without first learning the provisioning tool chain underneath it. A developer who needs a new service doesn't need to understand Terraform modules or OpenTofu providers, as long as the golden path already encodes the right module, the right security group, and the right compliance checks.

Developer portal frameworks are how golden paths get delivered in practice. Spotify originally built Backstage.io and later donated it to the CNCF, and it has become the dominant open-source framework for building these portals. Commercial alternatives, including Port and Cortex, trade some of that implementation flexibility for a faster path to a working portal. The choice between them tracks directly back to the empowerment criterion from earlier: a platform team with real engineering capacity to spare can absorb the implementation work Backstage requires, while a team earlier in its platform journey often gets more value from a managed option that ships a working portal sooner.

This layer's value is visible in onboarding. A new engineer who can self-serve a new service, a new environment, and a working deployment pipeline through a golden path reaches productivity far faster than one working through a manual, runbook-driven process, and every manual onboarding sequence pulls a senior engineer away from building platform capability to instead walk someone through steps a template could have handled. A fintech platform team automated its compliance workflows through an internal developer portal, and its regulatory audit preparation time dropped substantially each quarter, because it codified compliance checks into the golden path rather than re-deriving them by hand before every audit cycle.

Portals and golden paths solve the empowerment problem that provisioning tools, on their own, cannot. They don't solve a different problem sitting one layer below the cloud resource layer, in the developer's own machine and the environment the code actually runs in.

Environment-level reproducibility below the provisioning layer

Most IaC stacks reproduce cloud resources reliably. They leave the developer environment layer, meaning OS libraries, language runtimes, package versions, and tool chains, almost entirely unmanaged, and that's where the drift causing staging integration failures actually starts.

Divergent OS libraries between a developer's laptop and a CI runner, unpinned local package dependencies that resolve to different versions on different days, and environment variables set outside any tracked process: all of these produce the same reproducible staging integration failures that IaC was originally adopted to prevent. The failure mode hasn't gone away. It has moved down one layer, from the cloud resource layer that Terraform and OpenTofu manage well to the environment layer that neither tool touches.

The problem compounds once a team runs across architectures. If you run Linux x86-64 in CI, macOS ARM on developer laptops, and Linux ARM in edge production, you have no guarantee that these three environments behave the same way unless the environment definition itself is hermetically sealed and portable across architectures, not just across machines of the same kind. Containers partially solve this, but they introduce their own overhead: a build-scan-tag-push cycle adds latency to every change, container images grow large and opaque as they accumulate layers over time, and the container boundary itself separates a developer from the local tools and dotfiles they actually work in day to day.

Nix Flakes address this gap directly, functioning less like a package manager and more like a platform for defining an environment completely. A flake is a hermetically sealed description of its inputs and outputs, whether that output is a development environment, a Docker image, or an entire server configuration, and the accompanying flake.lock file pins the exact commit hashes of every dependency involved, so the environment stays reproducible across time and across machines. A practical path into this keeps Nix Flakes scoped narrowly, to environment definition and development builds, while leaving cloud resource provisioning to Terraform or OpenTofu and deployment to kubectl as before; Nix coexists with the rest of an existing stack rather than requiring a wholesale migration, which keeps the adoption risk low.

An emerging option goes a step further by removing the container from the equation at the Kubernetes runtime layer. With a Nix shim in place, a hash-pinned environment can run without ever building or pulling a container image: a pod starts from a stub, then activates the exact runtime it needs from a node-local store. Because every pod on a node draws from the same shared system library catalog, these Nix store paths behave like container layers with a much higher cache hit rate, since the underlying files are already shared across pods. Containers remain a legitimate way to package and deploy an application. The mistake is using the container image itself as the unit of environment definition, when that job belongs to a hermetic, hash-pinned description of the environment, one layer below where most IaC evaluations currently stop looking.

More in Platform Engineering and Developer Infrastructure