Basics
Core Workflow
terraform init # install providers & modules terraform plan # preview changes terraform apply # apply changes terraform destroy # tear down all resources
Essential Commands
terraform initInitialize working directory, download providers
terraform planShow execution plan without applying
terraform applyApply changes to infrastructure
terraform destroyDestroy all managed resources
terraform fmtFormat .tf files to canonical style
terraform validateCheck configuration syntax
terraform showDisplay current state or plan
terraform outputPrint output values
Providers
Provider Configuration
terraform { required_providers { aws = { source = "hashicorp/aws", version = "~> 5.0" } } } provider "aws" { region = "us-east-1" }
Provider Notes
sourceRegistry address (hashicorp/aws, hashicorp/google)
versionVersion constraint (~> 5.0, >= 3.0, < 4.0)
.terraform.lock.hclLock file — commit to version control
aliasUse multiple configs for the same provider
Resources
Resource Blocks
resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro" tags = { Name = "web-server" } }
Resource Meta-Arguments
depends_onExplicit dependency on another resource
countCreate multiple instances (count = 3)
for_eachCreate instances from a map or set
providerSelect a non-default provider alias
lifecycleCustomize create/update/destroy behavior
Referencing Resources
# type.name.attribute aws_instance.web.id aws_instance.web.public_ip aws_vpc.main.cidr_block
Variables
Declaring Variables
variable "region" { type = string default = "us-east-1" } variable "instance_count" { type = number description = "Number of instances" }
Setting Variable Values
-var 'region=us-west-2'CLI flag
-var-file=prod.tfvarsLoad from a .tfvars file
terraform.tfvarsAuto-loaded if present
TF_VAR_regionEnvironment variable
Interactive promptAsked at plan/apply if no default
Variable Types
string"us-east-1"
number42
booltrue / false
list(string)["a", "b"]
map(string){ key = "val" }
object({...})Structured type with named attributes
Outputs
Defining Outputs
output "instance_ip" { value = aws_instance.web.public_ip description = "Public IP of the web server" } output "db_password" { value = random_password.db.result sensitive = true }
Output Commands
terraform outputPrint all outputs
terraform output instance_ipPrint a specific output
terraform output -jsonJSON format for scripting
sensitive = trueHide value from CLI output
module.vpc.vpc_idAccess child module outputs
State
Remote Backend
terraform { backend "s3" { bucket = "my-tf-state" key = "prod/terraform.tfstate" region = "us-east-1" } }
State Commands
terraform state listList all resources in state
terraform state show <addr>Show attributes of a resource
terraform state mv <src> <dst>Rename / move a resource in state
terraform state rm <addr>Remove resource from state (keep infra)
terraform state pullDownload remote state to stdout
terraform import <addr> <id>Import existing infra into state
Modules
Using Modules
module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0" cidr = "10.0.0.0/16" }
Module Sources
"./modules/vpc"Local path
"terraform-aws-modules/vpc/aws"Terraform Registry
"github.com/org/repo//module"GitHub repository
"s3::https://bucket/module.zip"S3 bucket
Module Structure
modules/vpc/ main.tf # resources variables.tf # input variables outputs.tf # output values
Data Sources
Reading Existing Resources
data "aws_ami" "ubuntu" { most_recent = true filter { name = "name" values = ["ubuntu/images/hvm-ssd/*"] } owners = ["099720109477"] }
Common Data Sources
data.aws_amiLook up an AMI by filters
data.aws_vpcLook up existing VPC
data.aws_caller_identityCurrent AWS account ID
data.aws_regionCurrent AWS region
data.terraform_remote_stateRead outputs from another state file
data.externalRun an external program for data
Lifecycle
Lifecycle Rules
resource "aws_instance" "web" { lifecycle { create_before_destroy = true prevent_destroy = true ignore_changes = [tags] } }
Lifecycle Options
create_before_destroyCreate replacement before destroying old
prevent_destroyError if terraform destroy targets this
ignore_changesDon't detect drift on listed attributes
replace_triggered_byForce replacement when referenced resource changes
preconditionValidate assumptions before apply
postconditionValidate results after apply
Common Patterns
Loops & Conditionals
# for_each with a map resource "aws_iam_user" "users" { for_each = toset(["alice", "bob"]) name = each.value } # conditional resource count = var.create_db ? 1 : 0
Useful Functions
file("key.pub")Read file contents
join(", ", list)Join list into string
lookup(map, key, default)Map lookup with fallback
length(list)Number of elements
toset(["a", "b"])Convert list to set (for for_each)
try(expr, fallback)Return fallback if expr errors
templatefile(path, vars)Render a template file