Infrastructure as code is the practice of defining cloud resources in version-controlled files instead of clicking through a console. It sounds like extra work until the first time you need to recreate an environment from scratch, audit who changed a security group, or onboard a new engineer without a three-hour "here's how we set things up" session.
What changed in 2026
- OpenTofu stabilized as the open-source Terraform fork. Most teams evaluating new stacks treat OpenTofu and Terraform as interchangeable for core usage; the HCL ecosystem (providers, modules) is shared.
- Pulumi added AI-assisted code generation. Describing infra in TypeScript or Python and having a model scaffold the first draft became a practical workflow, not a gimmick.
- Cloud providers improved drift detection APIs. AWS Config, Azure Policy, and GCP Asset Inventory now integrate tightly with IaC pipelines, making automated drift alerts mainstream.
- Platform engineering teams standardized on internal developer platforms that expose IaC as self-service templates, reducing the raw HCL surface that product engineers touch.
Terraform vs Pulumi vs others
| Tool |
Language |
Strengths |
Watch out for |
| Terraform / OpenTofu |
HCL |
Huge provider ecosystem, stable, wide talent pool |
HCL loops and conditionals feel awkward |
| Pulumi |
TS, Python, Go, C# |
Real language features, testing, loops |
Smaller provider ecosystem, runtime dependency |
| AWS CDK |
TS, Python |
Deep AWS integration, L2/L3 constructs |
AWS-only, generates CloudFormation under the hood |
| Ansible |
YAML |
Great for configuration management, agentless |
Not ideal for resource lifecycle management |
| Crossplane |
Kubernetes CRDs |
GitOps-native, k8s-native orgs |
High complexity, steep learning curve |
For most teams starting in 2026: Terraform or OpenTofu is the lowest-risk default. The provider ecosystem (3,000+) and the talent pool are unmatched.
A minimal Terraform example
# main.tf
terraform {
required_version = ">= 1.9"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-tfstate"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "tf-state-lock" # locking — required
encrypt = true
}
}
resource "aws_s3_bucket" "assets" {
bucket = "my-app-assets-${var.env}"
tags = {
Environment = var.env
ManagedBy = "terraform"
}
}
The backend "s3" with a DynamoDB lock table is the minimum viable remote state setup. Never use local state for shared infra.
How to start
- Remote state first. Before writing a single resource, configure remote state with locking. Local
terraform.tfstate in a git repo is a trap.
- Import existing resources. Use
terraform import or the new terraform plan -generate-config-out to bring existing console-created resources under management.
- Use workspaces or directories for environments. Don't use a single workspace for prod and staging — separate state files prevent a mistyped variable from destroying production.
- Run plan in CI, apply on merge. Require a
terraform plan output in every PR. Apply only from the main branch in CD, never from a developer laptop.
- Enable drift detection. Schedule a
terraform plan daily against prod; page on any unexpected drift.
Common mistakes
No state locking. Two engineers running apply simultaneously corrupts state. DynamoDB locking for S3 backends, or Terraform Cloud's built-in locking, is mandatory.
Storing secrets in state. Terraform state is not encrypted by default and contains sensitive values. Use S3 server-side encryption, restrict access via IAM, and never commit state to git.
Giant monolithic root modules. One module for all 200 resources means every plan evaluates everything. Break infra into logical root modules (networking, compute, data) with separate state files.
No tagging strategy. Untagged resources are invisible to cost allocation and compliance scans. Enforce tags via policy or a default_tags block at the provider level.
Manual console changes after IaC adoption. Drift that goes undetected silently diverges from your source of truth until the next apply breaks something.
What to skip
- Using Terraform for Kubernetes workloads. Use Helm or Argo CD for k8s resources; Terraform manages the cluster, not the app deployments inside it.
- Massive custom provider modules when an official module exists. The Terraform Registry has battle-tested modules for EKS, RDS, VPCs — prefer them over rolling your own.
- Local state files checked into git. Merge conflicts in state files are painful and the decrypted state is a security risk.
FAQ
Should I use Terraform Cloud or self-managed backend?
Self-managed (S3 + DynamoDB) is free and works well for most teams. Terraform Cloud adds a UI, SSO, policy enforcement, and audit logs — worth it for teams of 10+ managing multiple workspaces.
How do I handle secrets in Terraform?
Mark outputs and variables as sensitive = true, use a secrets manager (AWS SSM, HashiCorp Vault) and reference ARNs rather than values, and restrict state file access tightly.
What is drift and how bad is it?
Drift is when real infrastructure differs from what IaC describes. It's bad because your next apply may revert someone's manual hotfix — or silently leave a security hole open.
Can I mix Terraform and Pulumi?
Yes, but it's painful. If you're starting fresh, pick one. If migrating, move module by module with a shared state data source to pass values across.
Where to go next
CI/CD pipeline basics in 2026, Observability vs monitoring in 2026, and Feature flags guide in 2026.