LearnThatStack Ace your next interview
DevOps · Free

Terraform.
Interview cheat sheet.

Quick reference for Terraform - sectioned for fast scanning. Skim the part you're shaky on, walk in confident.

DevOps 21-section reference ~11 min read

Summary

Terraform is an open-source Infrastructure as Code (IaC) tool developed by HashiCorp that enables developers and DevOps teams to provision and manage infrastructure across multiple cloud providers using a declarative configuration language (HCL). Key features include immutable infrastructure, execution plans, resource graphs, and a plugin-based architecture supporting 200+ providers. Core concepts include providers, resources, data sources, variables, outputs, modules, and state management with remote backends. Essential for DevOps teams implementing infrastructure automation, multi-cloud strategies, and environment consistency with version-controlled infrastructure definitions.

1. Core Concepts

What is Terraform?

  • Infrastructure as Code (IaC) tool by HashiCorp
  • Declarative language to provision and manage infrastructure
  • Cloud-agnostic (works with AWS, Azure, GCP, etc.)
  • Uses HCL (HashiCorp Configuration Language)

Key Benefits

  • Reproducibility: Same config = same infrastructure
  • Version Control: Track infrastructure changes
  • Automation: Reduce manual errors
  • Multi-Cloud: Single tool for multiple providers

2. Terraform Workflow

1. Write → 2. Plan → 3. Apply → 4. Destroy

Essential Commands

terraform init      # Initialize working directory
terraform plan      # Preview changes
terraform apply     # Apply changes
terraform destroy   # Remove infrastructure
terraform validate  # Check syntax
terraform fmt       # Format code
terraform show      # Show current state

3. Configuration Basics

Provider Configuration

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

Resource Declaration

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = {
    Name = "WebServer"
  }
}

4. Variables

Input Variables

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

# Usage
instance_type = var.instance_type

Variable Types

  • string, number, bool
  • list(type), set(type)
  • map(type), object({...})
  • tuple([type, ...])

Variable Precedence (lowest to highest)

  1. Environment variables (TF_VAR_name)
  2. terraform.tfvars file
  3. *.auto.tfvars files
  4. -var and -var-file CLI options

5. Outputs

output "instance_ip" {
  value       = aws_instance.web.public_ip
  description = "Public IP of instance"
  sensitive   = false
}

6. Data Sources

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-*"]
  }
}

# Usage
ami = data.aws_ami.ubuntu.id

7. State Management

State File

  • Stores infrastructure metadata
  • Maps real resources to configuration
  • Default: terraform.tfstate (local)

Remote State

terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "prod/terraform.tfstate"
    region = "us-east-1"
  }
}

State Commands

terraform state list              # List resources
terraform state show <resource>   # Show resource details
terraform state mv                # Move/rename resources
terraform state rm                # Remove from state
terraform import                  # Import existing resources

8. Modules

Module Structure

modules/
├── vpc/
│   ├── main.tf
│   ├── variables.tf
│   └── outputs.tf

Using Modules

module "vpc" {
  source = "./modules/vpc"
  
  cidr_block = "10.0.0.0/16"
  name       = "production"
}

# Access module outputs
subnet_id = module.vpc.public_subnet_id

9. Provisioners (Use Sparingly!)

resource "aws_instance" "web" {
  # ...
  
  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y nginx"
    ]
  }
}

10. Functions & Expressions

Common Functions

# String Functions
upper("hello")                    # "HELLO"
lower("HELLO")                    # "hello"
format("Hello, %s!", "World")     # "Hello, World!"

# Collection Functions
length(["a", "b", "c"])          # 3
concat(["a"], ["b"])             # ["a", "b"]
merge({a=1}, {b=2})              # {a=1, b=2}

# Filesystem
file("script.sh")                # Read file content
templatefile("config.tpl", {})   # Template rendering

# Type Conversion
tostring(123)                    # "123"
tonumber("123")                  # 123
tobool("true")                   # true

Conditional Expressions

instance_type = var.env == "prod" ? "t2.large" : "t2.micro"

Loops

# for_each
resource "aws_instance" "web" {
  for_each = toset(["web1", "web2"])
  
  ami           = "ami-123456"
  instance_type = "t2.micro"
  
  tags = {
    Name = each.key
  }
}

