Skip to content

Infrastructure as Code in Practice: Power, Pipeline, and What New Users Get Wrong

Published: 05/20/2026


Infrastructure as Code is one of those ideas that sounds obvious in retrospect. Instead of clicking through a cloud console or running CLI commands by hand, you describe what you want your infrastructure to look like, commit that description to version control, and let tooling handle the rest. The infrastructure becomes a text file. The text file becomes reviewable, versioned, repeatable, and auditable in ways that a sequence of manual steps never can be.

That simplicity is real. But the gap between "infrastructure as code" as a concept and "infrastructure as code" done correctly in production is larger than most introductory material lets on. The risks are real, the failure modes are specific, and the security implications of getting it wrong are significant. This post covers what IaC actually gives you when done right, what consistently goes wrong for teams new to it, how to structure a pipeline that is worth trusting, and where the tooling ecosystem currently stands.


What Infrastructure as Code Actually Gives You

The obvious benefit is repeatability. An environment defined in code can be recreated exactly, as many times as needed, without relying on anyone's memory of what was clicked where. That matters for disaster recovery, for spinning up parallel environments, and for onboarding engineers who need to understand what exists without reverse-engineering it from a console.

The less obvious benefit is the review process it enables. When infrastructure changes are pull requests, they go through the same code review workflow as application code. A security engineer can look at a proposed change and catch a public S3 bucket or an overly broad security group before it ever reaches an environment. That catch happens at the lowest-cost moment: before deployment, not after an incident. Teams that skip this workflow and apply changes directly are discarding one of the most valuable properties of the entire approach.

Drift detection is the third major benefit, and it is frequently underutilized. IaC tools track the current state of managed resources and can identify when the real world has diverged from the declared configuration, which happens when someone makes a manual change outside the IaC workflow. Undetected drift is how you end up with infrastructure that your code says is one thing and your cloud environment says is another. Running regular plan operations in CI catches this early.


What New Users Consistently Get Wrong

State files are not safe to ignore.

Every major IaC tool maintains a state file that maps your declared configuration to real cloud resources. In Terraform and OpenTofu, this is terraform.tfstate. What new users frequently miss is that this file often contains plaintext sensitive values: database passwords, generated credentials, private keys for resources that were created. Committing it to any repository, public or internal, is a credential leak. Storing it locally means it can be lost and that only one person can run applies.

The industry standard is a remote state backend with encryption at rest and strict access controls. For AWS this means S3 with SSE-KMS encryption and a DynamoDB table for state locking. GCS and Azure Blob Storage have equivalent mechanisms. The backend bucket or container should have an access policy that restricts reads to the automation principal and a small number of named humans. Most teams are too permissive here. Pair this with marking sensitive outputs as sensitive = true in your configuration, which prevents values from appearing in plan and apply logs even though they still exist in state.

OpenTofu goes a step further with built-in state encryption at the tool level. Rather than relying solely on the backend to encrypt the file after it arrives, OpenTofu can encrypt state before writing it, using AWS KMS, GCP Cloud KMS, or a local PBKDF2 key provider. This is a meaningful security improvement and one of the more significant practical differentiators OpenTofu has introduced over Terraform. terraform.tfstate, terraform.tfstate.backup, and .terraform/ should be in .gitignore from the moment a repository is created, before any other work happens.

The automation principal is over-permissioned.

When teams are getting started, the path of least resistance is to create an IAM role or service account for Terraform with broad permissions. Often this means administrator access or something close to it. The reasoning is practical: broad permissions prevent unexplained failures while you are figuring out what the tool actually needs. The problem is that this principal persists, and it becomes the credential that lives in your CI/CD secrets. An attacker who obtains it has the keys to your entire cloud environment. The right approach is to scope automation credentials to exactly what the IaC configuration needs, per environment, and review that scope as the configuration evolves.

Blast radius is not thought about until it matters.

A terraform apply against a production environment with no safeguards is a high-risk operation. New users running applies locally, against production, with nothing between them and a destructive change have no real protection against mistakes. Replacing a resource you did not intend to replace, or deleting one, can cause outages that take hours to recover from. Separating environments, requiring plan review before apply, and using targeted applies cautiously are the operational habits that prevent this class of incident.

Community modules are treated as trusted dependencies.

