Portal | Level: L1: Foundations | Topics: Terraform | Domain: DevOps & Tooling
Infrastructure as Code with Terraform - Primer¶
Why This Matters¶
Terraform lets you define infrastructure in code, version it in Git, review it in PRs, and apply it reproducibly. Instead of clicking through a cloud console, you write declarative configuration that describes the desired state, and Terraform figures out what to create, modify, or destroy to reach that state. This is the foundation of modern infrastructure management.
Core Concepts¶
How Terraform Works¶
Write config (.tf files)
|
v
terraform init # Download providers, initialize backend
|
v
terraform plan # Preview what will change (dry run)
|
v
terraform apply # Execute the changes
|
v
State file updated # Terraform records what it manages
Terraform is declarative: you describe what you want, not the steps to get there. If you define 3 EC2 instances and run apply, Terraform creates 3. If you change the config to 2 and apply again, Terraform destroys one.
Providers¶
Providers are plugins that talk to APIs (AWS, GCP, Azure, Kubernetes, GitHub, etc.):
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
Version constraints: ~> 5.0 means >= 5.0 and < 6.0. Pin provider versions to avoid surprises.
Resources¶
Resources are the infrastructure objects Terraform manages:
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.main.id
tags = {
Name = "web-server"
Environment = "production"
}
}
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
Resource references: aws_subnet.main.id references an attribute from another resource. Terraform builds a dependency graph and creates resources in the correct order.
State¶
State is Terraform's record of what it manages. It maps your config to real-world resources.
# Backend configuration (where state is stored)
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/network/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks" # State locking
encrypt = true
}
}
- Local state (
terraform.tfstate): fine for learning, dangerous for teams - Remote state (S3, GCS, Terraform Cloud): required for collaboration
- State locking (DynamoDB, GCS): prevents two people from applying simultaneously
Variables and Outputs¶
# variables.tf
variable "environment" {
description = "Deployment environment"
type = string
default = "staging"
validation {
condition = contains(["staging", "production"], var.environment)
error_message = "Environment must be staging or production."
}
}
variable "instance_count" {
description = "Number of instances"
type = number
default = 2
}
# Using variables
resource "aws_instance" "web" {
count = var.instance_count
instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
# ...
}
# outputs.tf
output "instance_ips" {
description = "Public IPs of web instances"
value = aws_instance.web[*].public_ip
}
Setting variables (in order of precedence):
1. Command line: terraform apply -var="environment=production"
2. Variable file: terraform apply -var-file="prod.tfvars"
3. Environment variables: TF_VAR_environment=production
4. Default values in variable definition
Modules¶
Modules are reusable packages of Terraform configuration:
# Using a module
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
}
# Referencing module outputs
resource "aws_instance" "web" {
subnet_id = module.vpc.private_subnets[0]
}
Module sources: Terraform Registry, Git repos, local paths. Always pin module versions.
Plan and Apply¶
# Preview changes (ALWAYS review this)
terraform plan -out=tfplan
# Apply the reviewed plan
terraform apply tfplan
# Destroy all resources
terraform destroy
# Target specific resources
terraform plan -target=aws_instance.web
terraform apply -target=aws_instance.web
The plan output shows:
- + create
- - destroy
- ~ update in-place
- -/+ destroy and recreate (replacement)
Data Sources¶
Read information from existing infrastructure (not managed by this config):
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-*"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
}
What Experienced People Know¶
- Always run
terraform planbeforeterraform apply. Read the plan output completely. A misseddestroyin the plan can take down production. - State is sacred. Lose it and Terraform doesn't know what it manages. Back up remote state with versioning enabled on the S3 bucket.
- Never store secrets in
.tffiles or.tfvarscommitted to Git. Use environment variables, vault references, orsensitive = trueon variables. -targetis for emergencies, not workflow. If you're routinely targeting, your configuration is too coupled.- Terraform doesn't detect manual changes (drift) unless you run
plan. If someone modifies infrastructure outside of Terraform, the next apply may revert their changes. countvsfor_each: usefor_eachwhen possible. Withcount, removing an item from the middle of a list forces recreation of subsequent resources.for_eachuses map keys, so removals are surgical.
See Also¶
- Deep dive: Terraform State Internals
- Cheatsheet: Terraform
- Drills: Terraform Drills
- Skillcheck: Terraform IaC
Wiki Navigation¶
Prerequisites¶
- Linux Ops (Topic Pack, L0)
Next Steps¶
- Case Study: Terraform Apply Fails — State Lock Stuck, DynamoDB Throttle (Case Study, L2)
- Crossplane (Topic Pack, L2)
- Infrastructure Testing (Topic Pack, L2)
- OpenTofu & Terraform Ecosystem (Topic Pack, L2)
- Pulumi (Topic Pack, L2)
- Terraform Deep Dive (Topic Pack, L2)
- Terraform Drills (Drill, L1)
Related Content¶
- Case Study: SSH Timeout — MTU Mismatch, Fix Is Terraform Variable (Case Study, L2) — Terraform
- Case Study: Terraform Apply Fails — State Lock Stuck, DynamoDB Throttle (Case Study, L2) — Terraform
- Crossplane (Topic Pack, L2) — Terraform
- Deep Dive: Terraform State Internals (deep_dive, L2) — Terraform
- Mental Models (Core Concepts) (Topic Pack, L0) — Terraform
- OpenTofu & Terraform Ecosystem (Topic Pack, L2) — Terraform
- Pulumi (Topic Pack, L2) — Terraform
- Runbook: Cloud Capacity Limit Hit (Runbook, L2) — Terraform
- Runbook: Terraform Drift Detection Response (Runbook, L2) — Terraform
- Runbook: Terraform State Lock Stuck (Runbook, L2) — Terraform
Pages that link here¶
- Anti-Primer: Terraform
- Certification Prep: HashiCorp Terraform Associate
- Comparison: Infrastructure as Code Tools
- Crossplane
- How We Got Here: Infrastructure as Code
- Infrastructure Testing
- Master Curriculum: 40 Weeks
- Mental Models (Core Concepts)
- Opentofu
- Production Readiness Review: Answer Key
- Production Readiness Review: Study Plans
- Pulumi
- Runbook: Cloud Capacity Limit Hit
- Runbook: Terraform Drift Detection Response
- Runbook: Terraform State Lock Stuck