By the end you'll know
- What problem Terraform solves, and what "infrastructure as code" means
- What it means for Terraform to be declarative
- How to install Terraform and check your version
- How to write your first configuration
- The core loop:
init,plan,applyanddestroy
The problem: infrastructure built by clicking
Most people's first cloud server is made by clicking through a website: pick a size, pick a region, click Create. It works. Then you need a second one exactly like it, and you can't remember every setting you chose. A colleague changes something, and nobody knows what or when. Six months later, someone asks "why is this set up this way?", and the only answer is "someone clicked it".
Infrastructure as code fixes that. Instead of clicking, you write down what you want in a text file: this server, this size, in this region, with this DNS record pointing at it. A tool reads the file and makes it real. The file can be saved in Git, reviewed before it changes, and used to build the same thing again, as many times as you like.
Terraform is the most widely used tool for this. It talks to almost every service you've heard of, from AWS and DigitalOcean to Cloudflare and GitHub, through plugins called providers, and it uses the same few commands for all of them.
Declarative: say what, not how
A thermostat doesn't ask you to switch the heating on and off; you set it to 21 degrees, and it works out what to do to get there, and what to do when the room drifts. Terraform works the same way. You don't write the steps ("create a server, then wait, then add a DNS record"); you describe the end result ("there should be one server, and one DNS record pointing at it"), and Terraform works out the steps.
That's called being declarative. It means you can run Terraform again and again: if everything already matches your file, it does nothing; if something's missing or different, it fixes just that part.
Terraform and OpenTofu
You'll come across the name OpenTofu. In 2023, HashiCorp, the company behind Terraform, changed Terraform's licence so it's no longer open source. A group of companies copied the last open-source version and carried it on as OpenTofu, which is run by the Linux Foundation. The two work almost identically, and nearly everything in this track applies to both. This track uses Terraform, because it's what most job adverts and existing projects use.
Install Terraform
On a Mac, with Homebrew:
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
brew tap adds HashiCorp's own list of packages to Homebrew, and the second line installs Terraform from it. On Windows and Linux, follow the instructions for your system on Terraform's install page (opens in a new tab). Then check it worked:
terraform version
It prints Terraform v1. followed by the rest of the version number, and your computer's platform.
Your first configuration
This lesson won't touch the cloud at all, so nothing can cost you money. The first thing you'll create is a file on your own computer. It's small, but it's real infrastructure as far as Terraform is concerned, and every command works exactly as it will with a server.
Make a folder for the track, and go into it:
mkdir terraform-practice
cd terraform-practice
In your editor, create a file called main.tf (Terraform files end in .tf) with this in it:
resource "local_file" "hello" {
filename = "hello.txt"
content = "Hello from Terraform!"
}
That's written in HCL (HashiCorp Configuration Language), which every Terraform file uses. Here's what each part means:
resourcesays "here's something I want to exist". A resource is one thing Terraform manages: a file, a server, a DNS record."local_file"is the resource type. The first word before the underscore is the provider,local, which handles things on your own computer; the rest says which kind of thing, a file."hello"is Terraform's own name for this one resource, so you can refer to it later. It doesn't appear anywhere outside Terraform.- Inside the curly brackets are the arguments: the file's name, and what it should contain. Lining up the
=signs is just Terraform's style.
The core loop
Every Terraform project, from this one file to a whole production system, uses the same four commands.
1. init: get ready. Run this once in a new folder, and again whenever you add a provider:
terraform init
Terraform reads main.tf, sees it needs the local provider, and downloads it. The output ends with:
Terraform has been successfully initialized!
It also created two things in your folder: a hidden .terraform folder, where the provider was downloaded, and a file called .terraform.lock.hcl, which records exactly which provider version you got. Lesson 9 covers the lock file.
2. plan: see what would change. This is the step that makes Terraform safe:
terraform plan
Your screen shows a few dozen lines. These are the ones that matter:
# local_file.hello will be created
+ resource "local_file" "hello" {
+ content = "Hello from Terraform!"
+ filename = "hello.txt"
}
Plan: 1 to add, 0 to change, 0 to destroy.
Here's how to read them:
- The
#line is Terraform telling you, in a sentence, what it will do and to which resource:local_file.hellowill be created. - The
+at the start of a line means "create". Lesson 2 adds the other symbols, for change and destroy. - The lines inside the curly brackets are the settings it will use: your two, plus several more that Terraform works out itself. Many of those say
(known after apply), which means Terraform can't know them until the file exists. - The
Plan:line is the summary. Read it first: here, one thing to add, nothing changed, nothing destroyed.
Nothing has happened yet; a plan only looks.
3. apply: make it so. Now do it:
terraform apply
Terraform shows the same plan again, then asks:
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
Type yes and press Enter. Only the full word works; that's deliberate, so a stray key can't change real infrastructure.
local_file.hello: Creating...
local_file.hello: Creation complete after 0s [id=cadbafd9572a72dc7769c97730552f5955e135fd]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Look in your folder: there's a new file, hello.txt. Show what's in it:
cat hello.txt
cat prints a file to the terminal, and you'll see Hello from Terraform!. Terraform also wrote a file called terraform.tfstate: its record of what it has created. Lesson 3 is all about that file; for now, leave it alone.
Now run terraform apply a second time. This time Terraform says:
No changes. Your infrastructure matches the configuration.
That's the thermostat at work: the room is already at 21 degrees, so there's nothing to do.
4. destroy: clean up. When you're finished with something, Terraform can remove everything it made:
terraform destroy
It shows a plan with - for "will be destroyed", and asks for yes again. Type it:
local_file.hello: Destroying... [id=cadbafd9572a72dc7769c97730552f5955e135fd]
local_file.hello: Destruction complete after 0s
Destroy complete! Resources: 1 destroyed.
hello.txt is gone. With a file it hardly matters, but in the cloud, destroy is what stops the bill. Run terraform apply once more and type yes, so hello.txt is back for the break-it section.
In my real setup
CivicGrid's production system is built this way. One Terraform folder, about 200 lines in total, describes a Kubernetes cluster on DigitalOcean, the DNS record on Cloudflare that points the API's address at it, and the tool that deploys the app once the cluster is up. Running terraform apply builds all of it. If the cluster were lost tomorrow, the same command would build it again, the same way, which is exactly what clicking can't promise.
Break it on purpose
Try each one, then put it back.
A typo in the resource type. In main.tf, change local_file to local_fil, and run terraform plan:
Error: Invalid resource type
on main.tf line 1, in resource "local_fil" "hello":
1: resource "local_fil" "hello" {
The provider hashicorp/local does not support resource type "local_fil".
Did you mean "local_file"?
Terraform checks every resource type against what its provider supports, before doing anything, and even suggests the fix. Read it the same way as any error: what went wrong, then which file and line. Put the e back.
Delete the file by hand. Delete hello.txt from your folder, without using Terraform, then run terraform plan:
# local_file.hello will be created
+ resource "local_file" "hello" {
ending with Plan: 1 to add, 0 to change, 0 to destroy. (Above that, Terraform may also print a note that local_file.hello has been deleted outside of Terraform.) Terraform noticed the real thing no longer matches what you described, and plans to put it back. That gap between what's described and what's real is called drift, and spotting it is one of the most useful things Terraform does. Lesson 3 explains how it knows. Run terraform apply and type yes to put the file back.
Skip init. Make a new folder next to terraform-practice, copy main.tf into it, go into it, and run terraform plan without running init first:
cd ..
mkdir skip-init
cp terraform-practice/main.tf skip-init/
cd skip-init
terraform plan
Error: Inconsistent dependency lock file
followed by a few lines explaining that the local provider is needed but hasn't been chosen, ending with the fix: run terraform init. A new folder has no providers downloaded yet. Run terraform init here and the plan works. Then tidy up: cd .., and delete the skip-init folder.
Where you are now
You've used the whole core loop: describe something in a .tf file, init to get the provider, plan to check, apply to make it real, and destroy to clean up. So far, every plan has said "1 to add". Lesson 2 changes the file after it exists, and teaches you to read the plans that matter most: the ones that change or replace things.
Check yourself
What does it mean that Terraform is declarative?
What does terraform init do?
Why run terraform plan before apply?
In resource "local_file" "hello", what is "hello"?
You delete hello.txt by hand, then run terraform plan. What does it show?
What does terraform destroy remove?
Key terms
- Infrastructure as code: describing servers, networks and other infrastructure in text files, so they can be reviewed, versioned and rebuilt.
- Terraform: the most widely used infrastructure-as-code tool. OpenTofu is an open-source copy that works almost the same way.
- Declarative: describing the end result, and letting the tool work out the steps.
- HCL (HashiCorp Configuration Language): the language
.tffiles are written in. - Provider: a plugin that lets Terraform manage one kind of service, like
local,awsordigitalocean. - Resource: one thing Terraform manages, like a file or a server.
- init, plan, apply, destroy: get ready, preview, make it real, and remove it.
- Drift: when the real infrastructure no longer matches what the configuration describes.
Go deeper
- HashiCorp's own Get started tutorials (opens in a new tab) walk through the same loop with Docker and the main clouds.
- The local_file resource (opens in a new tab) documentation, on the Terraform Registry, where every provider's documentation lives.
- OpenTofu (opens in a new tab), if you want to read about the open-source alternative.