Terraform
Filename suggestion:
TERRAFORM_CHEATSHEET.md
Quick reference for HashiCorp Terraform (HCL & CLI). Covers commands, configuration patterns, state, modules, and best practices.
⚙️ CLI Basics🔗
terraform init # Initialize working directory (providers, backend)
terraform validate # Validate configuration
terraform fmt # Reformat files to canonical HCL style
terraform plan # Show execution plan
terraform apply # Apply changes (use -auto-approve to skip prompt)
terraform destroy # Destroy managed infrastructure
terraform fmt -recursive
Common flags:
-var 'name=value'pass variable on CLI-var-file="filename.tfvars"pass variables file-target=resourcefocus plan/apply to specific resource (use rarely)-out=tfplansave plan to file-lock=falsedisable state locking (careful)
�️ File layout (recommended)🔗
.
├── main.tf # resources & provider configuration
├── variables.tf # variable declarations
├── outputs.tf # outputs
├── providers.tf # provider blocks (optional split)
├── versions.tf # required Terraform and provider versions
├── terraform.tfvars # default variable values (gitignored for secrets)
└── modules/ # local modules
� HCL Basics🔗
Providers🔗
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = var.region
}Resources🔗
resource "aws_instance" "web" {
ami = var.ami
instance_type = "t3.micro"
tags = {
Name = "web-${var.env}"
}
}Data sources🔗
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/*ubuntu-focal-20.04-amd64-server-*"]
}
}Variables🔗
variable "region" {
description = "AWS region"
type = string
default = "eu-west-1"
}
variable "allowed_ips" {
type = list(string)
}
Pass via terraform.tfvars or CLI:
region = "eu-west-1"Outputs🔗
output "instance_ip" {
description = "Public IP of web"
value = aws_instance.web.public_ip
sensitive = false
}
� Expressions, Interpolation & Functions🔗
- String interpolation:
${var.name}(HCL2 supports direct expressions:var.name) - Conditionals:
count = var.create ? 1 : 0 - For expressions:
tags = { for k,v in local.base_tags : k => v } subnet_ids = [for s in aws_subnet.all : s.id] - Common functions:
lookup(),concat(),join(),split(),element(),length(),replace(),file(),filebase64(),terraform.workspace
� Count, For_each, Dynamic Blocks🔗
count🔗
resource "aws_instance" "app" {
count = var.instance_count
ami = var.ami
instance_type = "t3.micro"
tags = { Name = "app-${count.index}" }
}for_each🔗
resource "aws_s3_bucket" "b" {
for_each = toset(var.bucket_names)
bucket = each.value
}Dynamic blocks🔗
resource "aws_security_group" "sg" {
# ...
dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from
to_port = ingress.value.to
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}
� State & Backends🔗
Local state (default)🔗
State stored in terraform.tfstate (don’t commit to VCS).
Remote backends (recommended for teams)🔗
Examples: S3 (AWS) with DynamoDB lock, Terraform Cloud, Azure Blob, GCS.
Example S3 backend with locking:
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-west-1"
dynamodb_table = "terraform-locks"
encrypt = true
}
}
Commands for remote state:
terraform init # configure backend
terraform state list # list resources in state
terraform state show aws_instance.web
terraform state pull # fetch remote state
terraform state push # push state (use carefully)
terraform refresh # update state with real-world resources
� Workspaces🔗
terraform workspace listterraform workspace new devterraform workspace select prodterraform.workspacegives current workspace in expressions
Workspaces are namespace for state — use with caution; not a substitute for separate environments when resources differ.
� Modules🔗
Create reusable modules under modules/ or use registry modules.
Call a module:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = ">= 3.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["eu-west-1a","eu-west-1b"]
}
Local module:
module "app" {
source = "./modules/app"
env = var.env
}
Best practices:
- Keep modules small and focused.
- Version registry modules via
version. - Use inputs/outputs; avoid hard-coded ARNs.
� Provisioners (last resort)🔗
Use remote-exec or local-exec only when necessary (immutable infra preferred).
Example:
resource "aws_instance" "app" {
# ...
provisioner "remote-exec" {
inline = [
"sudo apt update",
"sudo apt install -y nginx"
]
connection {
type = "ssh"
user = "ubuntu"
private_key = file(var.private_key_path)
host = self.public_ip
}
}
}
� Importing existing resources🔗
terraform import aws_instance.web i-0123456789abcdef0
Then add the corresponding resource block to config and run terraform plan to align.
⚠️ Lifecycle meta-arguments🔗
resource "aws_instance" "db" {
# ...
lifecycle {
prevent_destroy = true # protect resource from destroy
create_before_destroy = true # replace without downtime
ignore_changes = [ "tags" ] # ignore specific attribute changes
}
}
� Locks & Concurrency🔗
- Use backend with locking (DynamoDB for S3, Terraform Cloud, etc.).
terraform force-unlock LOCK_IDto recover from stale locks.
� Debugging & Logs🔗
TF_LOG=TRACE terraform apply(very verbose)TF_LOG=DEBUGorINFOfor less noiseTF_CLI_ARGS_plan="-out=tfplan"to persist plans
� Secrets & Sensitive Data🔗
- Mark outputs/variables as
sensitive = true. - Avoid storing secrets in
terraform.tfstate(state can contain secrets). - Use secret stores: AWS Secrets Manager, Vault (HashiCorp Vault provider), SSM Parameter Store.
- Use environment variables for provider creds (
AWS_PROFILE,GOOGLE_APPLICATION_CREDENTIALS, etc.).
✅ Best practices🔗
- Keep
terraform.tfstatein secure remote backend (not in Git). - Use
terraform fmtandvalidatein CI. - Pin provider versions in
required_providers. - Break infra into small modules.
- Prefer immutable infra patterns (replace rather than mutate).
- Use
create_before_destroyfor critical resources needing replacement. - Review plans (
terraform plan) beforeapplyin CI/CD.
� CI/CD Tips🔗
- In CI, run:
terraform init -input=false
terraform plan -input=false -out=tfplan
terraform show -no-color tfplan > tfplan.txt
# human review or automated checks
terraform apply -input=false tfplan
- Consider Terraform Cloud or Atlantis for PR-driven workflows.
� Useful Commands Summary🔗
terraform init
terraform fmt -recursive
terraform validate
terraform plan -out=tfplan
terraform apply tfplan
terraform apply -auto-approve
terraform destroy
terraform import <type.name> <id>
terraform state list
terraform state show <address>
terraform workspace list
terraform workspace new <name>
terraform providers
� Further resources🔗
- Official docs: https://developer.hashicorp.com/terraform
- Terraform Registry: https://registry.terraform.io
- Style guides & community modules on the registry
End of Terraform cheat sheet.