Tutorial
Terraform Tutorial
A practical tutorial on Terraform using AWS, from your first apply to running it in production with CI/CD and policy checks. Covers HCL, state, modules, remote backends, environments, testing, the OpenTofu fork, and the habits that keep a Terraform codebase healthy.
Chapters
01
Introduction: What Terraform Is and Why It Exists
02
HCL Basics: Blocks, Arguments, Expressions
03
Variables and Outputs: Making Configs Reusable
04
Resources and Data Sources: Where the Infrastructure Lives
05
State: What It Is, Why It Matters, How to Not Break It
06
Modules: Composition, Not Copy-Paste
07
Remote Backends: Sharing State Safely
08
Environments: Dev, Staging, Production
09
CI/CD: Plan, Apply, and Drift Detection
10
Testing and Validation: Catching Bugs Before They Cost Money
11
Ecosystem: Terragrunt, Atlantis, OpenTofu, and Friends
12
Best Practices: Habits and Anti-Patterns
About this tutorial
A practical tour of Terraform using AWS, from your first apply to running it in production with CI/CD, policy checks, and a team.
Who This Is For
- Engineers who've clicked through the AWS console and want to stop
- Developers who know what infrastructure is but have never written it as code
- Teams tired of "works in Sarah's environment, broken in Tom's"
- Anyone who has seen a rogue apply nuke a production resource and wants to prevent the next one
Contents
Fundamentals
- Introduction: What Terraform is, install, providers, first apply
- HCL Basics: Blocks, arguments, expressions, references
- Variables and Outputs: Input variables, outputs, locals, types, .tfvars
Core Concepts
- Resources and Data Sources: Lifecycle, depends_on, count, for_each
- State: What state is, where it lives, terraform state commands
- Modules: Writing and consuming modules, registry, versioning
Advanced
- Remote Backends: S3 backend, DynamoDB locking, workspaces
- Environments: Dev, staging, production patterns
Ecosystem
- CI/CD: GitHub Actions plan/apply, drift detection, PR workflows
- Testing and Validation: validate, fmt, tflint, Terratest, policy-as-code
- Ecosystem: Terragrunt, Atlantis, Spacelift, OpenTofu, Terramate
Mastery
- Best Practices: Directory structure, naming, production patterns, anti-patterns
How to Use This Tutorial
- Read sequentially for a complete path from zero to production-ready
- Have an AWS account open. A free-tier account is enough for every example
- Destroy what you create. Each chapter ends with
terraform destroy; don't leave resources running
Quick Reference
Essential Commands
# The core loop
terraform init # download providers, initialize backend
terraform plan # preview changes (read-only)
terraform apply # apply changes (with confirmation)
terraform destroy # tear down managed resources
# Code quality
terraform fmt # auto-format .tf files
terraform validate # check syntax and internal consistency
# State operations
terraform state list # show tracked resources
terraform state show <addr> # show one resource
terraform state mv <src> <dst> # rename or move a resource
terraform state rm <addr> # untrack (without destroying)
# Workspace
terraform workspace list
terraform workspace new staging
terraform workspace select production
Hello World (Your First Bucket)
terraform {
required_version = ">= 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "notes" {
bucket = "ada-notes-2026-04-19"
}
Run:
terraform init
terraform plan
terraform apply
terraform destroy
Common Patterns
# Input variable with a default
variable "environment" {
type = string
default = "dev"
}
# Output
output "bucket_name" {
value = aws_s3_bucket.notes.bucket
}
# Local (computed once)
locals {
common_tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
# Data source (read existing resources)
data "aws_caller_identity" "current" {}
# count (identical copies)
resource "aws_s3_bucket" "logs" {
count = 3
bucket = "logs-${count.index}"
}
# for_each (per-key uniqueness)
resource "aws_s3_bucket" "per_env" {
for_each = toset(["dev", "staging", "prod"])
bucket = "notes-${each.key}"
}
# Module call
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.5.0"
name = "main"
cidr = "10.0.0.0/16"
}
Learning Path Suggestions
Stop clicking in the AWS console (roughly 6 hours)
- Chapters 01 to 04 to write and apply real Terraform
- Chapter 05 for state, so you don't panic the first time it surprises you
- Chapter 12 for production habits before you ship
Team-ready Terraform (roughly 12 hours)
- All chapters in order
- Build a small real project (a VPC + a few services) as you go
- Set up CI/CD from chapter 9 on a personal repo
Evaluating tools around Terraform (roughly 3 hours)
- Chapter 11 for the ecosystem
- Chapter 10 for testing and policy
- Chapter 08 for environment patterns
Additional Resources
- Terraform docs: the canonical reference
- AWS provider docs: every resource and argument
- Terraform Best Practices by Anton Babenko: practical patterns
- OpenTofu: the open-source fork of Terraform, drop-in compatible
- Terragrunt docs: the wrapper that solves some of Terraform's ergonomic gaps
- awesome-terraform: a curated list
Terraform Version
This tutorial is written for Terraform 1.6+ (and works unchanged on OpenTofu 1.6+). AWS provider version ~> 5.0. Older versions work with small tweaks; some newer features require the versions listed.