Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

Terraform

Infrastructure as Code tool for provisioning and managing cloud resources declaratively using HashiCorp Configuration Language (HCL).

Terraform

Infrastructure as Code tool for provisioning and managing cloud resources declaratively using HashiCorp Configuration Language (HCL).

Overview

Terraform enables you to define infrastructure in human-readable configuration files that can be versioned, reused, and shared. It supports multiple cloud providers (AWS, GCP, Azure, etc.) and on-premises infrastructure through a plugin-based architecture. Terraform maintains state to track resource mappings and metadata, enabling incremental changes and drift detection.

ApproveRejectFuture ChangesWrite HCL Configterraform initterraform planReview Changesterraform applyInfrastructureCreatedState UpdatedApproveRejectFuture ChangesWrite HCL Configterraform initterraform planReview Changesterraform applyInfrastructureCreatedState Updated

HCL Syntax Basics

HashiCorp Configuration Language (HCL) is Terraform's declarative language for defining infrastructure.

Key Concepts

  • Blocks - Containers for configuration (e.g., resource, variable, output)
  • Arguments - Assign values within blocks using name = value syntax
  • Expressions - Compute or reference values (literals, references, functions)
  • Comments - Single line # or //, multi-line /* */

Common Patterns

# Block structure
block_type "label_one" "label_two" {
  argument = "value"

  nested_block {
    nested_argument = "nested_value"
  }
}

# Data types
string_value    = "hello"
number_value    = 42
bool_value      = true
list_value      = ["a", "b", "c"]
map_value       = {
  key1 = "value1"
  key2 = "value2"
}

# String interpolation
message = "Hello, ${var.name}!"

# Heredoc syntax for multi-line strings
description = <<-EOT
  This is a multi-line
  string using heredoc
EOT

# Conditional expressions
instance_type = var.environment == "prod" ? "m5.large" : "t3.micro"

# For expressions
upper_names = [for name in var.names : upper(name)]
name_map    = {for name in var.names : name => upper(name)}

# Splat expressions
instance_ids = aws_instance.web[*].id

Examples

# Complete resource block example
resource "aws_instance" "web_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type

  tags = {
    Name        = "WebServer-${var.environment}"
    Environment = var.environment
    ManagedBy   = "Terraform"
  }

  # Dynamic block for multiple ingress rules
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.from_port
      to_port     = ingress.value.to_port
      protocol    = ingress.value.protocol
      cidr_blocks = ingress.value.cidr_blocks
    }
  }
}

Resources and Data Sources

Resources create and manage infrastructure objects; data sources fetch information about existing resources.

Key Concepts

  • Resources - Create, update, and delete infrastructure objects
  • Data Sources - Read-only queries to existing infrastructure
  • Meta-arguments - Special arguments like depends_on, count, for_each, provider, lifecycle
  • Implicit dependencies - Terraform automatically determines order from references
Data SourcesResourcesami_idzone_namesvpc_idsubnet_idaws_vpc.mainaws_subnet.publicaws_instance.webaws_ami.ubuntuaws_availability_zones.availableData SourcesResourcesami_idzone_namesvpc_idsubnet_idaws_vpc.mainaws_subnet.publicaws_instance.webaws_ami.ubuntuaws_availability_zones.available

Common Patterns

# Resource definition
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true

  tags = {
    Name = "main-vpc"
  }
}

# Data source definition
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]  # Canonical

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
  }
}

# Using data source in resource
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
  subnet_id     = aws_subnet.public.id
}

# Meta-arguments
resource "aws_instance" "server" {
  count = var.instance_count  # Create multiple instances

  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  tags = {
    Name = "Server-${count.index + 1}"
  }
}

# for_each with map
resource "aws_iam_user" "users" {
  for_each = toset(var.user_names)
  name     = each.value
}

# Lifecycle rules
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  lifecycle {
    create_before_destroy = true
    prevent_destroy       = false
    ignore_changes        = [tags]
  }
}

# Explicit dependencies
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  depends_on = [aws_iam_role_policy.web_policy]
}

Examples

# Complete infrastructure example
data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "main"
  }
}

