Terraform

Infrastructure as Code syntax, configuration blocks, and common workflow commands.

cli
terraformiacinfrastructuredevopscloud

Basic Configuration

# Provider configuration
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-west-2"              # AWS region
}

Resources

# Create an S3 bucket
resource "aws_s3_bucket" "example" {
  bucket = "my-unique-bucket-name"  # Bucket name (must be globally unique)
  
  tags = {
    Name        = "My bucket"
    Environment = "Dev"
  }
}

# EC2 instance
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"  # Amazon Machine Image ID
  instance_type = "t2.micro"                # Instance size
  
  tags = {
    Name = "WebServer"
  }
}

# Reference another resource's attribute
resource "aws_eip" "web" {
  instance = aws_instance.web.id    # Reference EC2 instance ID
  domain   = "vpc"
}

Variables

# Input variable declaration
variable "region" {
  description = "AWS region to deploy resources"
  type        = string
  default     = "us-west-2"         # Default value (optional)
}

variable "instance_count" {
  description = "Number of instances to create"
  type        = number
  default     = 1
}

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

# Using variables
resource "aws_instance" "app" {
  count         = var.instance_count    # Reference variable
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = var.tags                       # Use map variable
}

Outputs

# Output values (displayed after apply)
output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.web.id     # Export resource attribute
}

output "instance_public_ip" {
  description = "Public IP address"
  value       = aws_instance.web.public_ip
}

output "bucket_arn" {
  description = "ARN of the S3 bucket"
  value       = aws_s3_bucket.example.arn
  sensitive   = false                   # Mark if output contains secrets
}

Data Sources

# Query existing resources (read-only)
data "aws_ami" "ubuntu" {
  most_recent = true                    # Get latest AMI
  owners      = ["099720109477"]        # Canonical's AWS account ID
  
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-*-22.04-amd64-server-*"]
  }
}

# Use data source in resource
resource "aws_instance" "ubuntu_server" {
  ami           = data.aws_ami.ubuntu.id    # Reference data source
  instance_type = "t2.micro"
}

# Get current AWS account info
data "aws_caller_identity" "current" {}

output "account_id" {
  value = data.aws_caller_identity.current.account_id
}

Modules

# Call a reusable module
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"  # Module source
  version = "5.0.0"                          # Pin module version
  
  name = "my-vpc"
  cidr = "10.0.0.0/16"
  
  azs             = ["us-west-2a", "us-west-2b"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
  
  enable_nat_gateway = true
  enable_vpn_gateway = false
  
  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

# Reference module outputs
resource "aws_instance" "app" {
  subnet_id = module.vpc.private_subnets[0]  # Use module output
  # ... other configuration
}

Conditionals and Loops

# Count for multiple resources
resource "aws_instance" "server" {
  count         = 3                     # Create 3 instances
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = {
    Name = "Server-${count.index}"      # Use count.index (0, 1, 2)
  }
}

# for_each for dynamic resources
variable "users" {
  default = ["alice", "bob", "charlie"]
}

resource "aws_iam_user" "example" {
  for_each = toset(var.users)           # Convert list to set
  name     = each.key                   # Current item value
}

# Conditional expression
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.environment == "prod" ? "t3.large" : "t2.micro"
}

# Dynamic blocks
resource "aws_security_group" "example" {
  name = "example"
  
  dynamic "ingress" {
    for_each = var.service_ports        # Iterate over collection
    content {
      from_port   = ingress.value
      to_port     = ingress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

Local Values

# Define local computed values
locals {
  environment = "production"
  common_tags = {
    Environment = local.environment
    ManagedBy   = "Terraform"
    Project     = var.project_name
  }
  
  # Computed values
  full_name = "${var.prefix}-${var.name}-${var.suffix}"
}

# Use locals in resources
resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = merge(
    local.common_tags,                  # Spread common tags
    {
      Name = local.full_name            # Add specific tag
    }
  )
}

Backend Configuration

# Remote state storage
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"   # State file path
    region         = "us-west-2"
    encrypt        = true                       # Enable encryption
    dynamodb_table = "terraform-lock"           # State locking table
  }
}

# Remote state data source (read state from another workspace)
data "terraform_remote_state" "network" {
  backend = "s3"
  config = {
    bucket = "my-terraform-state"
    key    = "network/terraform.tfstate"
    region = "us-west-2"
  }
}

