Title: Lecture 13 — Providers, Resources, and State: Thinking in Terraform
Course: AIINFRA 101: Applied Infrastructure & Containerization > Section: Week 13 — Infrastructure as Code with Terraform

📖 Lecture — Providers, Resources, and State: Thinking in Terraform

So far in this course — and in AIINFRA 100 before it — you've provisioned infrastructure by clicking around a cloud console, running one-off CLI commands, or writing imperative scripts: docker build, then kubectl apply, then helm install. That works for a single experiment, but it falls apart the moment you need to reproduce an environment, hand it off to a teammate, or tear it down and rebuild it exactly the same way six months later. Infrastructure as Code (IaC) solves this by describing your infrastructure in a text file that can be versioned, reviewed, and re-run. Terraform, built by HashiCorp, is the tool this course uses. An imperative script says "create this, then create that." Run it twice and you risk an error or a duplicate resource. A declarative Terraform configuration (HCL, in .tf files) instead describes the desired end state — "here is what should exist" — and Terraform's engine figures out the sequence of API calls needed to get there. Run terraform apply once, or ten times, and Terraform only makes the changes needed to close the gap between what you asked for and what currently exists. Three core concepts make that comparison possible:

- Providers are plugins that teach Terraform how to talk to a platform's API — AWS, GCP, Azure, Kubernetes, Helm, or a community provider like kind for local clusters. There are over 1,000 providers in the Terraform Registry, which is why Terraform has become a common language for infrastructure regardless of which platform (or combination of platforms) a team uses. You install providers with terraform init.
- Resources are single declared pieces of infrastructure — an aws_instance, a kind_cluster, a kubernetes_deployment. You write resources as blocks in your .tf files, each with a type, a local name, and arguments describing its desired configuration. Alongside resources, most real configurations also use input variables (declared in variable blocks, often set via a .tfvars file) to parameterize a config instead of hardcoding values, and outputs to surface useful values — like a resulting IP address — after apply finishes.
- State (terraform.tfstate, plain JSON) is Terraform's record of what it believes exists in the real world: resource IDs, attributes, dependency relationships. Every plan or apply reads this file, compares it against your configuration, and computes the minimal diff needed to reconcile the two — which is what makes Terraform efficient instead of recreating everything from scratch each run.

The core workflow has four commands you'll use every week from here on: terraform init (downloads the providers your config references and sets up the backend), terraform plan (previews exactly what will be created, changed, or destroyed, without touching anything — your safety check), terraform apply (executes the plan against the real platform API), and terraform destroy (tears everything back down, proving your infrastructure is fully code-defined and disposable). The single most important misconception to unlearn this week: Terraform is idempotent, but it is not auto-reconciling. Running apply twice with no config changes is a no-op — but Terraform has no way of knowing if someone manually resized an EC2 instance in the AWS console, or scaled a Kubernetes Deployment with kubectl. Those out-of-band edits create configuration drift — a gap between your .tf files and what's actually running. Terraform doesn't detect or fix drift in the background; it only notices on the next plan, and will typically propose reverting the manual change back to match your code, because as far as Terraform can tell, the manual edit is a deviation, not an intentional update. The rule that prevents this: once a resource is managed by Terraform, all changes to it should flow through Terraform. Two more practices keep this model trustworthy at team scale. First, local state does not scale past one person — if state lives only on your laptop, a teammate has no idea what you've already created, and concurrent apply runs can corrupt state with no locking to prevent it. The fix is a remote backend with locking, such as S3 paired with DynamoDB, Google Cloud Storage, or a Terraform Cloud workspace: it centralizes state, locks it during an apply so only one change happens at a time, and keeps a version history. Second, mature teams split state by environment and component (a network/ state, a staging/ state, a production/ state) instead of one giant terraform.tfstate — so a mistake made while experimenting in one piece can't accidentally reference, or destroy, another. Finally, two safety misconceptions worth stating directly. Secrets do not belong in .tf or .tfvars files — those are ordinary text files that typically live in Git, and Git history keeps every value you ever committed, even after deletion; retrieve secrets at runtime instead, via a data source querying a secrets manager. And the state file itself is not safe to commit, public or shared — it can contain full resource attributes, including sensitive values like passwords, in plain text; that's exactly why an encrypted, access-controlled remote backend matters beyond team coordination alone. Terraform's range spans from local infrastructure — a kind Kubernetes cluster running entirely on your laptop through the community tehcyx/kind provider, at zero cost and zero risk — to cloud infrastructure — real (free-tier) AWS resources through the official AWS provider. The HCL, the workflow, and the state-handling discipline are identical either way; only the provider and the blast radius of a mistake change.