# count
resource "aws_instance" "web" {
  count = 3
  
  ami           = "ami-123456"
  instance_type = "t2.micro"
  
  tags = {
    Name = "web-${count.index}"
  }
}

# Dynamic Blocks
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
  }
}

11. Dependencies

Implicit Dependencies

resource "aws_instance" "web" {
  subnet_id = aws_subnet.public.id  # Implicit dependency
}

Explicit Dependencies

resource "aws_instance" "web" {
  # ...
  depends_on = [aws_iam_role_policy.example]
}

12. Workspaces

terraform workspace new dev       # Create workspace
terraform workspace list          # List workspaces
terraform workspace select prod   # Switch workspace
terraform workspace show          # Current workspace

Usage in configuration:

instance_count = terraform.workspace == "prod" ? 5 : 1

13. Best Practices

Code Organization

project/
├── main.tf           # Main configuration
├── variables.tf      # Variable declarations
├── outputs.tf        # Output values
├── terraform.tfvars  # Variable values
├── versions.tf       # Provider versions
└── modules/          # Reusable modules

Naming Conventions

  • Use lowercase and underscores
  • Be descriptive: web_server not ws
  • Include environment: prod_web_server

Version Constraints

terraform {
  required_version = ">= 1.0"
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # Allow 5.x, not 6.0
    }
  }
}

State Management Best Practices

  1. Always use remote state for team environments
  2. Enable state locking (DynamoDB for S3 backend)
  3. Encrypt state files (sensitive data)
  4. Never commit state files to version control

14. Advanced Topics

Resource Lifecycle

resource "aws_instance" "web" {
  # ...
  
  lifecycle {
    create_before_destroy = true
    prevent_destroy       = true
    ignore_changes        = [tags]
  }
}

Meta-Arguments

  • depends_on: Explicit dependencies
  • count: Create multiple resources
  • for_each: Create resources from map/set
  • provider: Select specific provider
  • lifecycle: Customize resource lifecycle

Local Values

locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "Terraform"
  }
}

# Usage
tags = merge(local.common_tags, {
  Name = "WebServer"
})

15. Common Interview Topics

Terraform vs Other IaC Tools Comparison

Feature Terraform CloudFormation ARM Templates Pulumi Ansible
Language HCL (declarative) JSON/YAML JSON Real languages YAML (imperative)
Cloud Support Multi-cloud AWS only Azure only Multi-cloud Multi-cloud
State Management Explicit state file AWS managed Azure managed Language runtime Stateless
Provider Ecosystem 200+ providers AWS services Azure services Growing 3000+ modules
Execution Model Plan → Apply Stack operations Template deployment Language runtime Playbook execution
Cost Open source Free (AWS usage) Free (Azure usage) Free + commercial Open source
Learning Curve Moderate Easy Moderate Language dependent Easy to moderate

Advanced State Management Patterns

Remote State with Locking:

terraform {
  backend "s3" {
    bucket         = "terraform-state-bucket"
    key            = "env/prod/terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
    versioning     = true
  }
}

State Import Strategies:

# Import existing AWS resources
terraform import aws_instance.web i-1234567890abcdef0
terraform import aws_vpc.main vpc-12345678
terraform import aws_security_group.web sg-12345678

# Import complex resources with dependencies
terraform import 'aws_route53_record.example["www"]' Z123456789_www.example.com_CNAME
terraform import 'module.vpc.aws_subnet.private[0]' subnet-12345678

State Migration Best Practices:

# Backup current state
terraform state pull > backup-$(date +%Y%m%d).tfstate

# Move resources between states
terraform state mv aws_instance.web module.compute.aws_instance.web

# Remove resources from state (keeps actual resource)
terraform state rm aws_instance.old_web

# Replace provider (useful for fork/rename situations)
terraform state replace-provider registry.terraform.io/hashicorp/aws \
  registry.terraform.io/hashicorp/aws

Enterprise Module Patterns

Multi-Environment Module Structure:

modules/
├── aws-infrastructure/
│   ├── networking/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── versions.tf
│   ├── compute/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── outputs.tf
│   │   └── versions.tf
│   └── security/
├── environments/
│   ├── dev/
│   │   ├── main.tf
│   │   ├── terraform.tfvars
│   │   └── backend.tf
│   ├── staging/
│   └── prod/
└── shared/
    ├── data-sources/
    └── policies/

Advanced Module Composition:

