Infrastructure as Code tool for provisioning and managing cloud infrastructure declaratively.
Consider OpenTofu as an open source alternative (fork after HashiCorp licence change).
Basic Commands
terraform init # Initialise working directory, download providers
terraform plan # Preview changes without applying
terraform apply # Apply changes to infrastructure
terraform destroy # Destroy all managed infrastructureterraform fmt --recursive # Format all .tf files recursively
terraform validate # Validate configuration syntax
terraform refresh # Update state to match real infrastructure
terraform output # Display output valuesState
Terraform tracks infrastructure in a state file. This is the source of truth for what Terraform manages.
Backends
Configure where state is stored:
local- Default, stores state interraform.tfstatefiles3- AWS S3 bucket (recommended for AWS)gcs- Google Cloud Storageazurerm- Azure Blob Storageremote- Terraform Cloud/Enterprise
Remote backends provide:
- Collaboration (shared state)
- State locking (prevents concurrent modifications)
- Encryption at rest
- Versioning/history
State Commands
terraform show # Display current state
terraform state list # List all resources in state
terraform state show <resource> # Show details of a resource
terraform state mv <src> <dst> # Move/rename resource in state
terraform state rm <resource> # Remove resource from state (doesn't destroy)
terraform import <resource> <id> # Import existing infrastructure into state
terraform state pull # Download and output remote state
terraform state push # Upload local state to remoteState Locking
Most remote backends support locking to prevent concurrent operations. If a lock is stuck:
terraform force-unlock <lock_id>Variables
Input Variables
Define in configuration:
variable "region" {
type = string
default = "eu-west-2"
description = "AWS region to deploy to"
validation {
condition = can(regex("^eu-", var.region))
error_message = "Region must be in EU."
}
}Set values (in order of precedence, lowest to highest):
- Default value in variable block
terraform.tfvarsor*.auto.tfvarsfiles (auto-loaded)-var-file="env.tfvars"flag-var="region=eu-west-1"flagTF_VAR_regionenvironment variable
terraform apply -var="gcp_project_id=my-project"
terraform apply -var-file="production.tfvars"Output Variables
Expose values from your configuration:
output "instance_ip" {
value = aws_instance.web.public_ip
description = "Public IP of the web server"
sensitive = false
}Local Values
Assign names to expressions for reuse:
locals {
environment = "production"
common_tags = {
Environment = local.environment
ManagedBy = "Terraform"
}
}Loops and Conditionals
count
Create multiple instances of a resource:
resource "aws_instance" "server" {
count = 3
ami = "ami-12345"
tags = {
Name = "server-${count.index}"
}
}for_each
Iterate over maps or sets:
resource "aws_instance" "server" {
for_each = toset(["web", "api", "worker"])
ami = "ami-12345"
tags = {
Name = each.value
Key = each.key
}
}Prefer for_each over count for resources that may be added/removed - it references by key rather than index, avoiding recreation of resources.
for Expressions
Transform collections:
locals {
upper_names = [for name in var.names : upper(name)]
name_map = { for name in var.names : name => upper(name) }
}Conditional Expressions
Ternary operator:
resource "aws_instance" "server" {
instance_type = var.environment == "production" ? "m5.large" : "t3.micro"
}Conditional resource creation:
resource "aws_instance" "server" {
count = var.create_instance ? 1 : 0
}dynamic Blocks
Generate repeated nested blocks:
dynamic "ingress" {
for_each = var.ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
}
}Modules
Modules are reusable, encapsulated Terraform configurations.
Using Modules
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
}Module Sources
- Local path:
source = "./modules/vpc" - Terraform Registry:
source = "hashicorp/consul/aws" - GitHub:
source = "github.com/org/repo//modules/vpc" - S3:
source = "s3::https://bucket.s3.amazonaws.com/module.zip" - GCS:
source = "gcs::https://bucket.storage.googleapis.com/module.zip"
Module Outputs
Access module outputs:
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnet_ids[0]
}Workspaces
Manage multiple environments with the same configuration:
terraform workspace list # List workspaces
terraform workspace new staging # Create new workspace
terraform workspace select production # Switch workspace
terraform workspace delete staging # Delete workspaceAccess current workspace in configuration:
locals {
environment = terraform.workspace
}Best Practices
File Structure
project/
├── main.tf # Main resources
├── variables.tf # Variable declarations
├── outputs.tf # Output declarations
├── providers.tf # Provider configuration
├── versions.tf # Required versions
├── terraform.tfvars # Variable values (don't commit secrets)
└── modules/
└── vpc/
├── main.tf
├── variables.tf
└── outputs.tf
General
- Use remote state with locking for team collaboration
- Never commit
.tfstatefiles or.tfvarswith secrets - Pin provider and module versions
- Use
terraform planbeforeapplyin CI/CD - Tag all resources consistently
- Use data sources to reference existing infrastructure
- Keep modules small and focused
.gitignore
*.tfstate
*.tfstate.*
*.tfvars
.terraform/
.terraform.lock.hcl
Resources
Cheat Sheets
- https://spacelift.io/blog/terraform-commands-cheat-sheet
- https://acloudguru.com/blog/engineering/the-ultimate-terraform-cheatsheet
- https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9
Documentation
- Terraform Documentation
- Terraform Registry - providers and modules
- OpenTofu Documentation
Learning
- Terraform: Up & Running by Yevgeniy Brikman
- HashiCorp Learn