Who this is for
You build and operate infrastructure as a Platform Engineer or Backend Engineer. You want reproducible environments, fast provisioning, and safer changes.
Prerequisites
- Basic CLI skills (shell or PowerShell)
- Installed: Terraform 1.4+ on your machine
- Optional: Cloud account credentials if you want to try provider-specific resources (not required for local examples)
Learning path
- Core workflow: init → validate → plan → apply → destroy
- Providers, resources, data sources
- Variables, outputs, tfvars
- State and backends (what they are and why they matter)
- Modules and composition
Why this matters
- Provision environments consistently for dev/stage/prod.
- Review changes before they happen (plans in PRs).
- Detect and fix drift from manual changes.
- Spin up ephemeral environments for feature branches.
- Enable self-service infrastructure for product teams.
Concept explained simply
Terraform reads configuration files (.tf), compares the desired state to the current state, and produces a plan. When you apply, Terraform makes API calls to providers to reach the desired state. The state file records what was created so Terraform can track and update resources safely.
Mental model
- Configuration = your recipe.
- Providers = the kitchens you can cook in (AWS, Azure, GCP, Kubernetes, local).
- Resources = the dishes to create (buckets, VMs, files).
- State = the ledger of what exists and how it maps to your config.
- Plan = the diff between recipe and ledger.
- Apply = executing the diff.
Key terms you will use
- provider: plugin that talks to an API (e.g., aws, azurerm, google, kubernetes, local, random).
- resource: something Terraform manages (e.g., aws_s3_bucket, local_file).
- data source: reads data from an API without creating it.
- variable: inputs you can pass to configs.
- output: values printed after apply, often consumed by other tools.
- module: a folder of .tf files you can reuse and call.
- state: mapping between your config and real-world objects.
- workspace: named state instances (e.g., default, dev, prod).
Core workflow
- terraform init — download providers and set up the working directory.
- terraform validate — static checks for config correctness.
- terraform plan — preview changes; no side effects.
- terraform apply — execute the plan to reach desired state.
- terraform destroy — remove all managed resources in the workspace.
Safety tips
- Do not commit secrets or terraform.tfstate to public repos.
- Pin provider versions to avoid unexpected upgrades.
- Use variables and tfvars for environment-specific values.
- Avoid provisioners unless absolutely necessary.
Worked examples
Example 1: Write a local file (safe to run)
Creates a text file on your machine using the local provider.
# files: main.tf
terraform {
required_version = ">= 1.4.0"
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.4"
}
}
}
provider "local" {}
variable "message" {
type = string
description = "Content of the file"
default = "Hello, Terraform!"
}
resource "local_file" "note" {
content = var.message
filename = "${path.module}/hello.txt"
}
output "file_path" {
value = local_file.note.filename
}
- Run:
terraform init→terraform plan→terraform apply - Result: hello.txt is created; output shows the file path.
Example 2: Add a random suffix and variables
Demonstrates dependencies and idempotency.
# files: main.tf
terraform {
required_providers {
local = { source = "hashicorp/local", version = "~> 2.4" }
random = { source = "hashicorp/random", version = "~> 3.5" }
}
}
provider "local" {}
provider "random" {}
variable "message" { type = string, default = "Hello" }
resource "random_id" "suffix" { byte_length = 2 }
resource "local_file" "note" {
content = "${var.message} - ${random_id.suffix.hex}"
filename = "${path.module}/hello-${random_id.suffix.hex}.txt"
}
output "file_path" { value = local_file.note.filename }
- First apply creates the file with a random suffix.
- Re-running apply without changes should show No changes.
- Forcing regeneration:
terraform apply -replace="random_id.suffix"will create a new file.
Example 3: Create and reuse a simple module
Creates two files via a reusable module.
# files: modules/two_files/main.tf
variable "name" { type = string }
resource "local_file" "a" {
filename = "${path.module}/${var.name}-a.txt"
content = "module ${var.name} - A"
}
resource "local_file" "b" {
filename = "${path.module}/${var.name}-b.txt"
content = "module ${var.name} - B"
}
output "files" {
value = [local_file.a.filename, local_file.b.filename]
}
# files: main.tf (root)
terraform {
required_providers {
local = { source = "hashicorp/local", version = "~> 2.4" }
}
}
provider "local" {}
module "alpha" { source = "./modules/two_files" name = "alpha" }
module "beta" { source = "./modules/two_files" name = "beta" }
output "all_files" {
value = concat(module.alpha.files, module.beta.files)
}
- Run:
terraform initthenterraform apply. - Result: four files and an output list of their paths.
Optional cloud example: AWS S3 bucket (do not apply without credentials)
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = var.aws_region }
variable "aws_region" { type = string, default = "us-east-1" }
variable "bucket_name" { type = string }
resource "aws_s3_bucket" "b" {
bucket = var.bucket_name
tags = { ManagedBy = "Terraform" }
}
output "bucket_name" { value = aws_s3_bucket.b.bucket }
Plan only if you don’t have credentials set up. Apply only in a safe sandbox account.
Exercises
Note: The quick test is available to everyone; sign in to save your progress.
Exercise 1 — Config file generator (local)
Create a Terraform config that writes a JSON file config.json containing two fields: env and service. Values must come from variables.
- Use the local provider and a single resource local_file.
- Expose an output named file_path showing the absolute path.
- Run init → plan → apply. Verify the file content.
Exercise 2 — Reusable module (local)
Create a module logset that writes two files: {name}-app.log and {name}-audit.log. Call the module twice with different names.
- Module variable: name (string).
- Output: files (list of file paths).
- Root outputs: combined list of all created files.
Exercise checklist
- terraform init completes with providers downloaded.
- terraform validate passes.
- terraform plan shows only expected adds, no destroys.
- terraform apply succeeds and outputs are printed.
- Re-running apply shows No changes.
Common mistakes and self-check
- Forgetting init: If plan fails with provider errors, run terraform init.
- Committing state: Add terraform.tfstate, terraform.tfstate.backup, and .terraform/ to .gitignore.
- Hardcoding secrets: Use variables, environment variables, and mark sensitive = true when needed.
- Unstable resource keys: Prefer for_each with stable map keys instead of count for sets that can reorder.
- Manual changes in console: Causes drift. Detect with plan; fix by updating code or importing resources.
- Unpinned providers: Pin versions to avoid breaking changes.
Self-check routine
- Run: terraform fmt -check, terraform validate.
- Run plan twice; second plan should show 0 to change.
- Change a variable value and confirm plan shows only intended changes.
Practical projects
- Local bootstrap: Use local and random providers to generate project skeleton files (README, .env.example) with variables and outputs.
- Template files: Create multiple environment-specific config files using variables and a module.
- Ephemeral artifacts: Use workspaces (default/dev) and observe separate state and outputs.
Next steps
- Learn remote state backends and locking (e.g., S3 + DynamoDB, Terraform Cloud).
- Add CI to run terraform fmt, validate, and plan on pull requests.
- Introduce modules for each service and adopt versioned releases.
Mini challenge
Build a module website that creates two files: index.html and styles.css using the local provider.
- Inputs: site_name (string), primary_color (string).
- Outputs: file paths of both files.
- Idempotent: running apply twice must show no changes.
- Stretch: add a random suffix variable (toggle via boolean) to filenames.