# Root module combining multiple child modules
module "networking" {
  source = "./modules/networking"
  
  vpc_cidr             = var.vpc_cidr
  availability_zones   = var.availability_zones
  enable_nat_gateway   = var.enable_nat_gateway
  enable_dns_hostnames = true
  
  tags = local.common_tags
}

module "security" {
  source = "./modules/security"
  
  vpc_id          = module.networking.vpc_id
  private_subnets = module.networking.private_subnet_ids
  public_subnets  = module.networking.public_subnet_ids
  
  allowed_cidr_blocks = var.allowed_cidr_blocks
  
  tags = local.common_tags
}

module "compute" {
  source = "./modules/compute"
  
  vpc_id              = module.networking.vpc_id
  subnet_ids          = module.networking.private_subnet_ids
  security_group_ids  = module.security.instance_security_group_ids
  key_name           = var.key_name
  instance_type      = var.instance_type
  
  user_data = templatefile("${path.module}/templates/user_data.sh", {
    region = var.aws_region
  })
  
  tags = local.common_tags
}

Module Versioning and Publishing:

# Using module from Terraform Registry
module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"
  
  name = "production-vpc"
  cidr = "10.0.0.0/16"
  
  azs             = ["us-east-1a", "us-east-1b", "us-east-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
  enable_vpn_gateway = true
}

# Using private module from Git
module "custom_app" {
  source = "git::https://github.com/company/terraform-modules.git//aws/application?ref=v2.1.0"
  
  application_name = "my-app"
  environment     = "production"
}

Advanced Configuration Techniques

Complex Data Transformations:

locals {
  # Transform list of objects into map
  security_groups_map = {
    for sg in var.security_groups :
    sg.name => sg
  }
  
  # Create subnet mappings
  subnet_mappings = {
    for idx, subnet in aws_subnet.private :
    data.aws_availability_zones.available.names[idx] => subnet.id
  }
  
  # Conditional resource creation
  create_database = var.environment == "prod" || var.enable_database
  
  # Complex tagging strategy
  tags = merge(
    var.common_tags,
    {
      Environment = var.environment
      ManagedBy   = "Terraform"
      CreatedDate = timestamp()
    }
  )
}

# Dynamic blocks for complex configurations
resource "aws_security_group" "web" {
  name_prefix = "${var.name_prefix}-web-"
  vpc_id      = var.vpc_id
  
  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      description     = lookup(ingress.value, "description", null)
      from_port       = ingress.value.from_port
      to_port         = ingress.value.to_port
      protocol        = ingress.value.protocol
      cidr_blocks     = lookup(ingress.value, "cidr_blocks", null)
      security_groups = lookup(ingress.value, "security_groups", null)
    }
  }
  
  dynamic "egress" {
    for_each = var.egress_rules
    content {
      description = lookup(egress.value, "description", null)
      from_port   = egress.value.from_port
      to_port     = egress.value.to_port
      protocol    = egress.value.protocol
      cidr_blocks = lookup(egress.value, "cidr_blocks", null)
    }
  }
  
  tags = local.tags
}

Security and Secrets Management

HashiCorp Vault Integration:

# Configure Vault provider
provider "vault" {
  address = "https://vault.company.com"
}

# Read secrets from Vault
data "vault_generic_secret" "database" {
  path = "secret/myapp/database"
}

resource "aws_db_instance" "main" {
  identifier = var.db_identifier
  
  username = data.vault_generic_secret.database.data["username"]
  password = data.vault_generic_secret.database.data["password"]
  
  # Other configuration...
}

# Write secrets to Vault
resource "vault_generic_secret" "app_config" {
  path = "secret/myapp/config"
  
  data_json = jsonencode({
    api_key = random_password.api_key.result
    jwt_secret = random_password.jwt_secret.result
  })
}

AWS Secrets Manager Integration:

# Create secret in AWS Secrets Manager
resource "aws_secretsmanager_secret" "app_secrets" {
  name = "${var.app_name}-secrets"
  
  tags = local.tags
}

resource "aws_secretsmanager_secret_version" "app_secrets" {
  secret_id = aws_secretsmanager_secret.app_secrets.id
  secret_string = jsonencode({
    database_password = random_password.db_password.result
    api_key          = random_id.api_key.hex
  })
}