resource "aws_subnet" "public" {
  count             = 2
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.${count.index + 1}.0/24"
  availability_zone = data.aws_availability_zones.available.names[count.index]

  tags = {
    Name = "public-${count.index + 1}"
  }
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Security group for web servers"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Variables and Outputs

Variables parameterise configurations; outputs expose values for use by other configurations or users.

Key Concepts

  • Input Variables - Parameters for Terraform configurations
  • Local Values - Named expressions for reuse within a module
  • Output Values - Return values from a module
  • Variable Precedence - Environment vars < terraform.tfvars < *.auto.tfvars < -var flag

Common Patterns

# Variable definition (variables.tf)
variable "region" {
  description = "AWS region for resources"
  type        = string
  default     = "eu-west-1"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t3.micro"

  validation {
    condition     = can(regex("^t3\\.", var.instance_type))
    error_message = "Instance type must be from the t3 family."
  }
}

variable "environment" {
  description = "Deployment environment"
  type        = string

  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "tags" {
  description = "Common tags for all resources"
  type        = map(string)
  default     = {}
}

variable "subnet_cidrs" {
  description = "CIDR blocks for subnets"
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
}

# Complex type
variable "ingress_rules" {
  description = "List of ingress rules"
  type = list(object({
    from_port   = number
    to_port     = number
    protocol    = string
    cidr_blocks = list(string)
  }))
  default = []
}

# Sensitive variable
variable "db_password" {
  description = "Database password"
  type        = string
  sensitive   = true
}

# Local values (locals.tf)
locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
    Project     = var.project_name
  }

  name_prefix = "${var.project_name}-${var.environment}"
}

# Output values (outputs.tf)
output "vpc_id" {
  description = "ID of the created VPC"
  value       = aws_vpc.main.id
}

output "instance_public_ips" {
  description = "Public IPs of all instances"
  value       = aws_instance.web[*].public_ip
}

output "db_connection_string" {
  description = "Database connection string"
  value       = "postgresql://${aws_db_instance.main.endpoint}/${aws_db_instance.main.db_name}"
  sensitive   = true
}

Examples

# terraform.tfvars
region       = "eu-west-2"
environment  = "prod"
instance_type = "t3.small"

tags = {
  Team    = "Platform"
  CostCentre = "12345"
}

# Using variables in resources
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-web"
  })
}

# Referencing outputs from another module
module "vpc" {
  source = "./modules/vpc"
}

resource "aws_instance" "web" {
  subnet_id = module.vpc.public_subnet_ids[0]
}

State Management

Terraform state tracks the mapping between configuration and real-world resources.

Key Concepts

  • State File - JSON file storing resource metadata and mappings
  • Remote State - Store state in shared location (S3, GCS, Azure Blob, Terraform Cloud)
  • State Locking - Prevent concurrent modifications
  • State Isolation - Separate state per environment/component
Remote State ArchitectureTerraform CLIState BackendS3 BucketDynamoDB LockTeam Member 1Team Member 2CI/CD PipelineRemote State ArchitectureTerraform CLIState BackendS3 BucketDynamoDB LockTeam Member 1Team Member 2CI/CD Pipeline

Common Patterns

# Remote state with S3 backend (backend.tf)
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "eu-west-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# GCS backend
terraform {
  backend "gcs" {
    bucket = "my-terraform-state"
    prefix = "prod"
  }
}

# Azure backend
terraform {
  backend "azurerm" {
    resource_group_name  = "tfstate"
    storage_account_name = "tfstate12345"
    container_name       = "tfstate"
    key                  = "prod.terraform.tfstate"
  }
}

# Terraform Cloud backend
terraform {
  cloud {
    organization = "my-org"  # Note: US spelling required
    workspaces {
      name = "my-workspace"
    }
  }
}

# Reading remote state from another configuration
data "terraform_remote_state" "vpc" {
  backend = "s3"
  config = {
    bucket = "my-terraform-state"
    key    = "vpc/terraform.tfstate"
    region = "eu-west-1"
  }
}

# Using remote state data
resource "aws_instance" "web" {
  subnet_id = data.terraform_remote_state.vpc.outputs.public_subnet_id
}

State Commands

# List resources in state
terraform state list

# Show specific resource
terraform state show aws_instance.web

# Move resource (rename)
terraform state mv aws_instance.web aws_instance.web_server

# Remove resource from state (without destroying)
terraform state rm aws_instance.web

# Import existing resource into state
terraform import aws_instance.web i-1234567890abcdef0

# Pull remote state to local file
terraform state pull > terraform.tfstate.backup

# Push local state to remote
terraform state push terraform.tfstate

# Replace provider in state
terraform state replace-provider hashicorp/aws registry.terraform.io/hashicorp/aws

# Force unlock state (use with caution)
terraform force-unlock LOCK_ID

