All topics
DevOps · Learning hub

Terraform notes for developers

Master Terraform with a curated set of 1 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore DevOps notes
Terraform

Terraform Basics

Terraform Basics Core Concepts Infrastructure as Code — declare infrastructure in .tf files, version control it, review it like code Provider — plugin to intera

Terraform Basics

Core Concepts

  • Infrastructure as Code — declare infrastructure in .tf files, version control it, review it like code

  • Provider — plugin to interact with a platform (AWS, GCP, Azure, Kubernetes, GitHub)

  • Resource — infrastructure object to create/manage (EC2 instance, S3 bucket, DNS record)

  • State — terraform.tfstate tracks what Terraform manages; store remotely (S3 + DynamoDB lock)

  • Module — reusable, parameterized group of resources

  • Data source — read-only query of existing resources not managed by this config

HCL Syntax

# main.tf

# Provider configuration
terraform {
  required_version = ">= 1.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

provider "aws" {
  region = var.aws_region
}

# Variables
variable "aws_region" {
  type        = string
  description = "AWS region to deploy to"
  default     = "us-east-1"
}

variable "instance_type" {
  type    = string
  default = "t3.micro"
  validation {
    condition     = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
    error_message = "Must be a t3 instance type."
  }
}

variable "tags" {
  type    = map(string)
  default = { Environment = "prod", Team = "platform" }
}

# Locals — computed values
locals {
  name_prefix = "${var.project}"
  common_tags = merge(var.tags, { ManagedBy = "terraform" })
}

# Data source — query existing resources
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

# Resource
resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = var.instance_type
  subnet_id     = aws_subnet.public.id

  tags = merge(local.common_tags, { Name = "${local.name_prefix}-web" })
}

# Output — expose values after apply
output "instance_ip" {
  value       = aws_instance.web.public_ip
  description = "Public IP of web instance"
}

output "instance_id" {
  value     = aws_instance.web.id
  sensitive = false
}

CLI Commands

# Workflow
terraform init              # download providers, init backend
terraform validate          # syntax + config validation
terraform fmt               # format .tf files
terraform plan              # preview changes (dry-run)
terraform plan -out=tfplan  # save plan to file
terraform apply             # apply changes (prompts for confirmation)
terraform apply tfplan      # apply saved plan (no prompt)
terraform apply -auto-approve  # skip confirmation (CI/CD only)
terraform destroy           # destroy all managed resources

# Targeting
terraform plan -target=aws_instance.web
terraform apply -target=module.vpc

# State management
terraform state list                     # list managed resources
terraform state show aws_instance.web   # inspect resource state
terraform state rm aws_instance.web     # remove from state (not destroy)
terraform import aws_instance.web i-1234567  # import existing resource

# Workspace (lightweight environments)
terraform workspace list
terraform workspace new staging
terraform workspace select staging

# Variables from CLI
terraform apply -var="instance_type=t3.small"
terraform apply -var-file="prod.tfvars"

Modules & Real Example

# Using a public module
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.4.0"

  name = "${local.name_prefix}-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b", "us-east-1c"]
  public_subnets  = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  private_subnets = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]

  enable_nat_gateway = true
  tags               = local.common_tags
}

# Custom module — modules/rds/main.tf
resource "aws_db_instance" "main" {
  identifier        = var.identifier
  engine            = "postgres"
  engine_version    = "16.1"
  instance_class    = var.instance_class
  allocated_storage = var.storage_gb
  db_name           = var.db_name
  username          = var.username
  password          = var.password  # use aws_secretsmanager_secret instead!
  db_subnet_group_name   = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  skip_final_snapshot    = false
  backup_retention_period = 7
  tags               = var.tags
}

Keep your Terraform knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever