Why Terraform
What goes wrong when infrastructure lives in people's heads.
Part 1: The problem
QuickCart’s first network, load balancer and database were built by Maya, by hand, in the AWS console. It took her most of a Tuesday: about a hundred form fields, in the right order.
It worked. Six months later, QuickCart needed a second copy in another region, closer to customers in Europe. Maya had left for a two-week holiday. Leo opened her notes: a short document and a few screenshots.
He clicked through it all again. It took him two days. The new servers couldn’t download updates, and it took another day to find why: one route table was missing a single line. Maya’s notes didn’t mention it, because she’d fixed it on the first day and forgotten.
That’s what building by hand costs. Not the first time, which goes fine. Every time after.
This lesson is about writing infrastructure down as code, and why that fixes both Leo’s two lost days and the typo nobody could review.
Part 2: The idea
Building by clicking in the console has a nickname: ClickOps. It’s the right way to learn, and to explore. The trouble starts when a whole environment exists only because someone clicked it into being.
What goes wrong with clicking
| Problem | What it looks like at QuickCart |
|---|---|
| Unrepeatable | Leo can’t build Maya’s network again exactly. He can only try |
| Undocumented | The real setup is whatever is in AWS right now. Nobody wrote down why each setting is there |
| Unreviewed | One person clicks, and the change is live. Nobody else looks first |
| Drift | Someone changes a setting in a hurry. Now dev and production differ, and nobody knows how |
| Slow to copy | A second region, or a staging copy, means all the clicking again, from memory |
The idea: write it down as code
Infrastructure as code means you describe your infrastructure in text files. A tool reads the files and builds what they describe.
Terraform is the most widely used tool for this. You write files ending in .tf, like this:
resource "aws_ecr_repository" "orders" {
name = "quickcart-orders"
}| Part | What it means |
|---|---|
resource | something Terraform creates and looks after |
"aws_ecr_repository" | the type: an ECR repository, a private store for container images |
"orders" | the name your code uses for it |
name | the name AWS gives it. Leave it out and Terraform stops with an error, because it’s required |
That says: there should be an ECR repository called quickcart-orders. You’ll build exactly this in Your first resource.
Because the files are text, they go into git, like app code. So you get what app developers already rely on:
| With code | What it gives QuickCart |
|---|---|
| Git history | Who changed what, when, and why |
| Pull requests | Someone reviews a change before it runs |
| Run it again | An identical copy, as many times as you like |
| Change one value | The same environment in another region |
Declarative, not step by step
There are two ways to tell a computer what to do.
| Imperative: a list of steps | Declarative: the end result | |
|---|---|---|
| You write | “Create a VPC. Then create a subnet in it.” | “There is a VPC. It has this subnet.” |
| Run it twice | Tries to create everything twice, and fails or duplicates | Sees it already exists, and does nothing |
| Something changed by hand | The script doesn’t know | Spotted, and put back to match |
| Examples | A shell script, clicking through the console | Terraform |
Terraform is declarative. You describe what should exist. Terraform works out the steps.
How Terraform knows what to do
Every run compares three things:
| Thing | What it is |
|---|---|
| Your code | What you want to exist |
| State | Terraform’s record of what it built last time. Terraform state covers it in depth |
| AWS | What really exists right now |
From the difference, it makes a plan: a list of what it would create, change or destroy.
The loop you’ll use every day
| Step | Command | What happens |
|---|---|---|
| 1 | terraform init | Set up the folder and download the plugins it needs. Once per folder |
| 2 | Edit the .tf files | Describe what you want |
| 3 | terraform plan | See exactly what would change. Nothing changes yet |
| 4 | terraform apply | Shows the plan again, asks you to type yes, then makes the changes |
| 5 | terraform destroy | Remove everything this code built. For practice environments |
Then back to step 2. Because apply compares first, running it again with no changes does nothing. That’s called idempotent, and it’s why it’s safe to run as often as you like.
What Terraform doesn’t do
It doesn’t make clicking wrong. You’ll still use the console to look at things, to learn, and for the one bucket that holds Terraform’s own state. What changes is where the truth lives: in the code, not in someone’s memory.
Part 3: See it
Build QuickCart’s environment both ways. Then build a second copy in another region, and compare.
By clicking in the console
one form at a time- Time
- 0 hours
- Written down
- Nothing yet
By code, with Terraform
files in git, then terraform apply- Time
- 0 hours
- Written down
- Nothing yet
What to notice: the first build costs about the same either way. The difference only shows on the second copy: clicking is just as slow again and quietly misses a step, while code builds an identical copy by changing one value.
Part 4: Do it
Free — everything runs on your laptop. Nothing is created in AWS.
You’ll install Terraform and run the whole loop — init, plan, apply, apply again, change, destroy — with a built-in resource that lives only in Terraform’s memory. No AWS account needed. About 15 minutes.
Install Terraform
With Homebrew, on macOS, Linux or Windows (inside WSL), Terraform comes from HashiCorp’s own collection. Set up your computer installs Homebrew. First add the collection:
Terminalbrew tap hashicorp/tapPart What it means brew tapadd another collection of software that Homebrew can install from hashicorp/tapHashiCorp’s own collection Then install Terraform from it:
Terminalbrew install hashicorp/tap/terraformPart What it means brew installinstall a program hashicorp/tap/terraformTerraform, from HashiCorp’s collection Check it worked:
Terminalterraform versionPart What it means terraform versionprint the version of Terraform installed You should seeTerraform v1.16.4 on linux_amd64Yours may be newer. Anything from 1.10 upwards works for this course. The second line names your system:
darwin_arm64on a Mac with an Apple chip,linux_amd64on most Linux and Windows computers.On Windows or Linux, follow the install page on HashiCorp’s developer site, then run
terraform versionthe same way.Make a folder and describe something
Make a folder for this lesson:
Terminalmkdir quickcart-helloPart What it means mkdirmake a directory quickcart-helloits name Terminalcd quickcart-helloPart What it means cdchange directory. Every later command runs in here quickcart-hellothe folder you just made Create
main.tfin that folder, in any text editor:main.tfterraform { required_version = ">= 1.10.0" } resource "terraform_data" "environment" { input = { app = "quickcart" region = "us-east-1" zones = 2 } } output "environment" { value = terraform_data.environment.output }terraform_datais a resource built into Terraform. It creates nothing outside Terraform: it just stores the values you give it. That makes it perfect for practising the loop without an AWS account.Setting What it does If you leave it out When you’d change it terraform→required_versionRefuse to run on a Terraform older than 1.10 Any version runs it, and an old one may fail later with a confusing error When your code starts relying on a newer feature resource "terraform_data" "environment"Declare one resource. terraform_datais the type;environmentis the name your code uses for it— — inputThe values to store. Here, a small description of an environment The resource stores nothing, and has nothing to show Change it to see how a plan shows an update output "environment"Print a value after apply Nothing is printed after apply Add one for anything you’ll want to see or use elsewhere valueWhat the output prints: the stored values, read back from the resource Required — an output without a value is an error Point it at whatever you want to see Initialise the folder
Terminalterraform initPart What it means terraform initset up this folder: find and download any plugins the code needs You should seeInitializing the backend... Initializing provider plugins... - terraform.io/builtin/terraform is built in to Terraform Terraform has been successfully initialized!terraform_datais built in, so there’s nothing to download. With AWS, this is where the AWS plugin would be downloaded.Read the plan
Terminalterraform planPart What it means terraform plancompare the code with what exists, and show what would change. Changes nothing You should seeTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # terraform_data.environment will be created + resource "terraform_data" "environment" { + id = (known after apply) + input = { + app = "quickcart" + region = "us-east-1" + zones = 2 } + output = (known after apply) } Plan: 1 to add, 0 to change, 0 to destroy. Changes to Outputs: + environment = (known after apply)Read it top to bottom:
Line What it tells you + createthe key: +means something will be created# terraform_data.environment will be createdwhich resource, and what will happen to it (known after apply)a value that doesn’t exist until it’s created, like an ID Plan: 1 to add, 0 to change, 0 to destroy.the summary. Always read this line It also warns you that the plan wasn’t saved. That’s fine here: you’ll use saved plans later, in pipelines.
Apply it, then apply again
Terminalterraform applyPart What it means terraform applymake the plan again, show it, and carry it out once you type yesIt shows the same plan, then asks. Type
yes:You should seeDo you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes terraform_data.environment: Creating... terraform_data.environment: Creation complete after 0s [id=2af1f034-bd2c-4555-3989-3189330c5de7] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: environment = { "app" = "quickcart" "region" = "us-east-1" "zones" = 2 }Your ID will be different. A new file has appeared in the folder:
terraform.tfstate. That’s Terraform’s record of what it built.Now run exactly the same command again:
Terminalterraform applyPart What it means terraform applythe same command, with no change to the code You should seeterraform_data.environment: Refreshing state... [id=2af1f034-bd2c-4555-3989-3189330c5de7] No changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed. Apply complete! Resources: 0 added, 0 changed, 0 destroyed.Nothing was built twice. A script would have tried to create it again. Terraform compared, found everything matching, and did nothing. This is what makes it safe to run as often as you like.
Change the code, and read the plan
QuickCart decides to run in three zones. In
main.tf, changezones = 2tozones = 3. The whole file now reads:main.tfterraform { required_version = ">= 1.10.0" } resource "terraform_data" "environment" { input = { app = "quickcart" region = "us-east-1" zones = 3 } } output "environment" { value = terraform_data.environment.output }Terminalterraform planPart What it means terraform plancompare the changed code with what was built You should seeTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # terraform_data.environment will be updated in-place ~ resource "terraform_data" "environment" { id = "2af1f034-bd2c-4555-3989-3189330c5de7" ~ input = { ~ zones = 2 -> 3 # (2 unchanged attributes hidden) } ~ output = { - app = "quickcart" - region = "us-east-1" - zones = 2 } -> (known after apply) } Plan: 0 to add, 1 to change, 0 to destroy.~means change in place. The linezones = 2 -> 3shows the old value, an arrow, and the new one. Only what you changed is planned: the two unchanged values are hidden.You don’t need to apply this. The point was to see how Terraform describes a change.
Clean-up step: Clean up
Terminalterraform destroyPart What it means terraform destroyplan the removal of everything this folder built, and carry it out once you type yesIt shows a plan full of
-lines and asks. Typeyes:You should seePlan: 0 to add, 0 to change, 1 to destroy. Do you really want to destroy all resources? Terraform will destroy all your managed infrastructure, as shown above. There is no undo. Only 'yes' will be accepted to confirm. Enter a value: yes terraform_data.environment: Destroying... [id=2af1f034-bd2c-4555-3989-3189330c5de7] terraform_data.environment: Destruction complete after 0s Destroy complete! Resources: 1 destroyed.Nothing existed outside your laptop, so nothing else needs cleaning up. You can delete the
quickcart-hellofolder, or keep it to experiment with.
Part 5: Check yourself
QuickCart's network was built by hand and works. Why is that still a problem?
What does "declarative" mean for Terraform code?
You run terraform apply twice in a row with no changes to the code. What does the second run do?
Why does terraform plan exist as a separate step from apply?
Someone fixes an outage by changing a setting in the console, and doesn't update the code. What happens at the next apply?
Part 6: Cheat sheet
| Idea or command | What it means |
|---|---|
| ClickOps | Building by clicking in the console. Fine for learning; unrepeatable for real environments |
| Infrastructure as code | Infrastructure described in text files, kept in git, built by a tool |
| Declarative | Describe the end result; the tool works out the steps |
| Idempotent | Running it again with no changes does nothing |
terraform init | Set up a folder and download plugins. Once per folder |
terraform plan | Show what would change. Changes nothing |
terraform apply | Show the plan, ask for yes, make the changes |
terraform destroy | Remove everything this code built |
+ ~ - | Create, change in place, destroy |
| The rule | Every fix goes in the code. A console-only fix gets undone |