Modules

Modules are reusable, self-contained packages of Terraform configuration.

Key Concepts

  • Root Module - Top-level configuration directory
  • Child Modules - Modules called by other modules
  • Module Sources - Local paths, Git repos, Terraform Registry
  • Module Versioning - Pin versions for stability
Module StructureRoot ModuleVPC ModuleCompute ModuleDatabase Modulevariables.tfmain.tfoutputs.tfvariables.tfmain.tfoutputs.tfModule StructureRoot ModuleVPC ModuleCompute ModuleDatabase Modulevariables.tfmain.tfoutputs.tfvariables.tfmain.tfoutputs.tf

Common Patterns

# Calling a local module
module "vpc" {
  source = "./modules/vpc"

  vpc_cidr     = "10.0.0.0/16"
  environment  = var.environment
  project_name = var.project_name
}

# Calling a module from Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["eu-west-1a", "eu-west-1b", "eu-west-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
  single_nat_gateway = true
}

# Module from Git repository
module "vpc" {
  source = "git::https://github.com/org/terraform-modules.git//vpc?ref=v1.2.0"

  vpc_cidr = "10.0.0.0/16"
}

# Module from S3
module "vpc" {
  source = "s3::https://s3-eu-west-1.amazonaws.com/bucket/vpc.zip"
}

# Passing outputs between modules
module "vpc" {
  source = "./modules/vpc"
}

module "compute" {
  source = "./modules/compute"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnet_ids
}

# Module with count
module "web_cluster" {
  source   = "./modules/web-cluster"
  count    = var.create_cluster ? 1 : 0

  cluster_name = "web"
}

# Module with for_each
module "buckets" {
  source   = "./modules/s3-bucket"
  for_each = toset(["logs", "data", "backups"])

  bucket_name = "${var.project}-${each.value}"
}

Module Structure Example

# modules/vpc/variables.tf
variable "vpc_cidr" {
  description = "CIDR block for VPC"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
}

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true

  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
  }
}

resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, 1)
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.environment}-public"
  }
}

# modules/vpc/outputs.tf
output "vpc_id" {
  description = "ID of the VPC"
  value       = aws_vpc.main.id
}

output "public_subnet_id" {
  description = "ID of the public subnet"
  value       = aws_subnet.public.id
}

Workspaces

Workspaces enable multiple state files for the same configuration.

Key Concepts

  • Default Workspace - Created automatically, named "default"
  • Named Workspaces - Separate state for different environments
  • State Isolation - Each workspace has its own state file
  • Workspace Interpolation - Access current workspace via terraform.workspace

Common Commands

# List workspaces
terraform workspace list

# Create new workspace
terraform workspace new staging

# Select workspace
terraform workspace select prod

# Show current workspace
terraform workspace show

# Delete workspace
terraform workspace delete staging

Common Patterns

# Using workspace in configuration
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"

  tags = {
    Name        = "web-${terraform.workspace}"
    Environment = terraform.workspace
  }
}

# Workspace-specific variables
locals {
  environment_config = {
    dev = {
      instance_type = "t3.micro"
      instance_count = 1
    }
    staging = {
      instance_type = "t3.small"
      instance_count = 2
    }
    prod = {
      instance_type = "t3.large"
      instance_count = 3
    }
  }

  config = local.environment_config[terraform.workspace]
}

resource "aws_instance" "web" {
  count         = local.config.instance_count
  ami           = data.aws_ami.ubuntu.id
  instance_type = local.config.instance_type
}

# Backend configuration with workspaces
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "app/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "terraform-locks"

    workspace_key_prefix = "workspaces"
  }
}
# State stored at: workspaces/{workspace}/app/terraform.tfstate

Providers and Provisioners

Providers interact with cloud platforms and services; provisioners execute actions on resources.

Key Concepts

  • Providers - Plugins that interact with APIs (AWS, GCP, Azure, etc.)
  • Provider Configuration - Authentication, region, and other settings
  • Provider Aliases - Multiple configurations for the same provider
  • Provisioners - Last resort for bootstrapping (prefer cloud-init/user data)

Common Patterns

# Provider configuration
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 7.0"
    }
  }

  required_version = ">= 1.5.0"
}

# AWS provider
provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      ManagedBy = "Terraform"
      Project   = var.project_name
    }
  }
}

# Provider alias for multi-region
provider "aws" {
  alias  = "us_east"
  region = "us-east-1"
}

