Complete Terraform Learning Guide
📚 Table of Contents
- Introduction to Terraform
- Core Components
- Terraform Modules
- Strategy to Build a Project
- Best Practices
- Hands-On Examples
- Additional Resources
1. Introduction to Terraform
What is Terraform?
Terraform is an Infrastructure as Code (IaC) tool created by HashiCorp. It allows you to define and manage your cloud infrastructure using code instead of manually clicking through cloud provider consoles.
🎯 Simple Analogy
Think of Terraform like LEGO instructions:
- Without Terraform: You manually build your LEGO castle brick by brick, each time slightly different, prone to mistakes.
- With Terraform: You have a blueprint (code) that tells you exactly which pieces to use and where. Anyone can follow the same instructions and build an identical castle every time.
Why Infrastructure as Code?
| Traditional Infrastructure | Infrastructure as Code (Terraform) |
|---|---|
| Manual setup through UI | Automated through code |
| Hard to replicate | Easy to replicate |
| No version history | Version controlled (Git) |
| Error-prone | Consistent and predictable |
| Difficult collaboration | Easy team collaboration |
| No documentation | Self-documenting |
Key Benefits
- Automation: Create hundreds of servers with one command
- Consistency: Same code = Same infrastructure every time
- Version Control: Track changes like you do with application code
- Reusability: Write once, use multiple times
- Multi-Cloud: Works with AWS, Azure, Google Cloud, and 3000+ providers
How Terraform Works - The Restaurant Analogy
Imagine you're running a restaurant:
- Your order (Terraform code): Specifies what you want
- Chef (Terraform engine): Reads your order and coordinates
- Kitchen staff (Providers): Each specializes in different cuisines (cloud providers)
- Recipe book (State file): Keeps track of what's been prepared
The Declarative Approach
Imperative (Traditional):
1. Create a network
2. Wait for network to be ready
3. Create a subnet in the network
4. Create a server in the subnet
5. Install software on server
Declarative (Terraform):
# I want a server with these specs
resource "aws_instance" "my_server" {
instance_type = "t2.micro"
ami = "ami-12345678"
}
Terraform figures out the steps automatically!
2. Core Components
2.1 Terraform Architecture
2.2 Core Components Explained
1. Terraform Configuration Files (.tf files)
These are your "blueprints" written in HCL (HashiCorp Configuration Language).
Analogy: Think of them as cooking recipes - they describe ingredients (resources) and steps (dependencies).
# Example: Simple configuration structure
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "MyWebServer"
}
}
2. Terraform CLI (Command Line Interface)
The tool you use to execute Terraform commands.
Analogy: Like a construction foreman who reads the blueprints and directs the workers.
Common Commands:
| Command | What It Does | When to Use |
|---|---|---|
terraform init | Downloads providers and sets up backend | First time in a project |
terraform plan | Shows what will change | Before applying changes |
terraform apply | Creates/updates infrastructure | When ready to deploy |
terraform destroy | Deletes all managed infrastructure | Cleanup or teardown |
terraform validate | Checks syntax | During development |
terraform fmt | Formats code nicely | Before committing code |
3. Providers
Providers are plugins that allow Terraform to interact with cloud platforms, SaaS, and APIs.
Analogy: Like different language translators - AWS provider speaks "AWS language", Azure provider speaks "Azure language".
# AWS Provider
provider "aws" {
region = "us-east-1"
}
# Azure Provider
provider "azurerm" {
features {}
}
Popular Providers:
| Provider | Description | Example Resources |
|---|---|---|
| aws | Amazon Web Services | EC2, S3, RDS, VPC |
| azurerm | Microsoft Azure | Virtual Machines, Storage, AKS |
| Google Cloud Platform | Compute Engine, GCS, GKE | |
| kubernetes | Kubernetes | Deployments, Services, Ingress |
| docker | Docker | Containers, Images, Networks |
4. Resources
Resources are the infrastructure components you want to create.
Analogy: Resources are like individual LEGO blocks - servers, networks, databases, etc.
resource "resource_type" "resource_name" {
# Configuration parameters
parameter1 = "value1"
parameter2 = "value2"
}
Example:
# AWS EC2 Instance
resource "aws_instance" "app_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
# Azure Virtual Machine
resource "azurerm_virtual_machine" "app_vm" {
name = "myVM"
location = "East US"
resource_group_name = "myResourceGroup"
vm_size = "Standard_B2s"
}
5. State File (terraform.tfstate)
The state file is Terraform's memory - it keeps track of what infrastructure exists.
Analogy: Like an inventory list in a warehouse. Terraform checks this list to know what's already been built and what needs to change.
{
"version": 4,
"terraform_version": "1.9.0",
"resources": [
{
"type": "aws_instance",
"name": "web_server",
"instances": [
{
"attributes": {
"id": "i-1234567890abcdef0",
"instance_type": "t2.micro",
"public_ip": "54.123.45.67"
}
}
]
}
]
}
⚠️ Important: Never manually edit the state file! Let Terraform manage it.
6. Variables
Variables make your code reusable and flexible.
Analogy: Like placeholders in a form - you fill them in with different values each time.
# Defining a variable
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
# Using the variable
resource "aws_instance" "web" {
instance_type = var.instance_type
}
Variable Types:
7. Outputs
Outputs display information about your infrastructure after creation.
Analogy: Like a receipt after shopping - shows you what you got.
output "instance_public_ip" {
description = "Public IP of the EC2 instance"
value = aws_instance.web.public_ip
}
8. Data Sources
Data sources allow you to fetch information about existing resources.
Analogy: Like looking up information in a phonebook - read-only access to existing data.
# Fetch existing AWS 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-*"]
}
}
# Use the fetched data
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
}
2.3 Terraform Workflow
Step-by-Step Workflow:
- Write: Create
.tffiles with your infrastructure requirements - Initialize: Run
terraform initto download providers - Validate: Run
terraform validateto check syntax - Plan: Run
terraform planto preview changes - Apply: Run
terraform applyto create infrastructure - Modify: Update code as needed and repeat
- Destroy: Run
terraform destroywhen done
3. Terraform Modules
3.1 What are Modules?
Modules are containers for multiple resources that are used together. They're like reusable "packages" of Terraform code.
Analogy:
- Without Modules: Building a house by specifying every nail, plank, and screw individually
- With Modules: Using pre-fabricated walls, doors, and windows that you can install multiple times
3.2 Module Structure
project/
├── main.tf # Root module - calls other modules
├── variables.tf # Input variables
├── outputs.tf # Output values
├── terraform.tfvars # Variable values
└── modules/
├── network/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── compute/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── database/
├── main.tf
├── variables.tf
└── outputs.tf
3.3 Creating a Module - Network Example
modules/network/main.tf
# This module creates a VPC and subnet
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidr
availability_zone = var.availability_zone
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-subnet"
Environment = var.environment
}
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
Environment = var.environment
}
}
modules/network/variables.tf
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidr" {
description = "CIDR block for public subnet"
type = string
default = "10.0.1.0/24"
}
variable "availability_zone" {
description = "AWS availability zone"
type = string
}
modules/network/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_id" {
description = "ID of the public subnet"
value = aws_subnet.public.id
}
output "vpc_cidr_block" {
description = "CIDR block of the VPC"
value = aws_vpc.main.cidr_block
}
3.4 Using Modules
main.tf (Root Module)
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Use the network module
module "network" {
source = "./modules/network"
environment = var.environment
vpc_cidr = "10.0.0.0/16"
public_subnet_cidr = "10.0.1.0/24"
availability_zone = "${var.aws_region}a"
}
# Use outputs from the module
output "vpc_id" {
value = module.network.vpc_id
}
3.5 Module Sources
You can load modules from different sources:
| Source Type | Example | Use Case |
|---|---|---|
| Local Path | ./modules/network | Custom modules in your project |
| Terraform Registry | terraform-aws-modules/vpc/aws | Public, community modules |
| Git Repository | git::https://github.com/user/repo.git | Private/shared team modules |
| HTTP URL | https://example.com/module.zip | Hosted modules |
Example using Terraform Registry:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["us-east-1a", "us-east-1b"]
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 = true
tags = {
Environment = "dev"
}
}
3.6 Module Benefits
4. Strategy to Build a Project
4.1 Project Planning Phase
4.2 Project Structure Strategy
Small Project (Monolithic)
For simple projects with few resources:
simple-project/
├── main.tf # All resources
├── variables.tf # All variables
├── outputs.tf # All outputs
├── terraform.tfvars # Variable values
└── README.md # Documentation
Medium Project (Organized)
For projects with multiple environments:
medium-project/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ │ └── ...
│ └── prod/
│ └── ...
├── modules/
│ ├── network/
│ ├── compute/
│ └── database/
└── README.md
Large Project (Enterprise)
For complex, multi-team projects:
enterprise-project/
├── infrastructure/
│ ├── global/ # Shared resources (IAM, DNS)
│ │ ├── main.tf
│ │ └── ...
│ └── regional/ # Region-specific resources
│ ├── us-east-1/
│ └── eu-west-1/
├── modules/
│ ├── networking/
│ │ ├── vpc/
│ │ ├── subnets/
│ │ └── security-groups/
│ ├── compute/
│ │ ├── ec2/
│ │ ├── asg/
│ │ └── alb/
│ ├── data/
│ │ ├── rds/
│ │ ├── s3/
│ │ └── dynamodb/
│ └── security/
│ ├── iam/
│ └── kms/
├── environments/
│ ├── dev/
│ ├── staging/
│ └── prod/
├── scripts/ # Helper scripts
├── policies/ # Sentinel/OPA policies
├── docs/ # Documentation
├── .github/ # CI/CD workflows
│ └── workflows/
└── README.md
4.3 Environment Strategy
Example: Environment-Specific Variables
environments/dev/terraform.tfvars
environment = "dev"
instance_type = "t2.micro"
instance_count = 1
enable_backups = false
environments/prod/terraform.tfvars
environment = "prod"
instance_type = "t3.large"
instance_count = 3
enable_backups = true
multi_az = true
4.4 State Management Strategy
Example: Remote State with S3
backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
4.5 Naming Conventions
Consistent naming is crucial for maintainability:
# Resource naming pattern: [environment]-[app]-[resource-type]-[descriptor]
resource "aws_instance" "web" {
tags = {
Name = "${var.environment}-webapp-server-01"
# dev-webapp-server-01
# prod-webapp-server-01
}
}
resource "aws_s3_bucket" "logs" {
bucket = "${var.environment}-${var.app_name}-logs-${var.aws_region}"
# dev-myapp-logs-us-east-1
}
4.6 Step-by-Step Project Implementation
Phase 1: Foundation
Phase 2: Development
Phase 3: Deployment
5. Best Practices
5.1 Code Organization
✅ DO
# Good: Descriptive resource names
resource "aws_instance" "web_server" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = {
Name = "${var.environment}-web-server"
Environment = var.environment
ManagedBy = "Terraform"
}
}
# Good: Use variables for reusability
variable "instance_type" {
description = "EC2 instance type for web servers"
type = string
default = "t2.micro"
validation {
condition = contains(["t2.micro", "t2.small", "t2.medium"], var.instance_type)
error_message = "Instance type must be t2.micro, t2.small, or t2.medium."
}
}
❌ DON'T
# Bad: Hardcoded values
resource "aws_instance" "x" {
ami = "ami-12345678" # Don't hardcode AMI IDs
instance_type = "t2.micro" # Don't hardcode instance types
tags = {
Name = "server1" # Not descriptive
}
}
# Bad: No validation
variable "instance_type" {
type = string
}
5.2 State Management Best Practices
Example: Complete Backend Configuration
terraform {
required_version = ">= 1.9.0"
backend "s3" {
bucket = "company-terraform-states"
key = "prod/webapp/terraform.tfstate"
region = "us-east-1"
encrypt = true
kms_key_id = "arn:aws:kms:us-east-1:123456789:key/xxx"
dynamodb_table = "terraform-state-lock"
# Additional security
acl = "private"
# Enable versioning on S3 bucket
versioning = true
}
}
5.3 Security Best Practices
| Practice | Why | How |
|---|---|---|
| Never commit secrets | Prevents exposure | Use environment variables, AWS Secrets Manager |
| Use least privilege IAM | Minimizes risk | Create specific IAM roles per resource |
| Encrypt everything | Protects data | Enable encryption for state, databases, storage |
| Scan for vulnerabilities | Catch issues early | Use tools like tfsec, checkov, terrascan |
| Use private modules | Control access | Host modules in private registries |
Example: Using Secrets Securely
# ❌ BAD: Never do this!
resource "aws_db_instance" "bad_example" {
username = "admin"
password = "SuperSecret123!" # Never hardcode passwords!
}
# ✅ GOOD: Use AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/database/password"
}
resource "aws_db_instance" "good_example" {
username = "admin"
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
5.4 Resource Tagging Strategy
# Create a local map of common tags
locals {
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
Owner = var.owner_email
CostCenter = var.cost_center
CreatedDate = formatdate("YYYY-MM-DD", timestamp())
}
}
# Apply to resources
resource "aws_instance" "web" {
# ... other configuration ...
tags = merge(
local.common_tags,
{
Name = "${var.environment}-web-server"
Role = "WebServer"
}
)
}
5.5 Version Control Best Practices
# .gitignore for Terraform projects
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which might contain sensitive data
*.tfvars
*.tfvars.json
# Ignore override files
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Ignore plan files
*.tfplan
5.6 Code Quality Checks
Commands:
# Format code
terraform fmt -recursive
# Validate syntax
terraform validate
# Security scan with tfsec
tfsec .
# Policy scan with checkov
checkov -d .
# Generate plan
terraform plan -out=tfplan
5.7 Documentation Best Practices
Every module should have:
- README.md with:
- Purpose and description
- Requirements
- Usage examples
- Input variables table
- Output values table
Example README.md:
# Network Module
Creates a VPC with public and private subnets.
## Requirements
- Terraform >= 1.9.0
- AWS Provider >= 5.0
## Usage
```hcl
module "network" {
source = "./modules/network"
environment = "prod"
vpc_cidr = "10.0.0.0/16"
}
Inputs
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| environment | Environment name | string | - | yes |
| vpc_cidr | VPC CIDR block | string | "10.0.0.0/16" | no |
Outputs
| Name | Description |
|---|---|
| vpc_id | ID of the VPC |
| subnet_ids | List of subnet IDs |
### 5.8 Testing Strategy
```mermaid
graph TD
A[Testing Levels] --> B[Unit Tests]
A --> C[Integration Tests]
A --> D[End-to-End Tests]
B --> B1[terraform validate]
B --> B2[terraform plan]
B --> B3[Static Analysis]
C --> C1[Deploy to Test Env]
C --> C2[Verify Resources]
C --> C3[Connectivity Tests]
D --> D1[Full Application Stack]
D --> D2[User Acceptance Tests]
D --> D3[Performance Tests]
5.9 Dependency Management
# Use depends_on sparingly - Terraform usually handles dependencies automatically
# Explicit dependency (when needed)
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
subnet_id = aws_subnet.public.id # Implicit dependency - Preferred!
}
# Use depends_on only for non-obvious dependencies
resource "aws_iam_role_policy" "example" {
# ... configuration ...
depends_on = [aws_iam_role.example] # When Terraform can't detect automatically
}
5.10 Performance Optimization
| Technique | Description | When to Use |
|---|---|---|
-parallelism=n | Run operations in parallel | Large infrastructures |
-target=resource | Apply to specific resources | Debugging or partial updates |
-refresh=false | Skip state refresh | When state is known to be current |
| Workspaces | Separate environments | Multiple environments in same config |
| Remote State Data Source | Reference other state files | Sharing data between projects |
# Speed up large deployments
terraform apply -parallelism=20
# Target specific resource
terraform apply -target=aws_instance.web
# Skip refresh
terraform plan -refresh=false
6. Hands-On Examples
6.1 Example 1: Simple AWS Web Server
This example creates a complete AWS infrastructure with a web server.
Step 1: Create Project Structure
mkdir terraform-aws-webserver
cd terraform-aws-webserver
main.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Get latest Ubuntu AMI
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Create VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
}
}
# Create Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
}
}
# Create Public Subnet
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidr
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-subnet"
}
}
# Get available AZs
data "aws_availability_zones" "available" {
state = "available"
}
# Create Route Table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-public-rt"
}
}
# Associate Route Table with Subnet
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
# Create Security Group
resource "aws_security_group" "web" {
name = "${var.project_name}-web-sg"
description = "Security group for web server"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from anywhere"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "SSH from anywhere (restrict this in production!)"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-web-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.web.id]
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
echo "<h1>Hello from Terraform!</h1>" > /var/www/html/index.html
systemctl start nginx
systemctl enable nginx
EOF
tags = {
Name = "${var.project_name}-web-server"
}
}
variables.tf
variable "aws_region" {
description = "AWS region to deploy resources"
type = string
default = "us-east-1"
}
variable "project_name" {
description = "Project name for resource naming"
type = string
default = "terraform-demo"
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidr" {
description = "CIDR block for public subnet"
type = string
default = "10.0.1.0/24"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "web_server_public_ip" {
description = "Public IP address of web server"
value = aws_instance.web.public_ip
}
output "web_server_url" {
description = "URL to access the web server"
value = "http://${aws_instance.web.public_ip}"
}
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.web.id
}
terraform.tfvars (optional - for custom values)
aws_region = "us-east-1"
project_name = "my-web-app"
instance_type = "t2.micro"
Commands to Run:
# Initialize Terraform
terraform init
# Format code
terraform fmt
# Validate configuration
terraform validate
# Preview changes
terraform plan
# Apply changes (creates infrastructure)
terraform apply
# Access your web server (use the output URL)
# http://<public_ip>
# Destroy infrastructure when done
terraform destroy
Architecture Diagram:
6.2 Example 2: Azure Web Application
This example creates a complete Azure infrastructure with a web application.
main.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
}
}
# Create Resource Group
resource "azurerm_resource_group" "main" {
name = "${var.project_name}-rg"
location = var.location
tags = {
Environment = var.environment
Project = var.project_name
}
}
# Create Virtual Network
resource "azurerm_virtual_network" "main" {
name = "${var.project_name}-vnet"
address_space = ["10.0.0.0/16"]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tags = {
Environment = var.environment
}
}
# Create Subnet
resource "azurerm_subnet" "web" {
name = "${var.project_name}-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = ["10.0.1.0/24"]
}
# Create Public IP
resource "azurerm_public_ip" "web" {
name = "${var.project_name}-public-ip"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
allocation_method = "Static"
sku = "Standard"
tags = {
Environment = var.environment
}
}
# Create Network Security Group
resource "azurerm_network_security_group" "web" {
name = "${var.project_name}-nsg"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "AllowHTTP"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "AllowHTTPS"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "AllowSSH"
priority = 120
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
tags = {
Environment = var.environment
}
}
# Create Network Interface
resource "azurerm_network_interface" "web" {
name = "${var.project_name}-nic"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.web.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.web.id
}
tags = {
Environment = var.environment
}
}
# Associate NSG with Network Interface
resource "azurerm_network_interface_security_group_association" "web" {
network_interface_id = azurerm_network_interface.web.id
network_security_group_id = azurerm_network_security_group.web.id
}
# Create Virtual Machine
resource "azurerm_linux_virtual_machine" "web" {
name = "${var.project_name}-vm"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
size = var.vm_size
admin_username = var.admin_username
network_interface_ids = [
azurerm_network_interface.web.id,
]
admin_ssh_key {
username = var.admin_username
public_key = file("~/.ssh/id_rsa.pub") # Update this path
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
custom_data = base64encode(<<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
echo "<h1>Hello from Azure with Terraform!</h1>" > /var/www/html/index.html
systemctl start nginx
systemctl enable nginx
EOF
)
tags = {
Environment = var.environment
}
}
variables.tf
variable "project_name" {
description = "Project name for resource naming"
type = string
default = "terraform-azure-demo"
}
variable "location" {
description = "Azure region"
type = string
default = "East US"
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
variable "vm_size" {
description = "Size of the VM"
type = string
default = "Standard_B1s"
}
variable "admin_username" {
description = "Admin username for VM"
type = string
default = "azureuser"
}
outputs.tf
output "resource_group_name" {
description = "Name of the resource group"
value = azurerm_resource_group.main.name
}
output "public_ip_address" {
description = "Public IP address of the VM"
value = azurerm_public_ip.web.ip_address
}
output "vm_url" {
description = "URL to access the web server"
value = "http://${azurerm_public_ip.web.ip_address}"
}
output "vm_name" {
description = "Name of the virtual machine"
value = azurerm_linux_virtual_machine.web.name
}
output "ssh_command" {
description = "SSH command to connect to VM"
value = "ssh ${var.admin_username}@${azurerm_public_ip.web.ip_address}"
}
Commands to Run:
# Login to Azure
az login
# Initialize Terraform
terraform init
# Format and validate
terraform fmt
terraform validate
# Preview changes
terraform plan
# Apply changes
terraform apply
# Access your web server (use output URL)
# http://<public_ip>
# Connect via SSH
# ssh azureuser@<public_ip>
# Destroy infrastructure
terraform destroy
Architecture Diagram:
6.3 Example 3: Multi-Environment Setup with Modules
This example shows how to structure a project for multiple environments using modules.
Project Structure:
multi-env-project/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars
├── modules/
│ └── web-server/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── environments/
├── dev/
│ ├── main.tf
│ └── terraform.tfvars
├── staging/
│ ├── main.tf
│ └── terraform.tfvars
└── prod/
├── main.tf
└── terraform.tfvars
modules/web-server/main.tf
# Reusable web server module
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = var.subnet_id
vpc_security_group_ids = [aws_security_group.web.id]
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get install -y nginx
echo "<h1>${var.environment} Environment - Hello from Terraform!</h1>" > /var/www/html/index.html
systemctl start nginx
systemctl enable nginx
EOF
tags = merge(
var.tags,
{
Name = "${var.environment}-web-server"
}
)
}
resource "aws_security_group" "web" {
name = "${var.environment}-web-sg"
description = "Security group for ${var.environment} web server"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.ssh_cidr_blocks
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(
var.tags,
{
Name = "${var.environment}-web-sg"
}
)
}
modules/web-server/variables.tf
variable "environment" {
description = "Environment name"
type = string
}
variable "instance_type" {
description = "EC2 instance type"
type = string
}
variable "vpc_id" {
description = "VPC ID"
type = string
}
variable "subnet_id" {
description = "Subnet ID"
type = string
}
variable "ssh_cidr_blocks" {
description = "CIDR blocks allowed for SSH"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}
modules/web-server/outputs.tf
output "instance_id" {
description = "ID of the EC2 instance"
value = aws_instance.web.id
}
output "public_ip" {
description = "Public IP of the instance"
value = aws_instance.web.public_ip
}
output "security_group_id" {
description = "ID of the security group"
value = aws_security_group.web.id
}
environments/dev/main.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-states"
key = "dev/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
provider "aws" {
region = "us-east-1"
}
# Use existing VPC (or create one)
data "aws_vpc" "selected" {
default = true
}
data "aws_subnets" "public" {
filter {
name = "vpc-id"
values = [data.aws_vpc.selected.id]
}
}
module "web_server" {
source = "../../modules/web-server"
environment = "dev"
instance_type = "t2.micro"
vpc_id = data.aws_vpc.selected.id
subnet_id = data.aws_subnets.public.ids[0]
ssh_cidr_blocks = ["0.0.0.0/0"]
tags = {
Environment = "dev"
ManagedBy = "Terraform"
Team = "DevOps"
}
}
output "dev_server_ip" {
value = module.web_server.public_ip
}
environments/dev/terraform.tfvars
# Development environment specific values
# (All configuration is in main.tf for this simple example)
environments/prod/main.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "my-terraform-states"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
}
}
provider "aws" {
region = "us-east-1"
}
data "aws_vpc" "selected" {
default = true
}
data "aws_subnets" "public" {
filter {
name = "vpc-id"
values = [data.aws_vpc.selected.id]
}
}
module "web_server" {
source = "../../modules/web-server"
environment = "prod"
instance_type = "t3.medium" # Larger instance for production
vpc_id = data.aws_vpc.selected.id
subnet_id = data.aws_subnets.public.ids[0]
ssh_cidr_blocks = ["10.0.0.0/8"] # Restricted SSH access
tags = {
Environment = "prod"
ManagedBy = "Terraform"
Team = "DevOps"
Critical = "true"
}
}
output "prod_server_ip" {
value = module.web_server.public_ip
}
Usage:
# Deploy to development
cd environments/dev
terraform init
terraform plan
terraform apply
# Deploy to production
cd ../prod
terraform init
terraform plan
terraform apply
Comparison Table:
| Aspect | Development | Production |
|---|---|---|
| Instance Type | t2.micro | t3.medium |
| SSH Access | Open (0.0.0.0/0) | Restricted (10.0.0.0/8) |
| State File | dev/terraform.tfstate | prod/terraform.tfstate |
| Cost | Low | Higher |
| High Availability | No | Yes (can add) |
6.4 Example 4: AWS S3 Website Hosting
main.tf
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Create S3 bucket
resource "aws_s3_bucket" "website" {
bucket = var.bucket_name
tags = {
Name = var.bucket_name
Environment = var.environment
}
}
# Configure bucket for website hosting
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
# Make bucket public
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
# Bucket policy for public read access
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
})
depends_on = [aws_s3_bucket_public_access_block.website]
}
# Upload index.html
resource "aws_s3_object" "index" {
bucket = aws_s3_bucket.website.id
key = "index.html"
content_type = "text/html"
content = <<-EOF
<!DOCTYPE html>
<html>
<head>
<title>My Terraform Website</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
h1 {
text-align: center;
}
.container {
background: rgba(255, 255, 255, 0.1);
padding: 30px;
border-radius: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Welcome to My Terraform Website!</h1>
<p>This website is hosted on AWS S3 and deployed using Terraform.</p>
<p><strong>Environment:</strong> ${var.environment}</p>
<p><strong>Deployed on:</strong> ${timestamp()}</p>
</div>
</body>
</html>
EOF
}
# Upload error.html
resource "aws_s3_object" "error" {
bucket = aws_s3_bucket.website.id
key = "error.html"
content_type = "text/html"
content = <<-EOF
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
</head>
<body>
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
</body>
</html>
EOF
}
variables.tf
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "bucket_name" {
description = "Name of the S3 bucket (must be globally unique)"
type = string
}
variable "environment" {
description = "Environment name"
type = string
default = "dev"
}
outputs.tf
output "website_url" {
description = "URL of the website"
value = "http://${aws_s3_bucket_website_configuration.website.website_endpoint}"
}
output "bucket_name" {
description = "Name of the S3 bucket"
value = aws_s3_bucket.website.id
}
terraform.tfvars
bucket_name = "my-terraform-website-12345" # Must be globally unique!
aws_region = "us-east-1"
environment = "production"
Commands:
terraform init
terraform apply
# Visit the website URL from output
# http://my-terraform-website-12345.s3-website-us-east-1.amazonaws.com
7. Additional Resources
7.1 Official Documentation
| Resource | URL | Description |
|---|---|---|
| Terraform Docs | https://www.terraform.io/docs | Official documentation |
| Terraform Registry | https://registry.terraform.io | Provider and module registry |
| Terraform Tutorials | https://learn.hashicorp.com/terraform | Interactive learning |
| AWS Provider | https://registry.terraform.io/providers/hashicorp/aws | AWS provider docs |
| Azure Provider | https://registry.terraform.io/providers/hashicorp/azurerm | Azure provider docs |
7.2 Useful Tools
7.3 Common Terraform Commands Reference
# Initialization
terraform init # Initialize working directory
terraform init -upgrade # Upgrade providers
# Planning
terraform plan # Show execution plan
terraform plan -out=tfplan # Save plan to file
terraform plan -target=resource # Plan for specific resource
# Applying
terraform apply # Apply changes
terraform apply tfplan # Apply saved plan
terraform apply -auto-approve # Apply without confirmation
# Destroying
terraform destroy # Destroy all resources
terraform destroy -target=resource # Destroy specific resource
# State Management
terraform state list # List resources in state
terraform state show resource # Show resource details
terraform state rm resource # Remove resource from state
terraform state pull # Download remote state
# Workspace
terraform workspace list # List workspaces
terraform workspace new dev # Create new workspace
terraform workspace select dev # Switch workspace
# Formatting & Validation
terraform fmt # Format code
terraform fmt -recursive # Format recursively
terraform validate # Validate configuration
# Other
terraform output # Show outputs
terraform show # Show current state
terraform graph # Generate dependency graph
terraform import # Import existing resource
7.4 Troubleshooting Guide
| Problem | Solution |
|---|---|
| State lock error | terraform force-unlock <LOCK_ID> |
| Provider plugin issues | terraform init -upgrade |
| Resource already exists | terraform import the resource |
| State file corrupted | Restore from backup |
| Dependency cycle | Review depends_on usage |
| Plan shows unexpected changes | Check for drift with terraform refresh |
7.5 Learning Path
7.6 Next Steps
- Practice with Examples: Run all examples in this guide
- Build a Real Project: Start with a simple 3-tier application
- Explore Modules: Browse Terraform Registry for useful modules
- Join Community:
- Terraform Forum: https://discuss.hashicorp.com/c/terraform
- Reddit: r/terraform
- Discord: HashiCorp Community
- Certification: Consider HashiCorp Terraform Associate certification
7.7 Quick Reference - File Extensions
| Extension | Purpose |
|---|---|
| .tf | Terraform configuration files |
| .tfvars | Variable value files |
| .tfstate | State file (never edit manually!) |
| .tfplan | Saved execution plan |
| .terraform/ | Directory with providers and modules |
| .terraform.lock.hcl | Dependency lock file |
🎓 Summary
You've learned:
✅ What Terraform is: Infrastructure as Code tool
✅ Core Components: Providers, Resources, State, Variables, Outputs
✅ Modules: Reusable infrastructure components
✅ Project Strategy: How to structure real-world projects
✅ Best Practices: Code quality, security, and maintainability
✅ Hands-on Examples: AWS and Azure implementations
Remember
- Start Small: Begin with simple infrastructure
- Use Version Control: Always use Git
- Remote State: Never use local state for production
- Modules: Reuse code with modules
- Documentation: Document everything
- Security: Never commit secrets
- Test: Always run
terraform planbeforeapply
Happy Terraforming! 🚀
Terraform Version: 1.9.x
AWS Provider: 5.x
Azure Provider: 3.x
Written while teaching DevOps to 800+ engineers across 12 cohorts.
