Terraform describes infrastructure declaratively: you write what you want to exist, not the steps to create it, and Terraform figures out the difference between that and what's actually there. This is the shape of almost everything you'll do with it.
terraform init — downloads the providers a
configuration needs and sets up its backend. Run it once per directory,
and again whenever you add a provider or change backend config.terraform plan — computes the difference between your
configuration and real infrastructure, and prints what would change.
Read-only, changes nothing.terraform apply — runs a plan, shows it to you, and
(after confirmation) actually makes the changes.terraform destroy — the inverse of apply: tears down
everything Terraform knows about in that configuration.plan before apply isn't optional caution,
it's the whole safety model. Reading the diff is how you catch "this
would replace the resource, not update it" before it happens instead of
after.
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.4"
}
}
}
resource "local_file" "hello" {
filename = "${path.module}/hello.txt"
content = "Hello from Terraform"
}
terraform init, then terraform apply, and a
real file appears on disk. Delete the resource block and apply again,
and the file goes away. That's the entire model — the config is the
source of truth, and Terraform reconciles reality to match it.
Hardcoding values works for a five-line example and nothing else. Real configurations use variables for anything that changes between environments, and outputs for anything another piece of config (or a human) needs to read back out.
variable "filename" {
type = string
description = "Where to write the file"
default = "hello.txt"
}
resource "local_file" "hello" {
filename = var.filename
content = "Hello from Terraform"
}
output "path" {
value = local_file.hello.filename
}
Pass a variable at the command line with -var
"filename=other.txt", from a file with -var-file, or
via an environment variable named TF_VAR_filename. Real
projects almost always use a .tfvars file per environment
rather than command-line flags, so the actual values used are
version-controlled and reproducible.
Every time you apply, Terraform writes a state file (by default
terraform.tfstate) recording what it created and the real
IDs/attributes of each resource. This is how plan knows
what already exists without querying every single resource's live API
every time, and it's the only way Terraform can tell "this resource is
managed by me" from "this looks similar but I've never touched it."
Two rules that matter more than anything else about state:
Once the basic loop is comfortable, the next two things worth learning are providers (how Terraform actually talks to AWS, Cloudflare, Kubernetes, or anything else) and the habits that keep a Terraform codebase maintainable once it's not five lines anymore.