Click any item to expand the explanation and examples.
🚀 Core Commands
terraform init / plan / apply / destroy cli
# Initialize (download providers, set up backend) terraform initThe workflow:Preview changes
terraform plan
Apply changes
terraform apply terraform apply -auto-approve # Skip confirmation
Destroy everything
terraform destroy
Format code
terraform fmt terraform fmt -recursive
Validate syntax
terraform validate
Show current state
terraform show
List resources in state
terraform state list
init → plan → apply. Always plan before apply.
terraform state — manage state cli
# List all resources terraform state listShow details of a resource
terraform state show aws_instance.web
Remove resource from state (without destroying it)
terraform state rm aws_instance.web
Move/rename resource in state
terraform state mv aws_instance.web aws_instance.app
Import existing resource into state
terraform import aws_instance.web i-1234567890abcdef0
Pull remote state to local file
terraform state pull > state.json
📐 HCL Syntax
Providers and resources syntax
# Provider
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider “aws” {
region = “eu-west-1”
}
Resource
resource “aws_instance” “web” {
ami = “ami-0c55b159cbfafe1f0”
instance_type = “t3.micro”
tags = {
Name = “web-server”
Env = “production”
}
}
Variables and outputs syntax
# variables.tf
variable "region" {
type = string
default = "eu-west-1"
description = "AWS region"
}
variable “instance_type” {
type = string
default = “t3.micro”
}
variable “allowed_cidrs” {
type = list(string)
default = [“0.0.0.0/0”]
}
variable “tags” {
type = map(string)
default = {
Env = “dev”
}
}
Use variables
resource “aws_instance” “web” {
instance_type = var.instance_type
tags = var.tags
}
outputs.tf
output “instance_ip” {
value = aws_instance.web.public_ip
description = “Public IP of the web server”
}
Set variables
terraform apply -var=“region=us-east-1”
terraform apply -var-file=“prod.tfvars”
export TF_VAR_region=us-east-1
Data sources and locals syntax
# Data source — read existing resources
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = “name”
values = [“ubuntu/images/hvm-ssd/ubuntu--amd64-server-”]
}
}
resource “aws_instance” “web” {
ami = data.aws_ami.ubuntu.id
}
Locals — computed values
locals {
name_prefix = ”${var.project}-${var.environment}”
common_tags = {
Project = var.project
Environment = var.environment
ManagedBy = “terraform”
}
}
resource “aws_instance” “web” {
tags = merge(local.common_tags, { Name = ”${local.name_prefix}-web” })
}
🔁 Loops & Conditionals
count, for_each, for loops
# count — create N copies
resource "aws_instance" "web" {
count = 3
ami = "ami-123"
instance_type = "t3.micro"
tags = { Name = "web-${count.index}" }
}
for_each — create from map/set
resource “aws_iam_user” “users” {
for_each = toset([“alice”, “bob”, “carol”])
name = each.value
}
for_each with map
variable “buckets” {
default = {
assets = { acl = “private” }
logs = { acl = “private” }
public = { acl = “public-read” }
}
}
resource “aws_s3_bucket” “this” {
for_each = var.buckets
bucket = ”${var.project}-${each.key}”
}
for expression
output “instance_ips” {
value = [for i in aws_instance.web : i.public_ip]
}
Conditional
resource “aws_instance” “bastion” {
count = var.create_bastion ? 1 : 0
ami = “ami-123”
instance_type = “t3.micro”
}
Prefer for_each over count — it’s safer when removing items from the middle of a list.
📦 Modules
Using and creating modules modules
# Use a public module
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = “my-vpc”
cidr = “10.0.0.0/16”
azs = [“eu-west-1a”, “eu-west-1b”]
}
Use a local module
module “web” {
source = ”./modules/web-server”
instance_type = “t3.small”
subnet_id = module.vpc.public_subnets[0]
}
Module structure
modules/web-server/
main.tf
variables.tf
outputs.tf
🗄️ Backend (Remote State)
S3 backend backend
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Always use remote state in teams. The DynamoDB table prevents concurrent modifications.
⚡ Tips
Common patterns tips
# Target specific resource terraform plan -target=aws_instance.web terraform apply -target=aws_instance.webRefresh state (detect drift)
terraform refresh
Generate dependency graph
terraform graph | dot -Tpng > graph.png
Workspace (multiple environments)
terraform workspace new staging terraform workspace select production terraform workspace list
Lock version
terraform { required_version = ”>= 1.5.0” }
.gitignore for Terraform
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars (if contains secrets)