Terraform Fundamentals

Declarative infrastructure: desired state, reconciled

resource "aws_instance" "web" {
  ami           = "ami-0abcd1234"
  instance_type = "t3.micro"
  tags = {
    Name = "web-server"
  }
}

This block declares what should exist, not the steps to create it — the same declarative model as a Kubernetes Deployment's spec. Terraform's job is to compare this desired state against what it believes actually exists (tracked in the state file), compute the difference, and make the minimum set of API calls needed to close that gap. This is the same reconciliation idea already covered for Kubernetes — compare desired vs. actual, act on the difference — just run on-demand (terraform plan/apply) instead of continuously in a background loop.

The plan/apply cycle

graph TD hcl["HCL config<br/>(desired state)"] --> plan["terraform plan:<br/>diff desired vs. state file"] state["State file<br/>(believed actual state)"] --> plan plan --> diff["Human reviews the diff:<br/>+ create, ~ update, - destroy"] diff --> apply["terraform apply:<br/>make the API calls"] apply --> newstate["State file updated<br/>to reflect new reality"]

terraform plan is deliberately a read-only, side-effect-free step — it computes and displays the diff without changing anything, specifically so a human (or a CI pipeline) can review exactly what would happen before anything real is created, modified, or destroyed. terraform apply executes that plan, and — critically — updates the state file to match afterward, so the next plan compares against an accurate picture of what now exists.

Providers: the plugins that talk to real APIs

graph LR core["Terraform core<br/>(HCL parsing, graph, state)"] --> provider["Provider plugin<br/>(aws, google, kubernetes, ...)"] provider --> api["Cloud provider API"]

Terraform's core has no built-in knowledge of AWS, GCP, or Kubernetes — every resource type is implemented by a provider plugin, which translates a resource block into actual API calls (create an EC2 instance, create a GKE cluster, apply a Kubernetes manifest) and knows how to read back that resource's current attributes for future plans. This plugin architecture is why the exact same core workflow (plan/apply, state, modules) works identically whether the resources being managed are AWS infrastructure, Kubernetes objects, or a SaaS product's API-managed settings — the provider is what's different, the core mechanics aren't.

Common pitfall

Manually changing a Terraform-managed resource through a cloud console — resizing an instance, adding a tag, adjusting a security-group rule directly in the AWS console — creates drift: the real infrastructure no longer matches what the state file believes. The next plan either shows a confusing diff (Terraform wants to "fix" the console change back to the declared config, undoing a change someone made for a real reason) or, worse, apply silently reverts it without anyone realizing that was about to happen. Any change to Terraform-managed infrastructure should go through Terraform — editing the HCL and running plan/apply — specifically so the state file and reality never diverge in the first place. See State Management for what to do once drift has already happened.