The Terraform and OpenTofu registry contains thousands of community-published modules. Pulling in a module to provision an RDS instance or a VPC saves time, but it also means running someone else's code against your cloud account with your credentials. Modules that are not actively maintained, not widely used, or not from a source you trust should be reviewed before use, the same way you would review an open-source library before adding it to an application dependency.

Secrets end up in the wrong places.

Hardcoding credentials in .tf files is the most obvious mistake, and most people know to avoid it. The subtler version is using terraform.tfvars to hold sensitive values and then committing it alongside the configuration. The correct pattern is to pull secrets from a secrets manager at plan/apply time, but it is worth understanding how local variable files work for the cases where that is not yet in place.

terraform.tfvars and any file ending in .auto.tfvars are loaded automatically by Terraform and OpenTofu without any flags. Any other .tfvars filename must be passed explicitly with -var-file=. That distinction matters: naming a local secrets file something like secrets.local.tfvars means it will not be loaded unless you deliberately pass it, which reduces the risk of it being used unintentionally or noticed in a directory listing and assumed safe to commit. Set chmod 600 on the file so only the owner can read it, and keep it out of synced directories.

The committed pattern should be a terraform.tfvars.example file that shows the expected variable names with placeholder values, which gives the next engineer enough context to create their own local copy. The real file never enters the repository. Add *.tfvars to .gitignore and carve out an exception with !*.tfvars.example. As a backstop, run a pre-commit hook using a tool like Gitleaks or detect-secrets, both of which scan staged files for credential patterns before a commit goes through. A .gitignore entry is a convention; a pre-commit hook is a control. Both belong in any IaC repository.


Building a Pipeline Worth Trusting

A secure IaC pipeline separates plan from apply and treats both as distinct, controlled operations. The structure that holds up in practice looks like this: a pull request triggers a plan operation in CI. The plan output is posted as a comment or artifact. A human reviews what will change. On merge, the apply runs against the target environment using a scoped automation credential. No one runs applies locally against shared environments.

Remote state is a prerequisite for this workflow. Local state files cannot be shared across a CI system and a team of engineers without conflict. Terraform Cloud, S3 with DynamoDB locking, GCS, and Azure Blob Storage with locking are all viable backends. The critical properties are encryption at rest, access control that limits who and what can read or modify state, and state locking to prevent concurrent applies from corrupting it.

Policy as code belongs in the pipeline before the apply stage. Tools like Open Policy Agent allow you to write rules that evaluate a plan's proposed changes and block applies that would violate them: no public storage buckets, no security groups open to the world, no IAM policies with wildcard actions. Running these checks automatically means the review burden on engineers is lower, because entire classes of misconfiguration are caught programmatically before they require human attention. TerraGuard was built specifically for this layer.

The automation credentials used in CI should be short-lived where the platform supports it. AWS supports OIDC federation with GitHub Actions and GitLab CI, which means the pipeline can assume an IAM role for the duration of a job without storing a long-lived access key anywhere. This eliminates an entire category of credential leak. GCP and Azure have equivalent mechanisms. If you are storing a long-lived cloud access key in CI secrets to run Terraform, there is a better option available and it is worth switching to it.

Environment isolation matters at the credential level, not just the configuration level. A single credential that can apply changes to both staging and production is a single point of failure. Separate automation principals per environment, with separate scoped permissions, mean that a compromised staging credential does not hand an attacker production access.


On Terraform and OpenTofu

Terraform is where most people start. It has been the dominant IaC tool for cloud infrastructure since it was released by HashiCorp in 2014, and for good reason. The HCL configuration language is readable, the provider ecosystem is enormous, and the workflow is well-documented. For most teams evaluating IaC tools, Terraform is the obvious entry point.

In August 2023, HashiCorp changed Terraform's license from the Mozilla Public License 2.0 to the Business Source License 1.1. The BSL restricts use in products and services that compete with HashiCorp offerings. For teams using Terraform to manage their own infrastructure, the practical impact is minimal. For organizations building tooling around Terraform, or deploying it as part of a managed service, the licensing terms require scrutiny.

OpenTofu emerged from that moment. It is an open-source fork of Terraform maintained under the Linux Foundation, licensed under the MPL 2.0, and governed by a community that includes contributors from across the industry. For the majority of use cases, it is a drop-in replacement: same HCL syntax, same provider ecosystem, same state format. The configuration that runs on Terraform runs on OpenTofu without modification.