# Reference secrets in other resources
data "aws_secretsmanager_secret" "app_secrets" {
  name = aws_secretsmanager_secret.app_secrets.name
}

data "aws_secretsmanager_secret_version" "app_secrets" {
  secret_id = data.aws_secretsmanager_secret.app_secrets.id
}

locals {
  secrets = jsondecode(data.aws_secretsmanager_secret_version.app_secrets.secret_string)
}

Advanced Deployment Strategies

Blue-Green Deployment Pattern:

variable "deployment_color" {
  description = "Deployment color (blue or green)"
  type        = string
  default     = "blue"
  
  validation {
    condition     = contains(["blue", "green"], var.deployment_color)
    error_message = "Deployment color must be either 'blue' or 'green'."
  }
}

resource "aws_launch_template" "app" {
  name_prefix   = "${var.app_name}-${var.deployment_color}-"
  image_id      = var.ami_id
  instance_type = var.instance_type
  
  vpc_security_group_ids = [aws_security_group.app.id]
  
  user_data = base64encode(templatefile("${path.module}/user_data.sh", {
    app_version = var.app_version
    color      = var.deployment_color
  }))
  
  tag_specifications {
    resource_type = "instance"
    tags = merge(local.tags, {
      Color = var.deployment_color
    })
  }
  
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_autoscaling_group" "app" {
  name = "${var.app_name}-${var.deployment_color}"
  
  vpc_zone_identifier = var.subnet_ids
  target_group_arns   = var.target_group_arns
  health_check_type   = "ELB"
  
  min_size         = var.min_size
  max_size         = var.max_size
  desired_capacity = var.desired_capacity
  
  launch_template {
    id      = aws_launch_template.app.id
    version = "$Latest"
  }
  
  # Wait for instances to be healthy before considering deployment successful
  wait_for_capacity_timeout = "10m"
  
  lifecycle {
    create_before_destroy = true
    ignore_changes       = [desired_capacity]
  }
  
  tag {
    key                 = "Name"
    value               = "${var.app_name}-${var.deployment_color}"
    propagate_at_launch = true
  }
  
  tag {
    key                 = "Color"
    value               = var.deployment_color
    propagate_at_launch = true
  }
}

# Traffic switching logic
resource "aws_lb_listener_rule" "app" {
  listener_arn = var.listener_arn
  priority     = 100
  
  action {
    type             = "forward"
    target_group_arn = var.deployment_color == "blue" ? var.blue_target_group_arn : var.green_target_group_arn
  }
  
  condition {
    host_header {
      values = [var.domain_name]
    }
  }
}

Canary Deployment with Weighted Routing:

resource "aws_lb_listener_rule" "canary" {
  listener_arn = aws_lb_listener.main.arn
  priority     = 50
  
  action {
    type = "weighted-forward"
    
    forward {
      target_group {
        arn    = aws_lb_target_group.stable.arn
        weight = var.stable_weight
      }
      
      target_group {
        arn    = aws_lb_target_group.canary.arn
        weight = var.canary_weight
      }
      
      stickiness {
        enabled  = false
        duration = 1
      }
    }
  }
  
  condition {
    path_pattern {
      values = ["/*"]
    }
  }
}

# Gradual traffic shift
variable "canary_weight" {
  description = "Percentage of traffic to canary deployment"
  type        = number
  default     = 10
  
  validation {
    condition     = var.canary_weight >= 0 && var.canary_weight <= 100
    error_message = "Canary weight must be between 0 and 100."
  }
}

variable "stable_weight" {
  description = "Percentage of traffic to stable deployment"
  type        = number
  default     = 90
  
  validation {
    condition     = var.stable_weight >= 0 && var.stable_weight <= 100
    error_message = "Stable weight must be between 0 and 100."
  }
}

Performance and Optimization Patterns

Resource Dependency Optimization:

# Explicit dependency management
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true
  
  tags = merge(local.tags, {
    Name = "${var.name_prefix}-vpc"
  })
}

# Parallel resource creation where possible
resource "aws_subnet" "private" {
  count = length(var.availability_zones)
  
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + 1)
  availability_zone = var.availability_zones[count.index]
  
  tags = merge(local.tags, {
    Name = "${var.name_prefix}-private-${count.index + 1}"
    Type = "private"
  })
}

