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 |