Terraform Quick Reference
Everything you need day‑to‑day – HCL, providers, resources, state, and modules.
Core Concepts
Key Concepts
- Infrastructure as Code (IaC) – declarative infrastructure
- HCL – HashiCorp Configuration Language
- Provider – plugin for cloud/services (AWS, GCP, Azure, etc.)
- Resource – infrastructure component (EC2, S3, VPC, etc.)
- Data Source – read‑only information from providers
- Module – reusable collection of resources
- State – mapping of resources to real infrastructure
- Workspace – isolated state environments
Common Workflow
- Write – define configuration (.tf files)
- Init –
terraform init(download providers) - Plan –
terraform plan(preview changes) - Apply –
terraform apply(create/update resources) - Destroy –
terraform destroy(remove resources)
Installation & Setup
# macOS brew tap hashicorp/tap brew install hashicorp/tap/terraform # Linux wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update && sudo apt install terraform # Windows (Chocolatey) choco install terraform # Verify installation terraform --version
HCL Syntax
Basic Structure
# main.tf # Provider configuration provider "aws" { region = "us-east-1" } # Resource resource "aws_instance" "example" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro" tags = { Name = "example-instance" } }
Resource Syntax
resource "resource_type" "local_name" {
argument1 = value1
argument2 = value2
block {
nested_argument = nested_value
}
}
# Example
resource "aws_security_group" "web" {
name = "web-sg"
description = "Allow HTTP and SSH"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
}
Variables
# variables.tf
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t3.micro"
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "tags" {
description = "Resource tags"
type = map(string)
default = {
Environment = "dev"
Terraform = "true"
}
}
variable "instance_count" {
description = "Number of instances"
type = number
default = 1
validation {
condition = var.instance_count > 0 && var.instance_count <= 10
error_message = "Instance count must be between 1 and 10."
}
}
Outputs
# outputs.tf
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.example.id
}
output "instance_public_ip" {
description = "Public IP of the EC2 instance"
value = aws_instance.example.public_ip
}
output "instance_tags" {
description = "Tags of the instance"
value = aws_instance.example.tags
sensitive = false
}
Data Sources
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "example" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
}
Locals
locals {
environment = "prod"
project = "myapp"
common_tags = {
Environment = local.environment
Project = local.project
ManagedBy = "Terraform"
}
name_prefix = "${local.environment}-${local.project}"
}
resource "aws_instance" "example" {
tags = local.common_tags
name = "${local.name_prefix}-server"
}
Modules
# Use a module from the Terraform Registry module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "5.0.0" name = "my-vpc" cidr = "10.0.0.0/16" azs = ["us-east-1a", "us-east-1b", "us-east-1c"] private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] enable_nat_gateway = true enable_vpn_gateway = false tags = local.common_tags } # Access module outputs output "vpc_id" { value = module.vpc.vpc_id }
Conditionals
# Ternary operator resource "aws_instance" "example" { instance_type = var.environment == "prod" ? "t3.large" : "t3.micro" ami = "ami-0c55b159cbfafe1f0" # count with conditional count = var.environment == "prod" ? 3 : 1 } # Condition with dynamic block dynamic "ebs_block" { for_each = var.attach_ebs ? [1] : [] content { device_name = "/dev/sdh" volume_type = "gp3" volume_size = 100 } }
Loops (for_each & count)
# count – iterate by number resource "aws_instance" "example" { count = var.instance_count instance_type = var.instance_type ami = data.aws_ami.amazon_linux.id tags = { Name = "instance-${count.index}" } } # for_each – iterate over set/map variable "users" { type = list(string) default = ["alice", "bob", "charlie"] } resource "aws_iam_user" "users" { for_each = toset(var.users) name = each.value } # for_each with map variable "instances" { type = map(object({ instance_type = string ami = string })) default = { "web" = { instance_type = "t3.micro" ami = "ami-0c55b159cbfafe1f0" } "app" = { instance_type = "t3.medium" ami = "ami-0c55b159cbfafe1f0" } } } resource "aws_instance" "custom" { for_each = var.instances instance_type = each.value.instance_type ami = each.value.ami tags = { Name = each.key } }
Functions
# String functions upper("hello") # HELLO lower("WORLD") # world join(", ", ["a", "b", "c"]) # a, b, c split("-", "a-b-c") # ["a", "b", "c"] replace("foo", "o", "x") # fxx # Collection functions length(["a", "b", "c"]) # 3 element(["a", "b", "c"], 1) # b lookup({a=1, b=2}, "a", 0) # 1 keys({a=1, b=2}) # ["a", "b"] values({a=1, b=2}) # [1, 2] # CIDR functions cidrsubnet("10.0.0.0/16", 8, 1) # 10.0.1.0/24 cidrhost("10.0.1.0/24", 10) # 10.0.1.10 # File functions file("path/to/file.txt") templatefile("path/to/template.tpl", { var = "value" })
Terraform CLI Commands
Core Commands
| Command | Description |
|---|---|
terraform init |
Initialize directory, download providers |
terraform plan |
Preview changes (dry run) |
terraform apply |
Create/update resources (with approval) |
terraform apply -auto-approve |
Apply without approval |
terraform destroy |
Delete all resources |
terraform destroy -auto-approve |
Destroy without approval |
terraform validate |
Validate configuration syntax |
terraform fmt |
Format HCL files |
State Commands
| Command | Description |
|---|---|
terraform state list |
List resources in state |
terraform state show resource |
Show resource details |
terraform state mv src dst |
Move resource in state |
terraform state rm resource |
Remove resource from state |
terraform refresh |
Update state with real infrastructure |
Workspace Commands
| Command | Description |
|---|---|
terraform workspace list |
List all workspaces |
terraform workspace new name |
Create new workspace |
terraform workspace select name |
Switch workspace |
terraform workspace delete name |
Delete workspace |
terraform workspace show |
Show current workspace |
Module Commands
| Command | Description |
|---|---|
terraform get |
Download and update modules |
terraform providers |
List required providers |
Advanced Commands
| Command | Description |
|---|---|
terraform output |
Display output values |
terraform output -json |
Output as JSON |
terraform graph |
Generate dependency graph (DOT format) |
terraform graph | dot -Tpng > graph.png |
Generate graph as PNG |
terraform show |
Show state or plan |
terraform console |
Interactive console for expressions |
terraform taint resource |
Force resource recreation on next apply |
terraform untaint resource |
Remove taint from resource |
terraform import resource id |
Import existing resource into state |
Common CLI Flags
# General flags -var="key=value" # Set variable value -var-file="vars.tfvars" # Use variable file -state="path/to/state.tfstate" # Custom state file -chdir=./subdir # Change working directory # Plan & Apply -target=resource # Only apply to specific resource -replace=resource # Force replacement -destroy # Plan for destroy -out=plan.tfplan # Save plan to file # Examples terraform plan -var="environment=prod" terraform apply -target=aws_instance.example terraform apply -replace=aws_instance.example terraform plan -destroy -out=destroy-plan
Backend Configuration
Local Backend (Default)
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
S3 Backend (Remote State)
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
Azure Backend
terraform {
backend "azurerm" {
resource_group_name = "my-rg"
storage_account_name = "myterraformstate"
container_name = "tfstate"
key = "prod.terraform.tfstate"
}
}
GCS Backend (Google Cloud)
terraform {
backend "gcs" {
bucket = "my-terraform-state"
prefix = "prod"
}
}
Provider Configuration
AWS Provider
provider "aws" {
region = var.aws_region
access_key = var.aws_access_key
secret_key = var.aws_secret_key
default_tags {
tags = {
Environment = var.environment
Project = var.project
}
}
}
Multiple Providers
provider "aws" {
alias = "us-east-1"
region = "us-east-1"
}
provider "aws" {
alias = "us-west-2"
region = "us-west-2"
}
resource "aws_instance" "east" {
provider = aws.us-east-1
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}
resource "aws_instance" "west" {
provider = aws.us-west-2
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}
Required Providers
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
Common Patterns
Random Resource
resource "random_id" "suffix" {
byte_length = 4
}
resource "random_pet" "name" {
length = 2
separator = "-"
}
resource "aws_s3_bucket" "example" {
bucket = "my-bucket-${random_id.suffix.hex}-${random_pet.name.id}"
}
Lifecycle Rules
resource "aws_instance" "example" {
lifecycle {
create_before_destroy = true
prevent_destroy = false
ignore_changes = [
ami,
user_data,
]
}
}
Depends On
resource "aws_security_group" "web" {
# ...
}
resource "aws_instance" "web" {
depends_on = [aws_security_group.web]
# ...
}
Provisioners
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t3.micro"
user_data = templatefile("${path.module}/user_data.sh", {
app_name = var.app_name
})
connection {
type = "ssh"
user = "ec2-user"
private_key = file("${path.module}/key.pem")
host = self.public_ip
}
provisioner "file" {
source = "script.sh"
destination = "/tmp/script.sh"
}
provisioner "remote-exec" {
inline = [
"chmod +x /tmp/script.sh",
"/tmp/script.sh"
]
}
}
State Management
State Commands Example
# List all resources terraform state list # Show specific resource terraform state show aws_instance.example # Move resource terraform state mv aws_instance.example module.ec2.aws_instance.example # Remove resource from state (then import later) terraform state rm aws_instance.example # Import existing resource terraform import aws_instance.example i-1234567890abcdef0
State Locking
# DynamoDB table for locking (AWS)
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
Best Practices
- Use remote state – store state in S3, GCS, Azure Storage (not local)
- Enable state locking – prevent concurrent modifications
- Version your state – enable versioning on state bucket
- Use modules – organise and reuse code
- Use variables and locals – avoid hardcoding
- Tag resources – for cost tracking and identification
- Use workspaces – separate environments (dev, staging, prod)
- Use `.tfvars` files – separate config from code
- Run `terraform plan` – always review changes before applying
- Use `terraform fmt` – maintain consistent formatting
- Use `terraform validate` – catch syntax errors early
- Use `terraform apply -target` – for focused changes (use sparingly)
- Set `create_before_destroy` – avoid downtime during updates
- Avoid hardcoding secrets – use HashiCorp Vault, AWS Secrets Manager
- Use provisioners sparingly – prefer user_data and configuration management
- Version your modules – use semantic versioning
- Use CI/CD – automate Terraform runs (GitHub Actions, GitLab CI, Atlantis)
- Plan and apply in CI – with approval gates
Directory Structure
.
├── main.tf
├── variables.tf
├── outputs.tf
├── locals.tf
├── providers.tf
├── terraform.tfvars
├── modules/
│ ├── network/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── compute/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── environments/
├── dev/
│ └── terraform.tfvars
├── staging/
│ └── terraform.tfvars
└── prod/
└── terraform.tfvars
📌 Quick Reference
Init:
Plan:
Apply:
Destroy:
State:
Workspace:
Backend: S3 (AWS), GCS (GCP), Azurerm (Azure) – remote state
Resources:
Variables:
Modules: Reusable collections, source from registry or local path
terraform initPlan:
terraform planApply:
terraform applyDestroy:
terraform destroyState:
terraform state list, terraform state showWorkspace:
terraform workspace new/select/showBackend: S3 (AWS), GCS (GCP), Azurerm (Azure) – remote state
Resources:
resource "provider_type" "name"Variables:
variable "name" { type = string }Modules: Reusable collections, source from registry or local path