# Use remote state outputs
resource "aws_instance" "app" {
  subnet_id = data.terraform_remote_state.network.outputs.subnet_id
}

Common Commands

# Initialize working directory (download providers, modules)
terraform init

# Preview changes without applying
terraform plan

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

# Apply changes (creates/updates infrastructure)
terraform apply

# Apply without confirmation prompt
terraform apply -auto-approve

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

# Destroy all resources
terraform destroy

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

# Format code to canonical style
terraform fmt

# Validate configuration syntax
terraform validate

# Show current state
terraform show

# List resources in state
terraform state list

# Show specific resource details
terraform state show aws_instance.web

# Remove resource from state (doesn't destroy)
terraform state rm aws_instance.web

# Import existing infrastructure
terraform import aws_instance.web i-1234567890abcdef0

# View available outputs
terraform output

# Get specific output value
terraform output instance_id

# Create workspace (environment isolation)
terraform workspace new staging

# List workspaces
terraform workspace list

# Switch workspace
terraform workspace select production

# Refresh state from real infrastructure
terraform refresh

# Generate dependency graph
terraform graph | dot -Tsvg > graph.svg

Variable Files

# terraform.tfvars (automatically loaded)
region         = "us-east-1"
instance_count = 3
environment    = "production"

tags = {
  Environment = "Production"
  Project     = "MyApp"
  ManagedBy   = "Terraform"
}

# prod.tfvars (load with -var-file flag)
instance_type = "t3.large"
enable_monitoring = true

String Interpolation

# Variable interpolation
resource "aws_instance" "example" {
  tags = {
    Name = "server-${var.environment}"          # String interpolation
    ID   = "${var.project}-${var.environment}"  # Combine multiple vars
  }
}

# Function calls in strings
locals {
  uppercase_name = upper(var.name)              # Call function
  formatted_date = formatdate("YYYY-MM-DD", timestamp())
}

# Conditional strings
output "message" {
  value = var.enabled ? "Feature enabled" : "Feature disabled"
}

Functions

# Common built-in functions
locals {
  # String functions
  upper_name    = upper("hello")                    # "HELLO"
  lower_name    = lower("WORLD")                    # "world"
  title_name    = title("hello world")              # "Hello World"
  trimmed       = trimspace("  hello  ")            # "hello"
  replaced      = replace("hello", "l", "w")        # "hewwo"
  
  # Collection functions
  list_length   = length(["a", "b", "c"])           # 3
  joined        = join(", ", ["a", "b", "c"])       # "a, b, c"
  sorted        = sort(["c", "a", "b"])             # ["a", "b", "c"]
  distinct      = distinct([1, 2, 2, 3])            # [1, 2, 3]
  merged        = merge({a = 1}, {b = 2})           # {a = 1, b = 2}
  
  # Numeric functions
  max_value     = max(5, 12, 9)                     # 12
  min_value     = min(5, 12, 9)                     # 5
  
  # Type conversion
  to_string     = tostring(42)                      # "42"
  to_number     = tonumber("42")                    # 42
  to_list       = tolist(["a", "b"])                # List type
  to_set        = toset(["a", "b", "a"])            # Set (unique values)
  
  # File functions
  file_content  = file("${path.module}/config.txt") # Read file
  template_file = templatefile("user-data.sh", {    # Template with vars
    hostname = "web-server"
  })
}

Lifecycle Rules

resource "aws_instance" "example" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  lifecycle {
    create_before_destroy = true    # Create new before destroying old
    prevent_destroy       = true    # Prevent accidental deletion
    ignore_changes        = [        # Ignore changes to these attributes
      tags,
      user_data
    ]
  }
}

Provisioners

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  # Run command on remote resource
  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
    
    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/id_rsa")
      host        = self.public_ip
    }
  }
  
  # Run local command after creation
  provisioner "local-exec" {
    command = "echo ${self.public_ip} >> ip_list.txt"
  }
  
  # Run on destruction
  provisioner "local-exec" {
    when    = destroy
    command = "echo 'Resource destroyed' >> log.txt"
  }
}

Dependencies

# Implicit dependency (automatic)
resource "aws_eip" "example" {
  instance = aws_instance.web.id    # Terraform detects dependency
}

# Explicit dependency (manual)
resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  depends_on = [
    aws_iam_role_policy.example     # Force dependency order
  ]
}