Terraform & Infrastructure as Code#
Clicking through a cloud console is undocumented, unrepeatable, and unreviewable. Declare your infrastructure in files, and
git logbecomes your change history.
โฑ ~10 min read ยท ~20 min hands-on ๐ needs: Serverless Functions ยท Git & GitHub
Infrastructure as Code (IaC) means your cloud resources are declared in version-controlled files. You describe the desired end state; Terraform computes the diff and applies it. The wins are reproducibility, code review for infrastructure, and one command to tear a whole environment down.
Try it in 5 minutes โ declare, plan, apply#
# main.tf
terraform {
required_providers {
google = { source = "hashicorp/google", version = "~> 6.0" }
}
}
provider "google" {
project = var.project_id
region = "asia-south1"
}
variable "project_id" { type = string }
resource "google_storage_bucket" "scraped_data" {
name = "${var.project_id}-scraped-data"
location = "ASIA-SOUTH1"
uniform_bucket_level_access = true
force_destroy = false # guard against deleting real data
lifecycle_rule {
condition { age = 90 } # auto-delete objects after 90 days
action { type = "Delete" }
}
}
output "bucket_url" {
value = google_storage_bucket.scraped_data.url
}terraform init # download providers
terraform plan # show what WOULD change โ read this every time
terraform apply # make it so
terraform destroy # remove everything this config createdโ
plan before apply, always. It’s a dry run that has prevented more outages than any other habit in this course.
The core loop#
flowchart LR
W["Write .tf<br/>(desired state)"] --> P["terraform plan<br/>(diff vs reality)"]
P --> R["Review the diff<br/>โ in a PR"]
R --> A["terraform apply"]
A --> S["State file<br/>(what exists now)"]
S --> PTerraform keeps a state file mapping your config to real resources. That file is how it knows what already exists, and it is the part people get wrong.
State: the part that bites#
- Never commit state to git. It contains resource IDs and often secrets in plaintext.
- Use a remote backend so a team shares one state with locking. Two people running
applyagainst local state will corrupt each other’s work.
terraform {
backend "gcs" {
bucket = "my-tfstate-bucket"
prefix = "prod"
}
}- Never hand-edit resources Terraform manages. Console changes cause drift; the next
planwill try to undo them. If you must,terraform importthe resource so state matches reality.
Habits worth forming early#
| Habit | Why |
|---|---|
Pin provider versions (~> 6.0) | A provider upgrade shouldn’t change infrastructure |
| Separate environments (dir or workspace) | Never share state between dev and prod |
Use variables + terraform.tfvars | No hard-coded project IDs; .tfvars stays out of git |
Secrets from a secret manager, not .tf | .tf files are committed; secrets must not be |
plan in CI on every PR | Reviewers see the infrastructure diff before merge |
Terraform in CI is where IaC pays off: post plan output on the PR, and apply only on merge to main โ with an environment approval gate.
โ๏ธ
terraform destroyand any plan showing- destroyon a stateful resource (database, bucket) deletes real data. Read every plan; setforce_destroy = falseand deletion protection on anything that matters.
When it fails#
| Symptom | Cause | Fix |
|---|---|---|
| “Resource already exists” | Created by hand outside Terraform | terraform import it into state |
| Plan wants to destroy/recreate | Changed an immutable field (e.g. name) | Check the diff; rename intentionally |
| State lock stuck | A crashed run | terraform force-unlock <id> โ only when truly stale |
| Secret visible in state | Sensitive value stored by design | Remote backend + encryption + restricted access |
| Works for you, not a teammate | Local state | Move to a remote backend |
| Provider upgrade changed things | Unpinned version | Pin with ~> and a lockfile |
Your turn (โ20 min)#
- Create the bucket above with
planโapply; confirm it exists in the console. - Change the lifecycle age to 30 and run
planโ read the diff before applying. - Delete the bucket in the console, then run
planagain and watch Terraform detect the drift. - Move state to a remote backend and re-run
init. terraform destroyto clean up โ and note exactly what it warns you it will delete.
Checklist#
- I always run
planand read the diff beforeapply. - I never commit state files; I use a remote backend with locking.
- I pin provider versions.
- I don’t hand-edit Terraform-managed resources.
- I keep secrets out of
.tffiles. - I
destroycourse resources so they stop costing money.
Go deeper#
- Terraform documentation โ language, CLI, workflow.
- Remote state backends โ sharing state safely.
- OpenTofu โ the open-source fork, near-identical syntax.