Beyond licensing, OpenTofu has begun shipping features that distinguish it from Terraform. State encryption is the most significant: OpenTofu supports encrypting state files and plan files at rest using pluggable key providers, including AWS KMS, GCP Cloud KMS, and PBKDF2 for local use. This addresses one of the longstanding pain points of IaC state management directly in the tool rather than relying entirely on backend-level encryption. Provider-defined functions are another addition, allowing providers to expose custom functions callable from HCL configurations.

It is worth acknowledging that AWS, Azure, and GCP each ship their own native IaC tooling. AWS has CloudFormation, a declarative JSON and YAML templating system that covers the full AWS service catalog, and the AWS Cloud Development Kit, which lets engineers define infrastructure using general-purpose programming languages like TypeScript, Python, or Go that compile down to CloudFormation. The CDK appeals to developers who want loops, abstractions, and type safety rather than raw YAML. Azure has ARM Templates, the underlying JSON format that Azure Resource Manager consumes, and Bicep, a cleaner domain-specific language that transpiles to ARM and was introduced largely because raw ARM JSON is notoriously verbose and difficult to maintain. GCP has Cloud Deployment Manager, a YAML and Jinja2 based tool that covers GCP resources, and Config Connector, which takes a different approach entirely by letting teams manage GCP resources as Kubernetes custom resources using familiar kubectl workflows.

Each of these tools has real strengths. They integrate deeply with their respective platforms, often support new services before third-party providers do, and require no additional tooling beyond what the cloud provider already supplies. For teams that are genuinely single-cloud and plan to stay that way, the native option is worth evaluating seriously.

The reason most organizations land on Terraform or OpenTofu anyway is that cloud-agnostic tooling solves a different problem. A team that manages AWS infrastructure today and adds Azure or GCP resources later does not have to learn a second IaC language or maintain two separate pipeline workflows. Engineers who know HCL can move between organizations, teams, and cloud environments without retraining. The module patterns, the state management approach, the CI/CD structure are all consistent regardless of which provider is on the other end. That standardization has real value, and for organizations operating across more than one cloud, it largely makes the native tools a non-starter regardless of their individual merits.

The choice between Terraform and OpenTofu is, for most teams, a question of licensing philosophy and organizational preference rather than technical capability. The tools are functionally equivalent for standard deployments. What matters more than the tool choice is whether the workflow around it is sound. A well-structured OpenTofu pipeline is better than a poorly structured Terraform one, and vice versa.


The Right Mental Model

Infrastructure as Code is not a shortcut. It is a discipline. The teams that get the most value from it are the ones that treat their IaC repositories with the same rigor they apply to application code: pull request reviews, automated testing, security scanning in the pipeline, and access controls that match the blast radius of what the code can do.

The teams that run into trouble are almost always the ones that adopted the tooling without adopting the workflow. They have Terraform configuration in a repository, but it is applied locally by whoever needs to make a change. The state file is in an S3 bucket that half the engineering team has access to. The IAM role used for automation has more permissions than it needs because nobody went back to tighten it after the initial setup.

The most effective organizations take this a step further and establish IaC standards at the organization level. How modules are structured, how state backends are named and organized, how environments are separated, how credentials are passed, what the PR review process looks like before an apply is permitted. When those standards exist and are documented, any engineer in the organization can read another team's IaC configuration and understand it immediately. They know where the state lives, what the naming conventions mean, and how the pipeline is structured without having to ask. That shared understanding compounds over time. Onboarding is faster, reviews are more meaningful, and incidents are easier to diagnose because the infrastructure is predictable by design.

Infrastructure as Code makes it possible to build cloud environments that are reproducible, auditable, and defensible. Whether you reach for Terraform or OpenTofu, the tooling is mature enough that the differentiating factor in any deployment is the decisions made around it, not the tool itself.


Further Reading

Tool Documentation
Terraform developer.hashicorp.com/terraform
OpenTofu opentofu.org
AWS CloudFormation docs.aws.amazon.com/cloudformation
AWS CDK aws.amazon.com/documentation-overview/cdk
Azure Bicep Microsoft Bicep Docs
Azure ARM Templates Microsoft ARM Templates Docs
GCP Config Connector docs.cloud.google.com/config-connector
GCP Deployment Manager docs.cloud.google.com/deployment-manager