Infrastructure as Code in 2026: Terraform, OpenTofu, and the State You Have to Manage
Infrastructure as code is the practice of describing your servers, networks, databases, and DNS records in version-controlled text files, then having a tool reconcile reality to match that description. It replaces the console-clicking, the tribal knowledge, and the “don’t touch that instance, nobody knows how it was set up” with a repository you can diff, review, and replay. That much has been settled for a decade. What has not been settled — and what makes writing about IaC in 2026 different from writing about it in 2021 — is two things the introductory tutorials still skip. First, the most popular tool in the space, Terraform, is no longer open source, and its open-source fork OpenTofu is now a serious, independently-governed project that thousands of teams have migrated to. Second, the genuinely hard part of IaC was never the syntax; it is the state file, the tool’s model of what it believes exists, and almost every painful IaC incident traces back to state drifting from, corrupting against, or disagreeing with reality. This post is the foundation: the declarative model, the licensing landscape you have to choose within, real configuration, and the operational truths that decide whether IaC makes your life better or gives you a new and exciting way to delete production.
What Infrastructure as Code Actually Buys You
The pitch is usually delivered as a list of nouns — version control, reproducibility, collaboration, automation — and all of them are real, but it is worth being precise about why each one matters, because the value is not evenly distributed.
- Version control means infrastructure changes live in git alongside the application. You get blame, history, and the ability to answer “what changed right before the outage started” with
git loginstead of an archaeology dig through CloudTrail. - Reproducibility means the same configuration produces the same infrastructure. This is what makes staging actually resemble production, and what lets you stand up a disaster-recovery region from a file rather than from memory.
- Code review means a change to a security group or an IAM policy goes through the same pull-request scrutiny as a change to application logic. The
planoutput in the PR shows exactly what will be created, changed, or destroyed before anyone approves it. - Automation means no human in a console, which removes the entire class of “someone fat-fingered the wrong checkbox” failures.
What IaC does not buy you is also important, because over-selling it is how teams get burned. It does not make infrastructure changes safe — a terraform apply can delete a database just as thoroughly as a console can, faster and across more resources at once. It does not eliminate the need to understand the underlying cloud; HCL that provisions a misconfigured VPC is just a misconfigured VPC with better git history. And it does not, on its own, prevent drift — the gap between what your code says and what actually exists when someone makes an out-of-band change. IaC is a discipline that enables safety and reproducibility; it does not confer them automatically.
The Declarative Model and the State File
The core mental shift in IaC is from imperative (run these commands to build a server) to declarative (here is the server I want to exist; figure out how to make that true). You describe the desired end state, and the tool computes the difference between that and what exists, then makes the minimal set of changes to close the gap. This is the same reconciliation-loop idea that drives Kubernetes controllers, and it is powerful precisely because you never write the “how” — you write the “what” and the tool derives the steps.
To compute that difference, the tool needs to know what currently exists. It learns this from the state file — a JSON document mapping each resource in your configuration to the real-world object it manages (this aws_instance.web corresponds to instance i-0abc123). State is the linchpin of the entire system, and understanding the three-way relationship it sits in explains most of IaC’s behavior:
HCL config state file real cloud
(desired state) (last-known state) (actual state)
│ │ │
└────────► plan: refresh state vs real, ◄───┘
then diff desired vs state
│
▼
apply: create / update / destroy
│
▼
state file updated to match reality
terraform plan does two things people often conflate. It refreshes — querying the cloud to see whether the real objects still match what state recorded (this is how drift is detected) — and it diffs your desired configuration against that refreshed state to produce a change set. apply executes the change set and writes the new reality back into state. Everything that goes wrong with IaC is some perturbation of this loop: state that no longer matches reality (drift), two people applying against the same state at once (corruption), or state that records a resource the configuration no longer mentions (orphans, or an accidental destroy).
The single most important consequence: the state file is sensitive and load-bearing. It contains every attribute of every resource, including, for many resource types, secrets in plaintext — database passwords, private keys, generated tokens. Lose it and the tool no longer knows what it manages; leak it and you have leaked your infrastructure’s secrets. This is why state management, not syntax, is the real subject of IaC.
Terraform, OpenTofu, and the License Fork
For most of its life Terraform was open source under the Mozilla Public License. In August 2023, HashiCorp relicensed Terraform (and its other core products) to the Business Source License 1.1 — a “source-available” license that forbids using the software to compete commercially with HashiCorp. The community reaction was immediate: a group of vendors and contributors forked the last MPL-licensed version, first as OpenTF and then as OpenTofu, and placed it under the Linux Foundation for neutral governance. OpenTofu reached general availability with version 1.6 in January 2024. HashiCorp itself was acquired by IBM in a deal that closed in early 2025, which did not reverse the license change.
So in 2026 you are choosing between two tools that began as the same codebase and still speak the same language (HCL) and largely the same state format, but are governed by different entities under different licenses and have begun to diverge in features.
| Dimension | Terraform | OpenTofu |
|---|---|---|
| License | BUSL 1.1 (source-available, use restrictions) | MPL 2.0 (true open source) |
| Steward | HashiCorp (IBM) | Linux Foundation |
| Language | HCL | HCL — same syntax |
| State encryption | Client-side via HCP/Vault tooling | Built-in client-side state encryption |
| Module/provider registry | registry.terraform.io | registry.opentofu.org |
| Premium offering | HCP Terraform, Stacks | Community; no vendor upsell |
| Governance | Single vendor | Open, multi-company technical steering |
For an individual, a homelab, or most companies, the practical differences are small and the migration is genuinely close to a drop-in: install OpenTofu, point it at your existing configuration and state, and tofu plan behaves like terraform plan. OpenTofu has since shipped features Terraform did not have at the same time — notably built-in state encryption (so the secrets-in-state problem above can be mitigated without external tooling), early variable evaluation, provider-defined functions, and an -exclude flag for targeted operations. Terraform, in turn, has its own trajectory with HCP Terraform Stacks and ecosystem integration. The honest summary: if your use of Terraform could conceivably be read as “competing with HashiCorp,” or you simply want a genuinely open-source tool with neutral governance, OpenTofu is the safer long-term bet; if you are deep into HashiCorp’s commercial platform, staying on Terraform costs you nothing today. The commands and concepts below apply identically to both — substitute tofu for terraform at the CLI.
A Real Configuration
A Terraform/OpenTofu project is a directory of .tf files. The pieces you will use in almost every project: a provider block declaring which cloud and version, resources describing what to create, variables for parameterization, and outputs for surfacing values.
|
|
|
|
Two details in there separate a real configuration from a tutorial snippet. The AMI is resolved with a data source instead of a hard-coded ami-0c55b159... that rots the moment Amazon publishes a new image — a hard-coded AMI is one of the most common reasons a year-old Terraform example no longer applies cleanly. And the provider version is pinned with a pessimistic constraint (~> 5.60) so a major provider release does not silently change behavior under you.
The workflow itself is four commands, and the discipline is entirely in the second one:
|
|
plan is where safety lives. A plan that reports “1 to add, 0 to change, 0 to destroy” is routine; a plan that reports “0 to add, 0 to change, 14 to destroy” when you only meant to rename a tag is the warning that saves your weekend. Never let apply run without a human or a policy check reading the plan first.
State Management Is the Hard Part
For anything beyond a solo experiment, the default of storing state in a local terraform.tfstate file is a liability: it lives on one laptop, it is not locked against concurrent edits, and it is not backed up. Remote state fixes all three. The configuration is the backend block shown above; the canonical choice on AWS is an S3 bucket, with versioning enabled so you can roll back a corrupted state.
The notable 2026 change is locking. For years the standard pattern paired the S3 backend with a DynamoDB table purely to hold a lock so two engineers (or two CI jobs) could not apply against the same state simultaneously and corrupt it. Terraform 1.10 and OpenTofu added native S3 state locking using conditional writes (use_lockfile = true), which makes the separate DynamoDB table unnecessary for new setups — one less piece of infrastructure to provision just to bootstrap your infrastructure tooling. If you maintain older configurations, the DynamoDB pattern still works and is still widely deployed; you do not need to rush the migration.
A few state realities every practitioner eventually meets:
- Secrets live in state. Resource attributes like RDS passwords are stored in plaintext in the state JSON. Restrict access to the state bucket as tightly as you would a secrets store, enable encryption at rest, and prefer OpenTofu’s built-in state encryption or a tool like SOPS for the client-side gap. Treat the broader topic with the same seriousness as application secrets management.
- Drift is detected, not prevented. If someone changes a resource in the console, the next
planshows the drift and proposes to revert it. That is a feature — but it means out-of-band changes silently fight your code. The fix is cultural (nobody touches managed resources by hand) backed by policy. - Importing existing resources. Infrastructure that predates your IaC can be brought under management with
importblocks (orterraform import), which writes the existing object into state so the tool stops trying to recreate it. This is how you migrate a click-ops environment into code incrementally rather than in a terrifying big bang. - State is a blast radius. Splitting infrastructure into multiple smaller states (per environment, per service) limits how much a single bad
applycan destroy and lets teams work independently. The trade-off is wiring outputs between states, and the patterns for doing that well are covered in the deeper Terraform at scale guide.
Modules and Composition
Copy-pasting the same forty lines of VPC configuration into five projects is how IaC accumulates the exact technical debt it was supposed to eliminate. Modules are the unit of reuse: a directory of .tf files with defined inputs (variables) and outputs that you call from elsewhere.
|
|
A good module hides complexity behind a small, meaningful interface — the caller specifies what they want (a production-sized web cluster) and the module encodes how (the autoscaling group, launch template, security groups, and load balancer wiring). The public registries ship maintained modules for common patterns, and the terraform-aws-modules collection in particular is high quality. The discipline of designing module interfaces, versioning them, and avoiding the over-abstraction trap where a module has thirty inputs and is harder to use than raw resources, is its own subject — the Terraform at scale post goes deep on it, and the Terraform for the homelab walkthrough shows the same ideas on Proxmox rather than AWS.
The Broader Landscape
Terraform and OpenTofu are the center of gravity, but they are not the only model, and choosing the right tool for the layer you are working at matters.
| Tool | Model | Best at | Watch out for |
|---|---|---|---|
| Terraform / OpenTofu | Declarative HCL, external state | Multi-cloud provisioning, the default choice | State management, HCL’s limits as logic grows |
| Pulumi | Declarative in real languages (TS, Python, Go) | Teams who want loops, types, and tests in a real language | Smaller ecosystem; state service or self-managed |
| AWS CDK | Imperative code synthesizing CloudFormation | AWS-only shops wanting constructs and unit tests | CloudFormation’s drift handling and AWS lock-in |
| Crossplane | Kubernetes controllers reconciling cloud resources | Platform teams already living in Kubernetes | Cluster becomes a dependency for all provisioning |
| Ansible | Imperative/procedural, agentless SSH | Configuring inside servers, ordered steps | Not a provisioner; weak at desired-state for cloud objects |
The most useful distinction is provisioning versus configuration. Terraform, OpenTofu, Pulumi, CDK, and Crossplane provision — they create and destroy cloud objects (instances, networks, databases). Ansible configures — it installs packages, edits files, and starts services on machines that already exist. They are complementary, not competing: a common pattern is Terraform to stand up the instances and Ansible to configure what runs on them. If your goal is reproducible machines rather than reproducible cloud topology, the Nix approach in NixOS for reproducible infrastructure is a third path worth knowing. And for AWS-native teams, the AWS CDK deep dive and the Kubernetes-native Crossplane approach are both real alternatives to HCL rather than mere wrappers.
IaC in CI/CD and the Failure Modes Nobody Warns You About
The mature way to run IaC is to take it out of human hands at the keyboard and put it in a pipeline. The pattern: a pull request runs plan and posts the output as a comment so reviewers see exactly what will change; merging to the main branch runs apply. This is GitOps for infrastructure — the git history is the source of truth, and the pipeline is the only thing with the credentials to change production. It composes naturally with the rest of your CI/CD pipeline, and it is where IaC’s code-review benefit actually pays off.
PR opened ──► CI runs `plan` ──► plan posted to PR ──► human review
│
merge to main ▼
CI runs `apply -auto-approve`
│
▼
infrastructure changed,
state updated in backend
Adopt it with eyes open about the failure modes, because they are specific and recurring:
- The destroy that wasn’t supposed to happen. A change to a resource attribute that forces replacement (some attributes cannot be updated in place) shows up in the plan as “destroy and recreate.” For a stateless web server that is fine; for a database it is a catastrophe. Read every
-/+anddestroyline in a plan. Useprevent_destroylifecycle blocks on irreplaceable resources. - State corruption from concurrent applies. Two pipelines applying the same state at once without locking will corrupt it. This is exactly what the locking discussion above prevents — make sure it is actually enabled, not just configured.
- Drift you only discover during an incident. Someone made an emergency console change at 3am and never put it back into code. Months later a routine apply tries to revert their fix. Run
planon a schedule and alert on unexpected drift so you find it on a Tuesday afternoon, not mid-incident. - Provider and module version churn. An unpinned provider upgrades, changes a default, and your next plan wants to modify resources you never touched. Pin versions and upgrade deliberately.
- The bootstrap paradox. Your state backend (the S3 bucket) is itself infrastructure. Either create it by hand once, or manage it in a separate, carefully-guarded state. Do not put the bucket that holds your state into the state it holds.
None of these are reasons to avoid IaC. They are the reasons to treat it as production software with reviews, pinning, locking, and monitoring — not as a pile of scripts.
Verdict
Infrastructure as code is non-negotiable for anything beyond a single hand-managed server, and the declarative provisioning model — Terraform or OpenTofu at the center — is the correct default for the vast majority of teams. The 2026 reality adds one real decision the old tutorials never had to mention: Terraform is now source-available under the BUSL, and OpenTofu is its genuinely open-source, Linux-Foundation-governed fork that is close to a drop-in replacement and has begun shipping features like built-in state encryption ahead of its upstream. For new projects with no commitment to HashiCorp’s commercial platform, OpenTofu is the cleaner long-term choice; for everyone else the migration is low-risk but rarely urgent, and the concepts are identical either way.
The deeper lesson is the one the syntax tutorials bury: IaC’s hard problems are operational, and they all orbit the state file. Get remote state, locking, encryption, blast-radius splitting, and drift monitoring right, and IaC delivers everything it promises — reproducible, reviewable, auditable infrastructure. Skip them, and you have simply built a faster, more centralized way to break production. Start small, put plan in front of every apply, pin your versions, guard your state, and expand from there.
Sources
- OpenTofu — official site and documentation
- OpenTofu joins the Linux Foundation (announcement)
- HashiCorp adopts the Business Source License
- Terraform documentation — Backends and state
- Terraform 1.10 — S3 backend native state locking
- OpenTofu — state encryption
- Pulumi vs. Terraform (Pulumi docs)
- terraform-aws-modules registry
Comments