provider "aws" {
  alias  = "eu_west"
  region = "eu-west-1"
}

# Using provider alias
resource "aws_instance" "us_web" {
  provider      = aws.us_east
  ami           = "ami-12345678"
  instance_type = "t3.micro"
}

resource "aws_instance" "eu_web" {
  provider      = aws.eu_west
  ami           = "ami-87654321"
  instance_type = "t3.micro"
}

# Assume role configuration
provider "aws" {
  region = "eu-west-1"

  assume_role {
    role_arn     = "arn:aws:iam::123456789012:role/TerraformRole"
    session_name = "TerraformSession"
  }
}

# Provisioners (use sparingly)
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  # File provisioner
  provisioner "file" {
    source      = "scripts/setup.sh"
    destination = "/tmp/setup.sh"

    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/id_rsa")
      host        = self.public_ip
    }
  }

  # Remote-exec provisioner
  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/setup.sh",
      "/tmp/setup.sh"
    ]

    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/id_rsa")
      host        = self.public_ip
    }
  }

  # Local-exec provisioner
  provisioner "local-exec" {
    command = "echo ${self.private_ip} >> private_ips.txt"
  }

  # Destroy-time provisioner
  provisioner "local-exec" {
    when    = destroy
    command = "echo 'Instance ${self.id} destroyed' >> destroy.log"
  }
}

# Prefer user_data over provisioners
resource "aws_instance" "web" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  user_data = <<-EOF
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    systemctl start nginx
  EOF
}

Common Commands

Essential Terraform CLI commands for daily operations.

Initialisation and Planning

# Initialise working directory
terraform init

# Reinitialise and upgrade providers
terraform init -upgrade

# Initialise with backend configuration
terraform init -backend-config="bucket=my-state-bucket"

# Create execution plan
terraform plan

# Save plan to file
terraform plan -out=tfplan

# Plan for specific target
terraform plan -target=aws_instance.web

# Plan with variable
terraform plan -var="environment=prod"

# Plan with variable file
terraform plan -var-file="prod.tfvars"

# Show plan in JSON format
terraform plan -json

Apply and Destroy

# Apply changes
terraform apply

# Apply saved plan
terraform apply tfplan

# Auto-approve (use in CI/CD)
terraform apply -auto-approve

# Apply specific target
terraform apply -target=aws_instance.web

# Replace a resource
terraform apply -replace=aws_instance.web

# Destroy all resources
terraform destroy

# Destroy specific resource
terraform destroy -target=aws_instance.web

# Destroy with auto-approve
terraform destroy -auto-approve

Validation and Formatting

# Validate configuration
terraform validate

# Format configuration
terraform fmt

# Format recursively
terraform fmt -recursive

# Check formatting (CI/CD)
terraform fmt -check

# Show formatting diff
terraform fmt -diff

Information and Debugging

# Show outputs
terraform output

# Show specific output
terraform output vpc_id

# Output in JSON
terraform output -json

# Show providers
terraform providers

# Show provider versions
terraform version

# Generate resource graph
terraform graph | dot -Tpng > graph.png

# Enable debug logging
export TF_LOG=DEBUG
terraform apply

# Console for expression testing
terraform console

Best Practices

Guidelines for maintainable, scalable, and secure Terraform configurations.

Project Structure

project/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   ├── staging/
│   └── prod/
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── README.md
│   ├── compute/
│   └── database/
└── README.md

Configuration Best Practices

# Pin provider versions
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"  # Allow minor/patch updates within 6.x
    }
  }
  required_version = ">= 1.5.0"
}

# Use meaningful resource names
resource "aws_instance" "web_server" {  # Good
  # ...
}

resource "aws_instance" "instance1" {   # Bad
  # ...
}

# Tag all resources consistently
locals {
  common_tags = {
    Environment = var.environment
    Project     = var.project_name
    ManagedBy   = "Terraform"
    Owner       = var.team_name
  }
}

resource "aws_instance" "web" {
  tags = merge(local.common_tags, {
    Name = "${var.project_name}-web"
    Role = "WebServer"
  })
}

# Use data sources for dynamic values
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

# Avoid hardcoding
resource "aws_s3_bucket" "logs" {
  bucket = "${var.project_name}-logs-${data.aws_caller_identity.current.account_id}"
}

# Use count/for_each for similar resources
resource "aws_subnet" "private" {
  for_each = var.private_subnets

  vpc_id     = aws_vpc.main.id
  cidr_block = each.value.cidr

  tags = {
    Name = each.key
  }
}