resource "aws_subnet" "public" {
  count = length(var.availability_zones)
  
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 101)
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true
  
  tags = merge(local.tags, {
    Name = "${var.name_prefix}-public-${count.index + 1}"
    Type = "public"
  })
}

# Resource creation with minimal dependencies
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  
  tags = merge(local.tags, {
    Name = "${var.name_prefix}-igw"
  })
}

# Optimize for_each vs count usage
resource "aws_route_table" "private" {
  for_each = toset(var.availability_zones)
  
  vpc_id = aws_vpc.main.id
  
  tags = merge(local.tags, {
    Name = "${var.name_prefix}-private-rt-${each.key}"
  })
}

Provider Configuration Optimization:

# Configure provider aliases for multi-region deployments
provider "aws" {
  alias  = "primary"
  region = var.primary_region
}

provider "aws" {
  alias  = "secondary"
  region = var.secondary_region
}

# Use specific providers for resources
resource "aws_s3_bucket" "primary" {
  provider = aws.primary
  bucket   = "${var.bucket_name}-primary"
}

resource "aws_s3_bucket" "secondary" {
  provider = aws.secondary
  bucket   = "${var.bucket_name}-secondary"
}

# Cross-region replication
resource "aws_s3_bucket_replication_configuration" "replication" {
  provider   = aws.primary
  depends_on = [aws_s3_bucket_versioning.primary]
  
  role   = aws_iam_role.replication.arn
  bucket = aws_s3_bucket.primary.id
  
  rule {
    id     = "replicate-to-secondary"
    status = "Enabled"
    
    destination {
      bucket        = aws_s3_bucket.secondary.arn
      storage_class = "STANDARD_IA"
    }
  }
}

16. Debugging & Troubleshooting

Debug Levels

export TF_LOG=DEBUG  # DEBUG, INFO, WARN, ERROR
export TF_LOG_PATH=terraform.log

Common Issues & Solutions

  1. "Error acquiring state lock"

    • Someone else running terraform
    • Use terraform force-unlock
  2. "Resource already exists"

    • Use terraform import
    • Check for naming conflicts
  3. "Invalid count argument"

    • Count can't reference resource attributes
    • Use for_each instead

17. Performance Optimization

Parallel Execution

terraform apply -parallelism=10  # Default: 10

Targeted Operations

terraform apply -target=aws_instance.web
terraform destroy -target=aws_instance.web

18. Security Best Practices

  1. Principle of Least Privilege for IAM
  2. Encrypt state files (S3 + KMS)
  3. Use variables for sensitive data
  4. Enable MFA for state operations
  5. Audit trail with CloudTrail/similar
  6. Policy as Code with Sentinel/OPA

19. CI/CD Integration

Example Pipeline

stages:
  - validate
  - plan
  - apply

validate:
  script:
    - terraform fmt -check
    - terraform validate

plan:
  script:
    - terraform plan -out=tfplan

apply:
  script:
    - terraform apply tfplan
  when: manual

20. Quick Reference Card

Must-Know Concepts

  • Providers: Plugins for cloud/service APIs
  • Resources: Infrastructure components
  • Data Sources: Query existing infrastructure
  • Variables: Input parameters
  • Outputs: Export values
  • Modules: Reusable configurations
  • State: Current infrastructure snapshot
  • Backend: Where state is stored

Command Cheat Sheet

# Initialization
terraform init -upgrade     # Upgrade providers

# Planning
terraform plan -out=plan    # Save plan
terraform show plan         # View saved plan

# Applying
terraform apply -auto-approve  # Skip confirmation
terraform apply plan           # Apply saved plan

# State Management
terraform state pull > backup.tfstate  # Backup
terraform refresh                      # Update state

# Import/Export
terraform import aws_instance.web i-123456
terraform output -json > outputs.json

Interview Success Tips

  1. Understand state management deeply
  2. Know when to use count vs for_each
  3. Explain the terraform workflow
  4. Discuss real-world scenarios
  5. Mention best practices naturally
  6. Be ready to write simple configurations
  7. Understand provider ecosystem
  8. Know module design patterns

Remember: Terraform is declarative - you describe the desired state, not the steps to get there!

Found this useful? Pass it on.
Pro · $10/mo

The sheet is free. Pro goes deeper.

Pro opens the full question library behind every sheet, every refresher and a monthly AI allowance. One subscription, all formats.

Full question library All refreshers Cancel anytime