Terraform: your first resource
Providers, resources, plan and apply, the state file. How Terraform brings the cloud in line with the description.
Providers, resources, plan and apply, the state file. How Terraform brings infrastructure in line with the description.
Why
Terraform takes a text description of infrastructure and creates it in the cloud. You write what should exist, Terraform compares that to what's already there, and applies only the difference. It's the same declarative approach as in the previous lesson, now in practice.
The description is written in the HCL language in .tf files. Terraform reads all .tf files in
one folder as a single configuration, so you can split the code across files however is convenient.
Alongside Terraform there's an open fork, OpenTofu, with the same language and commands. It appeared because of a license change. On August 10, 2023, HashiCorp moved Terraform from the open MPL to the Business Source License (BSL), a license with restrictions on commercial use. Part of the community and the companies building products around Terraform published the OpenTF manifesto. HashiCorp didn't respond, and on August 25, 2023 they forked it. In September that year the project was renamed OpenTofu and handed to the Linux Foundation, and on January 10, 2024 the first stable release, 1.6.0, came out.
The basics match between them: .tf files, init/plan/apply, providers, state. New features
since 2024 differ between the two projects. Everything in the rest of this lesson and in the labs
works in both, except that OpenTofu's binary is called tofu.
Provider and resource
Two main entities: the provider and the resource.
A provider is a plugin for a specific platform (Yandex Cloud, AWS, Hetzner). It knows how to
talk to that platform's API. There are providers for local things too: hashicorp/local creates
files on disk, and this lesson's labs work with it.
A resource is a specific object: a VM, a network, a bucket, a file.
A fragment for Yandex Cloud (a real VM also requires boot_disk and network_interface blocks,
omitted here):
terraform {
required_providers {
yandex = {
source = "yandex-cloud/yandex"
version = ">= 0.100"
}
}
}
provider "yandex" {
zone = "ru-central1-a"
}
resource "yandex_compute_instance" "web" {
name = "web-01"
resources {
cores = 2
memory = 4
}
# boot_disk { ... } and network_interface { ... } are required
}
source tells Terraform where to download the provider from, version constrains its version.
Without version, Terraform takes the latest one, and the same code can behave differently six
months later.
The resource "type" "name" block describes one object. type (yandex_compute_instance) sets
the provider. name (web) is a local reference within the code: other resources reach this
object at the address yandex_compute_instance.web, and the object is recorded in state under the
same address. The object's actual name in the cloud is set by the name argument, which is a
different thing.
Workflow: init, plan, apply
terraform init # download providers, prepare the working directory
terraform plan # show what Terraform is about to do
terraform apply # show the plan again and apply it after yes
init creates two things in the folder. .terraform/ holds the downloaded providers and isn't
committed to git. .terraform.lock.hcl pins the exact provider versions chosen. This file is
committed, so the whole team and CI use the same provider.
terraform plan is the main command for safety. It changes nothing in the infrastructure, only
shows the difference between the code and reality. Here's what a plan looks like for a single file
through the hashicorp/local provider:
Terraform will perform the following actions:
# local_file.index will be created
+ resource "local_file" "index" {
+ content = <<-EOT
<h1>Hello from Terraform</h1>
EOT
...
+ filename = "./site/index.html"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Marks in front of a resource:
+create;~change in place;-delete;-/+delete and recreate.
The last one is the trickiest. Some arguments can't be changed on a live object, and then Terraform
recreates it entirely. Next to such an argument plan writes # forces replacement:
# local_file.index must be replaced
-/+ resource "local_file" "index" {
~ content = <<-EOT # forces replacement
- <h1>Hello from Terraform</h1>
+ <h1>Hello, IaC</h1>
EOT
...
Plan: 1 to add, 0 to change, 1 to destroy.
For a file that's harmless. For a VM with data on disk it's data loss. That's why plan is always
read before apply, and the first thing to check is destroy in the last line.
terraform destroy deletes everything recorded in this working directory's state. Good for
temporary environments.
State: Terraform's memory
To know what it has already created, Terraform keeps state, by default a terraform.tfstate
file in the working directory. For every address from the code (local_file.index) it records the
real object and its attributes. State is how Terraform knows what to change and what to leave
alone. You can see which addresses are in it with terraform state list.
State is critical and dangerous at the same time:
- It holds secrets in plain text (passwords, keys), even if a variable is marked
sensitive. That's why it isn't committed to git. - When several people work on the infrastructure, local copies of state drift apart. It's moved into shared storage (an S3-compatible bucket, HCP Terraform) with locking, so two people can't apply at once. More on this in the lesson on remote state.
Losing state means losing the link between the code and the real resources. The objects in the cloud stay where they are, but Terraform no longer knows about them and will try to create them again. That's why state is kept safe and backed up.
Common mistakes
- Apply without reading plan.
-auto-approvein a human's hands, as opposed to CI's, sooner or later deletes something important. Look at thePlan:line and at resources marked-and-/+. - Renamed a resource in the code. The address in state stayed the old one, and plan will show
that the old object needs deleting and a new one creating. The object itself never actually
changed. Terraform can move an object to a new address without recreating it; look up
movedandterraform state mvin the docs. - Committed
terraform.tfstateor.terraform/. The first carries secrets, the second weighs tens of megabytes of binaries. Both belong in.gitignore, while.terraform.lock.hclgoes into git. - Didn't pin the provider version. A new provider major version changes how resources behave.
Write
versioninrequired_providers. - Manual edits that bypass the code. The next plan will show them as drift, and apply will bring the object back to what's written in the code.
What you'll need for the tasks
The labs work without a cloud and without Docker: the resources in them are files created by the
hashicorp/local provider (the local_file resource, the filename and content arguments). You
need Terraform 1.4 or newer, or OpenTofu.
You'll find useful:
terraform -chdir=infra init,plan,apply: the same command, but for the code in theinfra/folder, withoutcd;terraform -chdir=infra state list: which addresses are recorded in state;terraform -chdir=infra plan -detailed-exitcode: exit code 0 if there are no changes, and 2 if there are;- resource documentation: local_file.
If init can't download the provider (a download error or a 403 from Russia), set up the Yandex
Cloud provider mirror following their instructions:
Yandex Cloud: Terraform provider mirror.
In upcoming lessons we'll cover GitOps, where code like this gets applied automatically from git.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson iac-basics-02-terraform-basics
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress