Infrastructure as Code tool for provisioning and managing cloud infrastructure declaratively. Commonly used with AWS, GCP, and Azure.
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