Security Best Practices

# Never commit secrets - use variables
variable "db_password" {
  type      = string
  sensitive = true
}

# Use IAM roles instead of access keys
provider "aws" {
  region = "eu-west-1"
  # No access keys - uses instance profile or environment
}

# Enable encryption
resource "aws_s3_bucket" "data" {
  bucket = "my-data-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
  }
}

# Use security groups with least privilege
resource "aws_security_group" "web" {
  name   = "web-sg"
  vpc_id = aws_vpc.main.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = var.allowed_cidrs  # Not 0.0.0.0/0
  }
}

State Management Best Practices

# Always use remote state with locking
terraform {
  backend "s3" {
    bucket         = "company-terraform-state"
    key            = "project/env/terraform.tfstate"
    region         = "eu-west-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}

# Separate state per environment/component
# dev:  project/dev/terraform.tfstate
# prod: project/prod/terraform.tfstate

# Use terraform_remote_state for cross-stack references
data "terraform_remote_state" "vpc" {
  backend = "s3"
  config = {
    bucket = "company-terraform-state"
    key    = "vpc/terraform.tfstate"
    region = "eu-west-1"
  }
}

Quick Reference

Command Description
terraform init Initialise working directory
terraform plan Preview changes
terraform apply Apply changes
terraform destroy Destroy resources
terraform fmt Format configuration
terraform validate Validate configuration
terraform output Show outputs
terraform state list List resources in state
terraform state show <resource> Show resource details
terraform import <resource> <id> Import existing resource
terraform workspace list List workspaces
terraform workspace select <name> Switch workspace
terraform console Interactive console
terraform graph Generate dependency graph

Variable Types

Type Example
string "hello"
number 42
bool true
list(type) ["a", "b"]
set(type) toset(["a", "b"])
map(type) { key = "value" }
object({...}) { name = string, age = number }
tuple([...]) [string, number]

Common Functions

Function Example Description
concat concat(list1, list2) Combine lists
merge merge(map1, map2) Combine maps
lookup lookup(map, key, default) Get map value
join join(",", list) Join list to string
split split(",", string) Split string to list
upper/lower upper("hello") Change case
format format("Hello, %s!", name) Format string
file file("path/to/file") Read file contents
templatefile templatefile("tpl", vars) Render template
cidrsubnet cidrsubnet("10.0.0.0/16", 8, 1) Calculate subnet
try try(expr, default) Error handling
can can(expr) Test expression

Common Issues and Solutions

Issue Cause Solution
Error: Provider not found Provider not initialised Run terraform init
Error: Resource already exists Resource exists outside Terraform Import with terraform import or delete external resource
Error: State lock Previous operation didn't release lock Check for running operations; use terraform force-unlock if stuck
Error: Invalid reference Typo or wrong resource name Check resource names and attributes in state
Error: Cycle detected Circular dependencies Use depends_on to break cycle or restructure
Drift detected Manual changes outside Terraform Run terraform apply to reconcile or update config
Module not found Wrong source path or version Verify source path; run terraform init -upgrade
Timeout errors Resource taking too long Increase timeouts block in resource
Permission denied Insufficient IAM permissions Check AWS/cloud provider permissions
Backend configuration changed Backend settings modified Run terraform init -reconfigure or -migrate-state

Debugging Tips

# Enable detailed logging
export TF_LOG=DEBUG
export TF_LOG_PATH=terraform.log

# Test expressions in console
terraform console
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"

# Visualise dependencies
terraform graph | dot -Tpng > graph.png

# Check state for issues
terraform state list
terraform state show aws_instance.web

# Refresh state without applying
terraform refresh  # Deprecated
terraform apply -refresh-only

# Taint resource for recreation
terraform taint aws_instance.web  # Deprecated
terraform apply -replace=aws_instance.web

Related Topics

The following topics would complement this Terraform cheatsheet:

  1. Ansible - Configuration management and application deployment; often used alongside Terraform for post-provisioning configuration
  2. AWS/GCP/Azure - Cloud provider-specific resources and services that Terraform manages
  3. Vault (HashiCorp) - Secrets management for storing and injecting sensitive values into Terraform configurations
  4. Packer - Machine image creation; images are then deployed via Terraform
  5. Terragrunt - Wrapper for Terraform that provides extra tools for working with multiple Terraform modules
  6. GitOps/CI-CD Patterns - Automating Terraform workflows in continuous delivery pipelines