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,boollist(type),set(type)map(type),object({...})tuple([type, ...])
Variable Precedence (lowest to highest)
- Environment variables (
TF_VAR_name) terraform.tfvarsfile*.auto.tfvarsfiles-varand-var-fileCLI 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_servernotws - 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
- Always use remote state for team environments
- Enable state locking (DynamoDB for S3 backend)
- Encrypt state files (sensitive data)
- 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 dependenciescount: Create multiple resourcesfor_each: Create resources from map/setprovider: Select specific providerlifecycle: 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
"Error acquiring state lock"
- Someone else running terraform
- Use
terraform force-unlock
"Resource already exists"
- Use
terraform import - Check for naming conflicts
- Use
"Invalid count argument"
- Count can't reference resource attributes
- Use
for_eachinstead
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
- Principle of Least Privilege for IAM
- Encrypt state files (S3 + KMS)
- Use variables for sensitive data
- Enable MFA for state operations
- Audit trail with CloudTrail/similar
- 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
- Understand state management deeply
- Know when to use count vs for_each
- Explain the terraform workflow
- Discuss real-world scenarios
- Mention best practices naturally
- Be ready to write simple configurations
- Understand provider ecosystem
- Know module design patterns
Remember: Terraform is declarative - you describe the desired state, not the steps to get there!