Skip to content

Terraform

Overview

Terraform is an open-source Infrastructure as Code (IaC) tool that allows you to define, preview, and deploy cloud infrastructure using declarative configuration files.


Core Concepts

State Management

Terraform maintains a state file that tracks all managed resources and their current state.

# Local state
terraform.tfstate

# Remote state (recommended)
s3 bucket, Azure Storage, Terraform Cloud

Configuration Structure

project/
├── main.tf           # Main configuration
├── variables.tf      # Input variables
├── outputs.tf        # Output values
├── terraform.tfvars  # Variable values
├── backend.tf        # Backend configuration
└── modules/          # Reusable modules
    └── vpc/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

Basic Terraform Workflow

1. Initialize

# Initialize Terraform working directory
terraform init

# Initialize with specific backend
terraform init -backend=true

2. Plan

# Create execution plan
terraform plan

# Save plan to file
terraform plan -out=tfplan

# Show plan from file
terraform show tfplan

3. Apply

# Apply configuration
terraform apply

# Apply without confirmation
terraform apply -auto-approve

# Apply specific plan
terraform apply tfplan

4. Destroy

# Destroy infrastructure
terraform destroy

# Destroy without confirmation
terraform destroy -auto-approve

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

Terraform Configuration

Basic Example: AWS EC2

terraform {
  required_version = ">= 1.0"

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

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

provider "aws" {
  region = var.aws_region

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

# Create VPC
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true

  tags = {
    Name = "${var.project_name}-vpc"
  }
}

# Create subnet
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.subnet_cidr
  availability_zone       = var.az
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.project_name}-subnet"
  }
}

# Create security group
resource "aws_security_group" "allow_ssh" {
  name        = "allow_ssh"
  description = "Allow SSH inbound"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = var.allowed_cidrs
  }

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

  tags = {
    Name = "${var.project_name}-sg"
  }
}

# Create EC2 instance
resource "aws_instance" "web" {
  ami                    = data.aws_ami.ubuntu.id
  instance_type          = var.instance_type
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.allow_ssh.id]

  root_block_device {
    volume_type           = "gp3"
    volume_size           = 20
    delete_on_termination = true
  }

  tags = {
    Name = "${var.project_name}-server"
  }
}

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

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
}

Variables

Input Variables (variables.tf)

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"

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

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

variable "tags" {
  description = "Common tags"
  type        = map(string)
  default = {
    Project = "MyProject"
  }
}

variable "allowed_cidrs" {
  description = "Allowed CIDR blocks"
  type        = list(string)
  default     = ["0.0.0.0/0"]
  sensitive   = false
}

Variable Assignment (terraform.tfvars)

aws_region    = "us-west-2"
environment    = "prod"
instance_count = 3
allowed_cidrs  = ["203.0.113.0/24", "198.51.100.0/24"]

Output Variables (outputs.tf)

output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

output "instance_ids" {
  description = "Instance IDs"
  value       = aws_instance.web[*].id
}

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

output "security_group_id" {
  description = "Security Group ID"
  value       = aws_security_group.allow_ssh.id
  sensitive   = false
}

Modules

Module Structure

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

resource "aws_subnet" "public" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.subnet_cidr
  availability_zone = var.availability_zone
}

# modules/vpc/variables.tf
variable "cidr_block" {
  type = string
}

variable "subnet_cidr" {
  type = string
}

variable "availability_zone" {
  type = string
}

# modules/vpc/outputs.tf
output "vpc_id" {
  value = aws_vpc.main.id
}

output "subnet_id" {
  value = aws_subnet.public.id
}

Using Modules

# main.tf
module "vpc" {
  source = "./modules/vpc"

  cidr_block       = "10.0.0.0/16"
  subnet_cidr      = "10.0.1.0/24"
  availability_zone = "us-east-1a"
}

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

  vpc_id = module.vpc.vpc_id
}

Advanced Features

Conditional Logic

resource "aws_instance" "web" {
  count         = var.create_instance ? 1 : 0
  ami           = var.ami_id
  instance_type = var.instance_type
}

resource "aws_ebs_volume" "data" {
  availability_zone = aws_instance.web[0].availability_zone
  size              = 10
}

For_each Loop

resource "aws_instance" "servers" {
  for_each      = var.servers
  ami           = each.value.ami
  instance_type = each.value.instance_type

  tags = {
    Name = each.key
  }
}

# variables.tf
variable "servers" {
  type = map(object({
    ami           = string
    instance_type = string
  }))
  default = {
    web = {
      ami           = "ami-12345"
      instance_type = "t3.medium"
    }
    app = {
      ami           = "ami-67890"
      instance_type = "t3.large"
    }
  }
}

Data Sources

# Get latest Ubuntu AMI
data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-*"]
  }
}

# Get VPC by tag
data "aws_vpc" "main" {
  tags = {
    Name = "main-vpc"
  }
}

# Use in resource
resource "aws_instance" "example" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"
}

State Management

Local State

# Default behavior - state stored locally
terraform init

Remote State (S3)

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

State Locking

# Prevent concurrent modifications
aws dynamodb create-table \
  --table-name terraform-locks \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

Best Practices

Practice Benefit Example
Use variables Reusability var.instance_type
Create modules Code organization Separate vpc, security, compute
Use remote state Team collaboration S3 backend with locking
Add validation Error prevention Input validation blocks
Use locals Reduce repetition locals { common_tags = {...} }
Version lock Stability version = "~> 5.0"
Use workspaces Environment separation terraform workspace select prod
Add descriptions Documentation Clear variable descriptions

Common Commands

Command Purpose
terraform init Initialize working directory
terraform plan Create execution plan
terraform apply Apply changes
terraform destroy Remove infrastructure
terraform fmt Format configuration files
terraform validate Validate configuration
terraform state list List state resources
terraform output Show output values
terraform workspace Manage workspaces
terraform taint Mark resource for recreation

Resources