
Terraform Infrastructure
- 301 installs
- 61 repo stars
- Updated June 13, 2026
- manutej/luxor-claude-marketplace
Provision and manage cloud resources with Terraform modules, variables, state backends, and environment-specific stacks for new or existing services.
About
Guides Claude through Terraform infrastructure design: structuring modules, managing remote state, defining variables and outputs, wiring cloud providers, and safely evolving stacks across dev, staging, and production.
- Module layout and variable conventions
- Remote state and workspace strategy
- IAM least-privilege patterns
- Environment promotion workflows
- Drift detection and import guidance
Terraform Infrastructure by the numbers
- 301 all-time installs (skills.sh)
- +18 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #407 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill terraform-infrastructureAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 301 |
|---|---|
| repo stars | ★ 61 |
| Last updated | June 13, 2026 |
| Repository | manutej/luxor-claude-marketplace ↗ |
What it does
Provision and manage cloud resources with Terraform modules, variables, state backends, and environment-specific stacks for new or existing services.
Files
Terraform Infrastructure-as-Code
A comprehensive skill for building, managing, and scaling cloud infrastructure using Terraform. Master declarative infrastructure, multi-cloud deployments, state management, module composition, and enterprise-grade patterns for AWS, Azure, GCP, and other providers.
When to Use This Skill
Use this skill when:
- Provisioning cloud infrastructure across AWS, Azure, GCP, or multi-cloud environments
- Building reusable infrastructure modules for teams and organizations
- Managing infrastructure state across multiple environments (dev, staging, production)
- Implementing infrastructure as code (IaC) best practices and governance
- Migrating from manual infrastructure to automated, version-controlled deployments
- Creating repeatable, testable infrastructure configurations
- Orchestrating complex multi-tier application architectures
- Managing Kubernetes clusters, databases, networks, and compute resources
- Implementing disaster recovery and multi-region deployments
- Collaborating on infrastructure changes with teams using GitOps workflows
Core Concepts
Infrastructure as Code Philosophy
Terraform enables declarative infrastructure management:
- Declarative Configuration: Define desired state, Terraform handles execution
- Immutable Infrastructure: Replace rather than modify infrastructure
- Version Control: Track infrastructure changes like application code
- Plan Before Apply: Preview changes before execution
- Resource Graph: Automatic dependency resolution and parallel execution
- State Management: Track real-world resources and their configuration
Key Terraform Components
1. Providers: Plugins for infrastructure platforms (AWS, Azure, GCP, Kubernetes, etc.) 2. Resources: Infrastructure objects (VMs, networks, databases, storage) 3. Data Sources: Query existing infrastructure or external data 4. Variables: Parameterize configurations for reusability 5. Outputs: Export values for consumption by other configurations 6. Modules: Reusable, composable infrastructure components 7. State: JSON file tracking managed infrastructure 8. Workspaces: Manage multiple instances of infrastructure
Terraform Workflow
Write → Init → Plan → Apply → Destroy
↓ ↓ ↓ ↓ ↓
.tf Download Review Execute Remove
files providers changes changes resourcesTerraform Language (HCL)
Basic Syntax
HCL (HashiCorp Configuration Language) is declarative and human-readable:
# Block structure
block_type "block_label" "block_name" {
argument_name = argument_value
nested_block {
nested_argument = value
}
}
# Example: EC2 instance resource
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "WebServer"
Environment = "production"
}
}Variables and Types
Terraform supports rich type system:
# String variable
variable "region" {
type = string
description = "AWS region for resources"
default = "us-east-1"
}
# Number variable
variable "instance_count" {
type = number
default = 3
}
# Boolean variable
variable "enable_monitoring" {
type = bool
default = true
}
# List variable
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
# Map variable
variable "instance_tags" {
type = map(string)
default = {
Environment = "production"
Project = "web-app"
}
}
# Object variable
variable "database_config" {
type = object({
engine = string
engine_version = string
instance_class = string
allocated_storage = number
})
default = {
engine = "postgres"
engine_version = "13.7"
instance_class = "db.t3.micro"
allocated_storage = 20
}
}
# Set variable
variable "allowed_cidr_blocks" {
type = set(string)
default = ["10.0.0.0/8", "172.16.0.0/12"]
}
# Tuple variable
variable "server_config" {
type = tuple([string, number, bool])
default = ["t3.micro", 2, true]
}Variable Validation
Add custom validation rules:
variable "instance_type" {
type = string
description = "EC2 instance type"
validation {
condition = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
error_message = "Instance type must be t3.micro, t3.small, or t3.medium."
}
}
variable "environment" {
type = string
validation {
condition = can(regex("^(dev|staging|prod)$", var.environment))
error_message = "Environment must be dev, staging, or prod."
}
}
variable "cidr_block" {
type = string
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "Must be a valid IPv4 CIDR block."
}
}Locals and Expressions
Locals compute values once and reuse them:
locals {
# Simple local
environment = terraform.workspace
# Computed local
common_tags = {
Environment = local.environment
ManagedBy = "Terraform"
Project = var.project_name
}
# Conditional local
instance_count = var.environment == "prod" ? 5 : 2
# List manipulation
all_subnets = concat(var.public_subnets, var.private_subnets)
# Map merging
merged_tags = merge(
local.common_tags,
var.additional_tags
)
# String interpolation
bucket_name = "${var.project_name}-${local.environment}-data"
# For expression
subnet_ids = [for subnet in aws_subnet.private : subnet.id]
# For expression with filtering
prod_instances = [
for instance in aws_instance.app :
instance.id if instance.tags["Environment"] == "prod"
]
# Map transformation
instance_map = {
for idx, instance in aws_instance.app :
instance.tags["Name"] => instance.id
}
}Functions
Terraform provides built-in functions:
# String functions
upper("hello") # "HELLO"
lower("WORLD") # "world"
title("hello world") # "Hello World"
trim(" spaces ") # "spaces"
trimprefix("prefix-value", "prefix-") # "value"
format("Server %03d", 1) # "Server 001"
join("-", ["a", "b", "c"]) # "a-b-c"
split("-", "a-b-c") # ["a", "b", "c"]
substr("hello", 0, 3) # "hel"
replace("hello", "l", "r") # "herro"
# Numeric functions
max(5, 12, 9) # 12
min(5, 12, 9) # 5
ceil(5.1) # 6
floor(5.9) # 5
parseint("100", 10) # 100
# Collection functions
length([1, 2, 3]) # 3
element(["a", "b", "c"], 1) # "b"
concat([1, 2], [3, 4]) # [1, 2, 3, 4]
contains(["a", "b"], "a") # true
distinct([1, 2, 2, 3]) # [1, 2, 3]
flatten([[1, 2], [3, 4]]) # [1, 2, 3, 4]
keys({a = 1, b = 2}) # ["a", "b"]
values({a = 1, b = 2}) # [1, 2]
lookup({a = 1, b = 2}, "a", 0) # 1
merge({a = 1}, {b = 2}) # {a = 1, b = 2}
reverse([1, 2, 3]) # [3, 2, 1]
slice([1, 2, 3, 4], 1, 3) # [2, 3]
sort(["c", "a", "b"]) # ["a", "b", "c"]
# Encoding functions
base64encode("hello") # "aGVsbG8="
base64decode("aGVsbG8=") # "hello"
jsonencode({key = "value"}) # "{\"key\":\"value\"}"
jsondecode("{\"key\":\"value\"}") # {key = "value"}
yamlencode({key = "value"}) # "key: value\n"
yamldecode("key: value") # {key = "value"}
# Filesystem functions
file("path/to/file.txt") # Read file content
templatefile("template.tpl", { # Render template
var1 = "value1"
})
# Date/time functions
timestamp() # "2024-01-15T12:30:45Z"
formatdate("DD MMM YYYY", timestamp()) # "15 Jan 2024"
# Network functions
cidrhost("10.0.0.0/24", 5) # "10.0.0.5"
cidrnetmask("10.0.0.0/24") # "255.255.255.0"
cidrsubnet("10.0.0.0/16", 8, 2) # "10.0.2.0/24"
# Type conversion
tostring(42) # "42"
tonumber("42") # 42
tobool("true") # true
tolist([1, 2, 3]) # [1, 2, 3]
toset([1, 2, 2, 3]) # [1, 2, 3]
tomap({a = 1}) # {a = 1}
# Conditional functions
can(regex("^[a-z]+$", var.name)) # true if valid
try(var.optional_value, "default") # Return first valid valueConditional Expressions
# Ternary operator
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
# With count for conditional resources
resource "aws_instance" "web" {
count = var.create_instance ? 1 : 0
# ... configuration
}
# Dynamic blocks
resource "aws_security_group" "example" {
name = "example"
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
}
}
}Meta-Arguments
Resources support special arguments:
# depends_on: Explicit dependencies
resource "aws_instance" "web" {
depends_on = [aws_security_group.web_sg]
# ...
}
# count: Create multiple instances
resource "aws_instance" "web" {
count = 3
ami = var.ami_id
instance_type = "t3.micro"
tags = {
Name = "web-server-${count.index}"
}
}
# for_each: Create from map or set
resource "aws_instance" "servers" {
for_each = var.servers # map or set
ami = each.value.ami
instance_type = each.value.type
tags = {
Name = each.key
}
}
# provider: Specify provider configuration
resource "aws_instance" "replica" {
provider = aws.us-west-2
# ...
}
# lifecycle: Control resource behavior
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = "t3.micro"
lifecycle {
create_before_destroy = true # Create new before destroying old
prevent_destroy = true # Prevent accidental deletion
ignore_changes = [ # Ignore specific changes
tags,
user_data
]
}
}Providers
Provider Configuration
Configure infrastructure platforms:
# AWS Provider
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
ManagedBy = "Terraform"
Project = var.project_name
}
}
}
# Azure Provider
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = true
}
}
}
# GCP Provider
provider "google" {
project = var.gcp_project_id
region = var.gcp_region
}
# Kubernetes Provider
provider "kubernetes" {
config_path = "~/.kube/config"
}
# Multiple provider configurations (aliases)
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
provider "aws" {
alias = "us_west_2"
region = "us-west-2"
}
# Use aliased provider
resource "aws_instance" "east" {
provider = aws.us_east_1
# ...
}Provider Authentication
Secure authentication methods:
# AWS - Environment variables (recommended)
# export AWS_ACCESS_KEY_ID="..."
# export AWS_SECRET_ACCESS_KEY="..."
# export AWS_SESSION_TOKEN="..." # for temporary credentials
provider "aws" {
region = "us-east-1"
# Credentials from environment or ~/.aws/credentials
}
# AWS - Assume role
provider "aws" {
region = "us-east-1"
assume_role {
role_arn = "arn:aws:iam::123456789012:role/TerraformRole"
session_name = "terraform-session"
external_id = "unique-id"
}
}
# Azure - Service principal
provider "azurerm" {
features {}
client_id = var.azure_client_id
client_secret = var.azure_client_secret
tenant_id = var.azure_tenant_id
subscription_id = var.azure_subscription_id
}
# Azure - Managed identity
provider "azurerm" {
features {}
use_msi = true
}
# GCP - Service account
provider "google" {
credentials = file("path/to/service-account-key.json")
project = var.gcp_project_id
region = var.gcp_region
}Resources
Resource Declaration
Define infrastructure components:
resource "resource_type" "resource_name" {
argument_name = argument_value
}
# Example: S3 bucket
resource "aws_s3_bucket" "data" {
bucket = "my-application-data"
tags = {
Name = "Data Bucket"
Environment = "production"
}
}
# Reference resource attributes
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id # Reference bucket ID
versioning_configuration {
status = "Enabled"
}
}Resource Lifecycle
# Create, update, destroy lifecycle
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
# Lifecycle customization
lifecycle {
# Create replacement before destroying
create_before_destroy = true
# Prevent destruction
prevent_destroy = false
# Ignore changes to specific attributes
ignore_changes = [
tags["LastModified"],
user_data
]
# Replace if specific attributes change
replace_triggered_by = [
aws_security_group.web.id
]
}
}Data Sources
Query existing infrastructure or external data:
# AWS AMI lookup
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"]
}
}
# Use data source in resource
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}
# VPC lookup
data "aws_vpc" "default" {
default = true
}
# Availability zones
data "aws_availability_zones" "available" {
state = "available"
}
# Current region
data "aws_region" "current" {}
# Current account
data "aws_caller_identity" "current" {}
# External data source
data "external" "example" {
program = ["python", "${path.module}/script.py"]
query = {
key = "value"
}
}
# HTTP data source
data "http" "ip" {
url = "https://ifconfig.me"
}
# Template file (deprecated, use templatefile())
data "template_file" "user_data" {
template = file("${path.module}/user-data.sh")
vars = {
server_port = 8080
db_address = aws_db_instance.main.address
}
}Modules
Module Structure
Organize code into reusable modules:
modules/
├── vpc/
│ ├── main.tf # Resources
│ ├── variables.tf # Input variables
│ ├── outputs.tf # Output values
│ └── README.md # Documentation
├── compute/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── versions.tf # Provider requirements
└── database/
├── main.tf
├── variables.tf
└── outputs.tfCreating a Module
# modules/vpc/variables.tf
variable "vpc_name" {
type = string
description = "Name of the VPC"
}
variable "vpc_cidr" {
type = string
description = "CIDR block for VPC"
default = "10.0.0.0/16"
}
variable "availability_zones" {
type = list(string)
description = "List of availability zones"
}
variable "public_subnet_cidrs" {
type = list(string)
description = "CIDR blocks for public subnets"
}
variable "private_subnet_cidrs" {
type = list(string)
description = "CIDR blocks for private subnets"
}
variable "enable_nat_gateway" {
type = bool
description = "Enable NAT gateway for private subnets"
default = true
}
variable "tags" {
type = map(string)
description = "Tags to apply to resources"
default = {}
}
# modules/vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(
var.tags,
{
Name = var.vpc_name
}
)
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-igw"
}
)
}
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-public-${count.index + 1}"
Type = "public"
}
)
}
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-private-${count.index + 1}"
Type = "private"
}
)
}
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
domain = "vpc"
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-nat-eip-${count.index + 1}"
}
)
}
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-nat-${count.index + 1}"
}
)
depends_on = [aws_internet_gateway.main]
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "vpc_cidr" {
description = "CIDR block of the VPC"
value = aws_vpc.main.cidr_block
}
output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs of private subnets"
value = aws_subnet.private[*].id
}
output "nat_gateway_ids" {
description = "IDs of NAT gateways"
value = aws_nat_gateway.main[*].id
}
output "internet_gateway_id" {
description = "ID of the internet gateway"
value = aws_internet_gateway.main.id
}Using Modules
# Root main.tf
module "vpc" {
source = "./modules/vpc"
vpc_name = "production-vpc"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
enable_nat_gateway = true
tags = {
Environment = "production"
ManagedBy = "Terraform"
}
}
# Reference module outputs
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = module.vpc.public_subnet_ids[0]
tags = {
Name = "web-server"
}
}
# Use remote module from Terraform Registry
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "3.15.0"
bucket = "my-application-bucket"
acl = "private"
versioning = {
enabled = true
}
}
# Use module from GitHub
module "consul" {
source = "github.com/hashicorp/consul//terraform/aws"
servers = 3
}
# Use module from Git with specific branch
module "vpc" {
source = "git::https://github.com/organization/terraform-modules.git//vpc?ref=v1.2.0"
# ...
}Module Versioning
# In module source (versions.tf)
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Using versioned modules
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0" # Allow minor and patch updates
# ...
}State Management
Local State
Default state stored locally:
# terraform.tfstate (automatically created)
{
"version": 4,
"terraform_version": "1.5.0",
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "web",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"attributes": {
"id": "i-1234567890abcdef0",
"ami": "ami-0c55b159cbfafe1f0",
"instance_type": "t3.micro"
}
}
]
}
]
}Remote State - S3 Backend
Store state in S3 for team collaboration:
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
# Optional: KMS encryption
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}
# Create S3 bucket and DynamoDB table for state
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state"
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}Remote State - Azure Backend
terraform {
backend "azurerm" {
resource_group_name = "terraform-state-rg"
storage_account_name = "terraformstate12345"
container_name = "tfstate"
key = "production.terraform.tfstate"
}
}Remote State - GCS Backend
terraform {
backend "gcs" {
bucket = "my-terraform-state"
prefix = "production"
}
}Remote State - Terraform Cloud
terraform {
cloud {
organization = "my-organization"
workspaces {
name = "production-infrastructure"
}
}
}Remote State Data Source
Read state from another configuration:
data "terraform_remote_state" "vpc" {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "vpc/terraform.tfstate"
region = "us-east-1"
}
}
# Use outputs from remote state
resource "aws_instance" "web" {
subnet_id = data.terraform_remote_state.vpc.outputs.public_subnet_ids[0]
# ...
}Workspaces
Manage multiple environments:
# List workspaces
terraform workspace list
# Create new workspace
terraform workspace new staging
terraform workspace new production
# Select workspace
terraform workspace select staging
# Show current workspace
terraform workspace show
# Delete workspace
terraform workspace delete stagingWorkspace-Based Configuration
# Use workspace in resource naming
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = terraform.workspace == "prod" ? "t3.large" : "t3.micro"
tags = {
Name = "web-${terraform.workspace}"
Environment = terraform.workspace
}
}
# Workspace-specific variables
locals {
env_config = {
dev = {
instance_count = 1
instance_type = "t3.micro"
}
staging = {
instance_count = 2
instance_type = "t3.small"
}
prod = {
instance_count = 5
instance_type = "t3.large"
}
}
current_env = local.env_config[terraform.workspace]
}
resource "aws_instance" "app" {
count = local.current_env.instance_count
instance_type = local.current_env.instance_type
# ...
}Best Practices
Code Organization
terraform-project/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ │ └── ...
│ └── production/
│ └── ...
├── modules/
│ ├── vpc/
│ ├── compute/
│ └── database/
├── global/
│ ├── iam/
│ └── route53/
└── README.mdNaming Conventions
# Resource naming: <resource_type>_<name>_<purpose>
resource "aws_security_group" "web_server_public" { }
resource "aws_instance" "web_server_primary" { }
# Variable naming: descriptive and specific
variable "vpc_cidr_block" { }
variable "database_instance_class" { }
variable "enable_auto_scaling" { }
# Tags: consistent and comprehensive
tags = {
Name = "resource-name"
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
Owner = "team-name"
CostCenter = "engineering"
}Security Best Practices
# Never hardcode credentials
# BAD
provider "aws" {
access_key = "AKIAIOSFODNN7EXAMPLE"
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
# GOOD - Use environment variables or IAM roles
provider "aws" {
region = "us-east-1"
}
# Use sensitive flag for secrets
variable "database_password" {
type = string
sensitive = true
}
# Encrypt state files
terraform {
backend "s3" {
bucket = "terraform-state"
key = "terraform.tfstate"
encrypt = true
}
}
# Use .gitignore
# .gitignore
*.tfstate
*.tfstate.*
.terraform/
*.tfvars # if contains secrets
crash.log
override.tf
override.tf.jsonDRY (Don't Repeat Yourself)
# Use locals for repeated values
locals {
common_tags = {
Environment = var.environment
ManagedBy = "Terraform"
Project = var.project_name
}
}
resource "aws_instance" "web" {
tags = local.common_tags
}
resource "aws_s3_bucket" "data" {
tags = local.common_tags
}
# Use modules for reusable infrastructure
module "web_server" {
source = "./modules/ec2-instance"
instance_type = "t3.micro"
tags = local.common_tags
}
# Use for_each to avoid duplication
resource "aws_instance" "servers" {
for_each = var.servers
ami = each.value.ami
instance_type = each.value.type
tags = merge(
local.common_tags,
{
Name = each.key
}
)
}Documentation
# Document variables
variable "vpc_cidr" {
type = string
description = "CIDR block for VPC. Must not overlap with existing VPCs."
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "Must be a valid IPv4 CIDR block."
}
}
# Document outputs
output "vpc_id" {
description = "ID of the VPC. Use this to reference the VPC in other configurations."
value = aws_vpc.main.id
}
# Add README.md to modules
# modules/vpc/README.md
# VPC Module
Creates a VPC with public and private subnets across multiple AZs.
## Usage
module "vpc" { source = "./modules/vpc"
vpc_name = "my-vpc" vpc_cidr = "10.0.0.0/16" availability_zones = ["us-east-1a", "us-east-1b"] public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"] private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"] }
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|----------|
| vpc_name | Name of the VPC | string | - | yes |
| vpc_cidr | CIDR block for VPC | string | 10.0.0.0/16 | no |
## Outputs
| Name | Description |
|------|-------------|
| vpc_id | ID of the VPC |
| public_subnet_ids | IDs of public subnets |Testing Infrastructure
# Use terraform validate
terraform validate
# Use terraform plan
terraform plan -out=tfplan
# Use terraform fmt for consistent formatting
terraform fmt -recursive
# Use external tools
# tflint - Terraform linter
# checkov - Security scanner
# terraform-docs - Generate documentation
# terrascan - Policy scannerAdvanced Patterns
Dynamic Backend Configuration
# backend-config-dev.hcl
bucket = "terraform-state-dev"
key = "dev/terraform.tfstate"
region = "us-east-1"
# backend-config-prod.hcl
bucket = "terraform-state-prod"
key = "prod/terraform.tfstate"
region = "us-east-1"
# Initialize with backend config
# terraform init -backend-config=backend-config-dev.hclConditional Resource Creation
# Create resource only in production
resource "aws_cloudwatch_alarm" "high_cpu" {
count = var.environment == "prod" ? 1 : 0
alarm_name = "high-cpu-utilization"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 300
statistic = "Average"
threshold = 80
}Zero-Downtime Deployments
# Blue-Green deployment with create_before_destroy
resource "aws_autoscaling_group" "app" {
name = "${var.app_name}-${var.version}"
launch_configuration = aws_launch_configuration.app.name
min_size = var.min_size
max_size = var.max_size
lifecycle {
create_before_destroy = true
}
}
resource "aws_launch_configuration" "app" {
name_prefix = "${var.app_name}-"
image_id = var.ami_id
instance_type = var.instance_type
lifecycle {
create_before_destroy = true
}
}Moved Blocks for Refactoring
# Refactor without destroying resources
moved {
from = aws_instance.web
to = module.compute.aws_instance.web
}
moved {
from = aws_security_group.web[0]
to = aws_security_group.web["primary"]
}Import Existing Resources
# Import existing resource into Terraform state
terraform import aws_instance.web i-1234567890abcdef0
# Import with for_each
terraform import 'aws_instance.servers["web-1"]' i-1234567890abcdef0---
Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: Infrastructure as Code, Cloud Engineering, DevOps Compatible With: AWS, Azure, GCP, Kubernetes, Terraform Cloud
Terraform Infrastructure Examples
Comprehensive real-world examples demonstrating Terraform patterns, best practices, and multi-cloud infrastructure implementations.
Table of Contents
1. AWS VPC with Public and Private Subnets 2. AWS Three-Tier Web Application 3. AWS Auto-Scaling Web Application 4. AWS RDS PostgreSQL Database 5. Creating and Using Terraform Modules 6. Multi-Environment with Workspaces 7. Remote State with S3 Backend 8. Azure Virtual Network and Virtual Machines 9. Azure App Service with Database 10. Azure Kubernetes Service (AKS) 11. GCP Compute Instance and Network 12. GCP Google Kubernetes Engine (GKE) 13. Terraform Cloud Integration 14. Kubernetes Deployment with Terraform 15. Multi-Cloud Architecture 16. AWS Lambda Function with API Gateway 17. AWS ECS Fargate Application 18. Disaster Recovery Multi-Region Setup 19. Testing Infrastructure with Terraform 20. Complete GitOps Workflow
---
Example 1: AWS VPC with Public and Private Subnets
Create a production-ready VPC with public and private subnets across multiple availability zones.
Directory Structure
vpc-infrastructure/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvarsmain.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}
}
}
# VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
}
}
# Public Subnets
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-subnet-${count.index + 1}"
Type = "public"
}
}
# Private Subnets
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project_name}-private-subnet-${count.index + 1}"
Type = "private"
}
}
# Elastic IPs for NAT Gateways
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
domain = "vpc"
tags = {
Name = "${var.project_name}-nat-eip-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}
# NAT Gateways
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.project_name}-nat-gw-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}
# Public 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"
}
}
# Public Route Table Association
resource "aws_route_table_association" "public" {
count = length(var.public_subnet_cidrs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# Private Route Tables
resource "aws_route_table" "private" {
count = var.enable_nat_gateway ? length(var.private_subnet_cidrs) : 0
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${var.project_name}-private-rt-${count.index + 1}"
}
}
# Private Route Table Association
resource "aws_route_table_association" "private" {
count = var.enable_nat_gateway ? length(var.private_subnet_cidrs) : 0
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}variables.tf
variable "aws_region" {
type = string
description = "AWS region for resources"
default = "us-east-1"
}
variable "project_name" {
type = string
description = "Project name used for resource naming"
}
variable "environment" {
type = string
description = "Environment name (dev, staging, prod)"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "vpc_cidr" {
type = string
description = "CIDR block for VPC"
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "Must be a valid IPv4 CIDR block."
}
}
variable "availability_zones" {
type = list(string)
description = "List of availability zones"
default = ["us-east-1a", "us-east-1b", "us-east-1c"]
}
variable "public_subnet_cidrs" {
type = list(string)
description = "CIDR blocks for public subnets"
default = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}
variable "private_subnet_cidrs" {
type = list(string)
description = "CIDR blocks for private subnets"
default = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
}
variable "enable_nat_gateway" {
type = bool
description = "Enable NAT gateway for private subnets"
default = true
}outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "vpc_cidr" {
description = "CIDR block of the VPC"
value = aws_vpc.main.cidr_block
}
output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs of private subnets"
value = aws_subnet.private[*].id
}
output "nat_gateway_ids" {
description = "IDs of NAT gateways"
value = aws_nat_gateway.main[*].id
}
output "internet_gateway_id" {
description = "ID of the internet gateway"
value = aws_internet_gateway.main.id
}terraform.tfvars
aws_region = "us-east-1"
project_name = "myapp"
environment = "prod"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
enable_nat_gateway = trueUsage
terraform init
terraform plan
terraform apply---
Example 2: AWS Three-Tier Web Application
Complete infrastructure for a three-tier web application with load balancer, web servers, and database.
main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Data source for latest Amazon Linux 2 AMI
data "aws_ami" "amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Security Group for ALB
resource "aws_security_group" "alb" {
name = "${var.app_name}-alb-sg"
description = "Security group for Application Load Balancer"
vpc_id = var.vpc_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"]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.app_name}-alb-sg"
}
}
# Security Group for Web Servers
resource "aws_security_group" "web" {
name = "${var.app_name}-web-sg"
description = "Security group for web servers"
vpc_id = var.vpc_id
ingress {
description = "HTTP from ALB"
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
ingress {
description = "SSH from bastion or VPN"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.ssh_cidr_blocks
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.app_name}-web-sg"
}
}
# Security Group for Database
resource "aws_security_group" "database" {
name = "${var.app_name}-db-sg"
description = "Security group for database"
vpc_id = var.vpc_id
ingress {
description = "PostgreSQL from web servers"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.web.id]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.app_name}-db-sg"
}
}
# Application Load Balancer
resource "aws_lb" "main" {
name = "${var.app_name}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
enable_deletion_protection = var.enable_deletion_protection
tags = {
Name = "${var.app_name}-alb"
}
}
# Target Group
resource "aws_lb_target_group" "web" {
name = "${var.app_name}-tg"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
enabled = true
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 5
interval = 30
path = "/"
protocol = "HTTP"
matcher = "200"
}
tags = {
Name = "${var.app_name}-tg"
}
}
# ALB Listener
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.web.arn
}
}
# Launch Template
resource "aws_launch_template" "web" {
name_prefix = "${var.app_name}-lt-"
image_id = data.aws_ami.amazon_linux_2.id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.web.id]
user_data = base64encode(templatefile("${path.module}/user-data.sh", {
db_host = aws_db_instance.main.address
db_name = var.db_name
}))
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.app_name}-web-server"
}
}
lifecycle {
create_before_destroy = true
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "web" {
name = "${var.app_name}-asg"
vpc_zone_identifier = var.private_subnet_ids
target_group_arns = [aws_lb_target_group.web.arn]
health_check_type = "ELB"
health_check_grace_period = 300
min_size = var.min_size
max_size = var.max_size
desired_capacity = var.desired_capacity
launch_template {
id = aws_launch_template.web.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.app_name}-asg-instance"
propagate_at_launch = true
}
lifecycle {
create_before_destroy = true
}
}
# DB Subnet Group
resource "aws_db_subnet_group" "main" {
name = "${var.app_name}-db-subnet-group"
subnet_ids = var.private_subnet_ids
tags = {
Name = "${var.app_name}-db-subnet-group"
}
}
# RDS PostgreSQL Instance
resource "aws_db_instance" "main" {
identifier = "${var.app_name}-db"
engine = "postgres"
engine_version = "15.4"
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
storage_type = "gp3"
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.database.id]
backup_retention_period = var.backup_retention_period
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
multi_az = var.multi_az
skip_final_snapshot = var.skip_final_snapshot
deletion_protection = var.deletion_protection
tags = {
Name = "${var.app_name}-db"
}
}variables.tf
variable "aws_region" {
type = string
default = "us-east-1"
}
variable "app_name" {
type = string
description = "Application name"
}
variable "vpc_id" {
type = string
description = "VPC ID"
}
variable "public_subnet_ids" {
type = list(string)
description = "Public subnet IDs for ALB"
}
variable "private_subnet_ids" {
type = list(string)
description = "Private subnet IDs for web servers and database"
}
variable "ssh_cidr_blocks" {
type = list(string)
description = "CIDR blocks allowed to SSH"
default = ["10.0.0.0/16"]
}
variable "instance_type" {
type = string
default = "t3.micro"
}
variable "min_size" {
type = number
default = 2
}
variable "max_size" {
type = number
default = 6
}
variable "desired_capacity" {
type = number
default = 2
}
variable "enable_deletion_protection" {
type = bool
default = false
}
variable "db_instance_class" {
type = string
default = "db.t3.micro"
}
variable "db_allocated_storage" {
type = number
default = 20
}
variable "db_name" {
type = string
}
variable "db_username" {
type = string
}
variable "db_password" {
type = string
sensitive = true
}
variable "backup_retention_period" {
type = number
default = 7
}
variable "multi_az" {
type = bool
default = false
}
variable "skip_final_snapshot" {
type = bool
default = true
}
variable "deletion_protection" {
type = bool
default = false
}user-data.sh
#!/bin/bash
yum update -y
yum install -y httpd postgresql15
# Start Apache
systemctl start httpd
systemctl enable httpd
# Create test page
cat > /var/www/html/index.html <<EOF
<html>
<head><title>Three-Tier App</title></head>
<body>
<h1>Three-Tier Web Application</h1>
<p>Database: ${db_host}</p>
<p>Hostname: $(hostname)</p>
</body>
</html>
EOF
# Configure application
export DB_HOST="${db_host}"
export DB_NAME="${db_name}"---
Example 3: AWS Auto-Scaling Web Application
Advanced auto-scaling configuration with scheduled scaling and CloudWatch alarms.
main.tf
# Auto Scaling Policies
resource "aws_autoscaling_policy" "scale_up" {
name = "${var.app_name}-scale-up"
scaling_adjustment = 1
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = aws_autoscaling_group.web.name
}
resource "aws_autoscaling_policy" "scale_down" {
name = "${var.app_name}-scale-down"
scaling_adjustment = -1
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = aws_autoscaling_group.web.name
}
# CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
alarm_name = "${var.app_name}-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 120
statistic = "Average"
threshold = 70
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.web.name
}
alarm_description = "Scale up if CPU > 70%"
alarm_actions = [aws_autoscaling_policy.scale_up.arn]
}
resource "aws_cloudwatch_metric_alarm" "cpu_low" {
alarm_name = "${var.app_name}-cpu-low"
comparison_operator = "LessThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = 120
statistic = "Average"
threshold = 30
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.web.name
}
alarm_description = "Scale down if CPU < 30%"
alarm_actions = [aws_autoscaling_policy.scale_down.arn]
}
# Scheduled Scaling - Scale up during business hours
resource "aws_autoscaling_schedule" "scale_up_morning" {
scheduled_action_name = "${var.app_name}-scale-up-morning"
min_size = 4
max_size = 10
desired_capacity = 4
recurrence = "0 8 * * MON-FRI" # 8 AM weekdays
autoscaling_group_name = aws_autoscaling_group.web.name
}
# Scheduled Scaling - Scale down after business hours
resource "aws_autoscaling_schedule" "scale_down_evening" {
scheduled_action_name = "${var.app_name}-scale-down-evening"
min_size = 2
max_size = 4
desired_capacity = 2
recurrence = "0 18 * * MON-FRI" # 6 PM weekdays
autoscaling_group_name = aws_autoscaling_group.web.name
}
# Target Tracking Scaling Policy
resource "aws_autoscaling_policy" "target_tracking" {
name = "${var.app_name}-target-tracking"
autoscaling_group_name = aws_autoscaling_group.web.name
policy_type = "TargetTrackingScaling"
target_tracking_configuration {
predefined_metric_specification {
predefined_metric_type = "ASGAverageCPUUtilization"
}
target_value = 50.0
}
}---
Example 4: AWS RDS PostgreSQL Database
Production-ready RDS configuration with read replicas, backups, and monitoring.
main.tf
# DB Subnet Group
resource "aws_db_subnet_group" "main" {
name = "${var.app_name}-db-subnet-group"
subnet_ids = var.private_subnet_ids
tags = {
Name = "${var.app_name}-db-subnet-group"
}
}
# DB Parameter Group
resource "aws_db_parameter_group" "postgres" {
name = "${var.app_name}-postgres15"
family = "postgres15"
parameter {
name = "log_connections"
value = "1"
}
parameter {
name = "log_disconnections"
value = "1"
}
parameter {
name = "log_duration"
value = "1"
}
parameter {
name = "shared_preload_libraries"
value = "pg_stat_statements"
}
tags = {
Name = "${var.app_name}-postgres15-params"
}
}
# Security Group
resource "aws_security_group" "rds" {
name = "${var.app_name}-rds-sg"
description = "Security group for RDS database"
vpc_id = var.vpc_id
ingress {
description = "PostgreSQL from application"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = var.application_security_group_ids
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.app_name}-rds-sg"
}
}
# KMS Key for encryption
resource "aws_kms_key" "rds" {
description = "KMS key for RDS encryption"
deletion_window_in_days = 10
enable_key_rotation = true
tags = {
Name = "${var.app_name}-rds-key"
}
}
resource "aws_kms_alias" "rds" {
name = "alias/${var.app_name}-rds"
target_key_id = aws_kms_key.rds.key_id
}
# Primary RDS Instance
resource "aws_db_instance" "primary" {
identifier = "${var.app_name}-db-primary"
engine = "postgres"
engine_version = "15.4"
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
max_allocated_storage = var.db_max_allocated_storage
storage_type = "gp3"
storage_encrypted = true
kms_key_id = aws_kms_key.rds.arn
db_name = var.db_name
username = var.db_username
password = var.db_password
db_subnet_group_name = aws_db_subnet_group.main.name
parameter_group_name = aws_db_parameter_group.postgres.name
vpc_security_group_ids = [aws_security_group.rds.id]
# High availability
multi_az = var.multi_az
# Backups
backup_retention_period = var.backup_retention_period
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
copy_tags_to_snapshot = true
skip_final_snapshot = var.skip_final_snapshot
final_snapshot_identifier = var.skip_final_snapshot ? null : "${var.app_name}-db-final-snapshot-${formatdate("YYYY-MM-DD-hhmmss", timestamp())}"
# Monitoring
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
performance_insights_enabled = true
performance_insights_retention_period = 7
# Protection
deletion_protection = var.deletion_protection
tags = {
Name = "${var.app_name}-db-primary"
}
}
# Read Replica
resource "aws_db_instance" "replica" {
count = var.create_read_replica ? 1 : 0
identifier = "${var.app_name}-db-replica-${count.index + 1}"
replicate_source_db = aws_db_instance.primary.identifier
instance_class = var.db_replica_instance_class
# Override settings from primary
publicly_accessible = false
skip_final_snapshot = true
# Monitoring
monitoring_interval = 60
monitoring_role_arn = aws_iam_role.rds_monitoring.arn
tags = {
Name = "${var.app_name}-db-replica-${count.index + 1}"
}
}
# IAM Role for Enhanced Monitoring
resource "aws_iam_role" "rds_monitoring" {
name = "${var.app_name}-rds-monitoring-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "monitoring.rds.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "rds_monitoring" {
role = aws_iam_role.rds_monitoring.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}
# CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "database_cpu" {
alarm_name = "${var.app_name}-db-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/RDS"
period = 300
statistic = "Average"
threshold = 80
alarm_description = "Database CPU utilization is too high"
dimensions = {
DBInstanceIdentifier = aws_db_instance.primary.id
}
}
resource "aws_cloudwatch_metric_alarm" "database_storage" {
alarm_name = "${var.app_name}-db-storage-low"
comparison_operator = "LessThanThreshold"
evaluation_periods = 1
metric_name = "FreeStorageSpace"
namespace = "AWS/RDS"
period = 300
statistic = "Average"
threshold = 10737418240 # 10 GB in bytes
alarm_description = "Database free storage space is low"
dimensions = {
DBInstanceIdentifier = aws_db_instance.primary.id
}
}
resource "aws_cloudwatch_metric_alarm" "database_connections" {
alarm_name = "${var.app_name}-db-connections-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 2
metric_name = "DatabaseConnections"
namespace = "AWS/RDS"
period = 300
statistic = "Average"
threshold = 80
alarm_description = "Database connection count is high"
dimensions = {
DBInstanceIdentifier = aws_db_instance.primary.id
}
}outputs.tf
output "primary_endpoint" {
description = "Primary database endpoint"
value = aws_db_instance.primary.endpoint
}
output "primary_address" {
description = "Primary database address"
value = aws_db_instance.primary.address
}
output "replica_endpoints" {
description = "Read replica endpoints"
value = aws_db_instance.replica[*].endpoint
}
output "database_name" {
description = "Database name"
value = aws_db_instance.primary.db_name
}---
Example 5: Creating and Using Terraform Modules
Build reusable infrastructure modules for team-wide use.
Module Structure
modules/
└── ec2-instance/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
└── README.mdmodules/ec2-instance/main.tf
# Data source for AMI
data "aws_ami" "this" {
most_recent = true
owners = var.ami_owners
dynamic "filter" {
for_each = var.ami_filters
content {
name = filter.value.name
values = filter.value.values
}
}
}
# EC2 Instance
resource "aws_instance" "this" {
count = var.instance_count
ami = var.ami_id != "" ? var.ami_id : data.aws_ami.this.id
instance_type = var.instance_type
subnet_id = var.subnet_ids[count.index % length(var.subnet_ids)]
vpc_security_group_ids = var.security_group_ids
iam_instance_profile = var.iam_instance_profile
key_name = var.key_name
user_data = var.user_data
user_data_replace_on_change = var.user_data_replace_on_change
monitoring = var.enable_monitoring
root_block_device {
volume_type = var.root_volume_type
volume_size = var.root_volume_size
delete_on_termination = var.root_delete_on_termination
encrypted = var.root_encrypted
}
dynamic "ebs_block_device" {
for_each = var.ebs_volumes
content {
device_name = ebs_block_device.value.device_name
volume_type = ebs_block_device.value.volume_type
volume_size = ebs_block_device.value.volume_size
delete_on_termination = ebs_block_device.value.delete_on_termination
encrypted = ebs_block_device.value.encrypted
}
}
metadata_options {
http_endpoint = "enabled"
http_tokens = var.require_imdsv2 ? "required" : "optional"
http_put_response_hop_limit = 1
}
tags = merge(
var.tags,
{
Name = "${var.name}-${count.index + 1}"
}
)
lifecycle {
create_before_destroy = var.create_before_destroy
ignore_changes = var.lifecycle_ignore_changes
}
}
# Elastic IPs (optional)
resource "aws_eip" "this" {
count = var.create_eip ? var.instance_count : 0
instance = aws_instance.this[count.index].id
domain = "vpc"
tags = merge(
var.tags,
{
Name = "${var.name}-eip-${count.index + 1}"
}
)
}modules/ec2-instance/variables.tf
variable "name" {
type = string
description = "Name prefix for resources"
}
variable "instance_count" {
type = number
description = "Number of instances to create"
default = 1
}
variable "ami_id" {
type = string
description = "AMI ID (leave empty to use ami_filters)"
default = ""
}
variable "ami_owners" {
type = list(string)
description = "AMI owners for filtering"
default = ["amazon"]
}
variable "ami_filters" {
type = list(object({
name = string
values = list(string)
}))
description = "AMI filters"
default = [
{
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
},
{
name = "virtualization-type"
values = ["hvm"]
}
]
}
variable "instance_type" {
type = string
description = "Instance type"
default = "t3.micro"
}
variable "subnet_ids" {
type = list(string)
description = "Subnet IDs for instance placement"
}
variable "security_group_ids" {
type = list(string)
description = "Security group IDs"
}
variable "iam_instance_profile" {
type = string
description = "IAM instance profile name"
default = null
}
variable "key_name" {
type = string
description = "SSH key pair name"
default = null
}
variable "user_data" {
type = string
description = "User data script"
default = null
}
variable "user_data_replace_on_change" {
type = bool
description = "Replace instance when user data changes"
default = false
}
variable "enable_monitoring" {
type = bool
description = "Enable detailed monitoring"
default = false
}
variable "root_volume_type" {
type = string
description = "Root volume type"
default = "gp3"
}
variable "root_volume_size" {
type = number
description = "Root volume size in GB"
default = 20
}
variable "root_delete_on_termination" {
type = bool
description = "Delete root volume on instance termination"
default = true
}
variable "root_encrypted" {
type = bool
description = "Encrypt root volume"
default = true
}
variable "ebs_volumes" {
type = list(object({
device_name = string
volume_type = string
volume_size = number
delete_on_termination = bool
encrypted = bool
}))
description = "Additional EBS volumes"
default = []
}
variable "require_imdsv2" {
type = bool
description = "Require IMDSv2 for instance metadata"
default = true
}
variable "create_eip" {
type = bool
description = "Create and associate Elastic IPs"
default = false
}
variable "create_before_destroy" {
type = bool
description = "Create replacement before destroying"
default = false
}
variable "lifecycle_ignore_changes" {
type = list(string)
description = "Lifecycle ignore changes"
default = []
}
variable "tags" {
type = map(string)
description = "Tags to apply to resources"
default = {}
}modules/ec2-instance/outputs.tf
output "instance_ids" {
description = "Instance IDs"
value = aws_instance.this[*].id
}
output "instance_private_ips" {
description = "Private IP addresses"
value = aws_instance.this[*].private_ip
}
output "instance_public_ips" {
description = "Public IP addresses"
value = aws_instance.this[*].public_ip
}
output "eip_public_ips" {
description = "Elastic IP addresses"
value = aws_eip.this[*].public_ip
}
output "instance_arns" {
description = "Instance ARNs"
value = aws_instance.this[*].arn
}Using the Module
# Root main.tf
module "web_servers" {
source = "./modules/ec2-instance"
name = "web-server"
instance_count = 3
instance_type = "t3.small"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.deployer.key_name
user_data = templatefile("${path.module}/web-user-data.sh", {
environment = "production"
})
root_volume_size = 30
enable_monitoring = true
tags = {
Environment = "production"
Tier = "web"
ManagedBy = "Terraform"
}
}
module "app_servers" {
source = "./modules/ec2-instance"
name = "app-server"
instance_count = 2
instance_type = "t3.medium"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [aws_security_group.app.id]
ebs_volumes = [
{
device_name = "/dev/sdf"
volume_type = "gp3"
volume_size = 100
delete_on_termination = true
encrypted = true
}
]
tags = {
Environment = "production"
Tier = "application"
ManagedBy = "Terraform"
}
}
# Use module outputs
output "web_server_ips" {
value = module.web_servers.instance_private_ips
}---
Example 6: Multi-Environment with Workspaces
Manage multiple environments using Terraform workspaces.
main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
backend "s3" {
bucket = "myapp-terraform-state"
key = "infrastructure/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Environment = terraform.workspace
ManagedBy = "Terraform"
Workspace = terraform.workspace
}
}
}
# Environment-specific configuration
locals {
environment_config = {
dev = {
vpc_cidr = "10.0.0.0/16"
instance_type = "t3.micro"
instance_count = 1
db_instance_class = "db.t3.micro"
multi_az = false
backup_retention = 1
enable_monitoring = false
}
staging = {
vpc_cidr = "10.1.0.0/16"
instance_type = "t3.small"
instance_count = 2
db_instance_class = "db.t3.small"
multi_az = false
backup_retention = 3
enable_monitoring = true
}
prod = {
vpc_cidr = "10.2.0.0/16"
instance_type = "t3.large"
instance_count = 5
db_instance_class = "db.t3.large"
multi_az = true
backup_retention = 7
enable_monitoring = true
}
}
config = local.environment_config[terraform.workspace]
common_tags = {
Project = var.project_name
Environment = terraform.workspace
ManagedBy = "Terraform"
}
}
# VPC
resource "aws_vpc" "main" {
cidr_block = local.config.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${terraform.workspace}-vpc"
}
)
}
# Application Instances
resource "aws_instance" "app" {
count = local.config.instance_count
ami = data.aws_ami.amazon_linux_2.id
instance_type = local.config.instance_type
monitoring = local.config.enable_monitoring
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${terraform.workspace}-app-${count.index + 1}"
}
)
}
# Database
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-${terraform.workspace}-db"
engine = "postgres"
engine_version = "15.4"
instance_class = local.config.db_instance_class
allocated_storage = 20
db_name = var.db_name
username = var.db_username
password = var.db_password
multi_az = local.config.multi_az
backup_retention_period = local.config.backup_retention
skip_final_snapshot = terraform.workspace != "prod"
tags = merge(
local.common_tags,
{
Name = "${var.project_name}-${terraform.workspace}-db"
}
)
}Usage
# Create workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
# Deploy to dev
terraform workspace select dev
terraform plan
terraform apply
# Deploy to staging
terraform workspace select staging
terraform plan
terraform apply
# Deploy to production
terraform workspace select prod
terraform plan
terraform apply
# List workspaces
terraform workspace list
# Show current workspace
terraform workspace show---
Example 7: Remote State with S3 Backend
Configure remote state storage with locking for team collaboration.
Step 1: Create State Backend Resources
# bootstrap/main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# S3 Bucket for Terraform State
resource "aws_s3_bucket" "terraform_state" {
bucket = "${var.project_name}-terraform-state"
lifecycle {
prevent_destroy = true
}
tags = {
Name = "Terraform State Bucket"
Project = var.project_name
ManagedBy = "Terraform"
}
}
# Enable versioning
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# Enable encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.terraform_state.arn
}
}
}
# Block public access
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Lifecycle policy
resource "aws_s3_bucket_lifecycle_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
id = "expire-old-versions"
status = "Enabled"
noncurrent_version_expiration {
noncurrent_days = 90
}
}
}
# KMS Key for encryption
resource "aws_kms_key" "terraform_state" {
description = "KMS key for Terraform state encryption"
deletion_window_in_days = 10
enable_key_rotation = true
tags = {
Name = "Terraform State Key"
Project = var.project_name
}
}
resource "aws_kms_alias" "terraform_state" {
name = "alias/${var.project_name}-terraform-state"
target_key_id = aws_kms_key.terraform_state.key_id
}
# DynamoDB Table for State Locking
resource "aws_dynamodb_table" "terraform_locks" {
name = "${var.project_name}-terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform State Lock Table"
Project = var.project_name
ManagedBy = "Terraform"
}
}
# Outputs
output "state_bucket_name" {
description = "S3 bucket name for Terraform state"
value = aws_s3_bucket.terraform_state.id
}
output "state_lock_table_name" {
description = "DynamoDB table name for state locking"
value = aws_dynamodb_table.terraform_locks.id
}Step 2: Bootstrap State Backend
# Initialize and create state backend resources
cd bootstrap
terraform init
terraform apply
# Note the bucket and table names from outputsStep 3: Configure Backend in Main Project
# backend.tf
terraform {
backend "s3" {
bucket = "myproject-terraform-state"
key = "production/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "myproject-terraform-locks"
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
}
}Step 4: Use Backend Configuration Files
# backend-config-dev.hcl
bucket = "myproject-terraform-state"
key = "dev/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "myproject-terraform-locks"
# backend-config-staging.hcl
bucket = "myproject-terraform-state"
key = "staging/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "myproject-terraform-locks"
# backend-config-prod.hcl
bucket = "myproject-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "myproject-terraform-locks"Usage
# Initialize with specific backend config
terraform init -backend-config=backend-config-dev.hcl
# Migrate existing local state to remote
terraform init -migrate-state
# Verify remote state
terraform state list
# Pull remote state
terraform state pull > terraform.tfstate.backup---
Example 8: Azure Virtual Network and Virtual Machines
Deploy Azure infrastructure with VNet, subnets, and virtual machines.
main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = true
}
virtual_machine {
delete_os_disk_on_deletion = true
graceful_shutdown = false
skip_shutdown_and_force_delete = false
}
}
}
# Resource Group
resource "azurerm_resource_group" "main" {
name = "${var.project_name}-rg"
location = var.azure_region
tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
}
}
# Virtual Network
resource "azurerm_virtual_network" "main" {
name = "${var.project_name}-vnet"
address_space = [var.vnet_cidr]
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
tags = azurerm_resource_group.main.tags
}
# Subnets
resource "azurerm_subnet" "web" {
name = "${var.project_name}-web-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.web_subnet_cidr]
}
resource "azurerm_subnet" "app" {
name = "${var.project_name}-app-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.app_subnet_cidr]
}
resource "azurerm_subnet" "data" {
name = "${var.project_name}-data-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.data_subnet_cidr]
}
# Network Security Group
resource "azurerm_network_security_group" "web" {
name = "${var.project_name}-web-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 = var.ssh_source_address
destination_address_prefix = "*"
}
tags = azurerm_resource_group.main.tags
}
# Associate NSG with subnet
resource "azurerm_subnet_network_security_group_association" "web" {
subnet_id = azurerm_subnet.web.id
network_security_group_id = azurerm_network_security_group.web.id
}
# Public IP
resource "azurerm_public_ip" "web" {
count = var.vm_count
name = "${var.project_name}-web-pip-${count.index + 1}"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
allocation_method = "Static"
sku = "Standard"
tags = azurerm_resource_group.main.tags
}
# Network Interface
resource "azurerm_network_interface" "web" {
count = var.vm_count
name = "${var.project_name}-web-nic-${count.index + 1}"
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[count.index].id
}
tags = azurerm_resource_group.main.tags
}
# Virtual Machines
resource "azurerm_linux_virtual_machine" "web" {
count = var.vm_count
name = "${var.project_name}-web-vm-${count.index + 1}"
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[count.index].id,
]
admin_ssh_key {
username = var.admin_username
public_key = file(var.ssh_public_key_path)
}
os_disk {
name = "${var.project_name}-web-osdisk-${count.index + 1}"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
custom_data = base64encode(file("${path.module}/cloud-init.yaml"))
tags = merge(
azurerm_resource_group.main.tags,
{
Name = "${var.project_name}-web-vm-${count.index + 1}"
}
)
}
# Load Balancer
resource "azurerm_lb" "web" {
name = "${var.project_name}-lb"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
sku = "Standard"
frontend_ip_configuration {
name = "PublicIPAddress"
public_ip_address_id = azurerm_public_ip.lb.id
}
tags = azurerm_resource_group.main.tags
}
resource "azurerm_public_ip" "lb" {
name = "${var.project_name}-lb-pip"
location = azurerm_resource_group.main.location
resource_group_name = azurerm_resource_group.main.name
allocation_method = "Static"
sku = "Standard"
tags = azurerm_resource_group.main.tags
}
resource "azurerm_lb_backend_address_pool" "web" {
loadbalancer_id = azurerm_lb.web.id
name = "WebBackendPool"
}
resource "azurerm_network_interface_backend_address_pool_association" "web" {
count = var.vm_count
network_interface_id = azurerm_network_interface.web[count.index].id
ip_configuration_name = "internal"
backend_address_pool_id = azurerm_lb_backend_address_pool.web.id
}
resource "azurerm_lb_probe" "web" {
loadbalancer_id = azurerm_lb.web.id
name = "http-probe"
port = 80
protocol = "Http"
request_path = "/"
}
resource "azurerm_lb_rule" "web" {
loadbalancer_id = azurerm_lb.web.id
name = "HTTPRule"
protocol = "Tcp"
frontend_port = 80
backend_port = 80
frontend_ip_configuration_name = "PublicIPAddress"
backend_address_pool_ids = [azurerm_lb_backend_address_pool.web.id]
probe_id = azurerm_lb_probe.web.id
}variables.tf
variable "azure_region" {
type = string
default = "East US"
}
variable "project_name" {
type = string
}
variable "environment" {
type = string
default = "production"
}
variable "vnet_cidr" {
type = string
default = "10.0.0.0/16"
}
variable "web_subnet_cidr" {
type = string
default = "10.0.1.0/24"
}
variable "app_subnet_cidr" {
type = string
default = "10.0.2.0/24"
}
variable "data_subnet_cidr" {
type = string
default = "10.0.3.0/24"
}
variable "ssh_source_address" {
type = string
description = "Source IP address for SSH access"
default = "*"
}
variable "vm_count" {
type = number
default = 2
}
variable "vm_size" {
type = string
default = "Standard_B2s"
}
variable "admin_username" {
type = string
default = "azureuser"
}
variable "ssh_public_key_path" {
type = string
description = "Path to SSH public key"
default = "~/.ssh/id_rsa.pub"
}---
This file contains 8 comprehensive examples covering AWS VPC, three-tier applications, auto-scaling, RDS databases, modules, workspaces, remote state, and Azure infrastructure. The next part will continue with Examples 9-20 covering Azure App Service, AKS, GCP, Kubernetes, multi-cloud, Lambda, ECS, disaster recovery, testing, and GitOps workflows.
Would you like me to continue with the remaining 12 examples?
Terraform Infrastructure-as-Code Skill
Comprehensive Terraform skill for building, managing, and scaling cloud infrastructure using declarative configuration and enterprise-grade patterns.
Overview
Terraform is an open-source infrastructure-as-code (IaC) tool that enables you to define and provision data center infrastructure using a declarative configuration language. This skill provides comprehensive knowledge of Terraform patterns, best practices, and real-world implementations across AWS, Azure, GCP, and other cloud providers.
What is Terraform?
Terraform allows you to:
- Define Infrastructure as Code: Write infrastructure configuration in human-readable HCL (HashiCorp Configuration Language)
- Manage Multi-Cloud Resources: Support for 1000+ providers including AWS, Azure, GCP, Kubernetes, and more
- Version Control Infrastructure: Track infrastructure changes in Git like application code
- Automate Provisioning: Eliminate manual infrastructure setup and configuration
- Preview Changes: See what will change before applying modifications
- Collaborate Safely: Share state and coordinate team changes with remote backends
Key Features
Declarative Configuration
Define the desired end state of your infrastructure, and Terraform determines the steps to achieve it:
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "WebServer"
}
}Multi-Cloud Support
Manage resources across multiple cloud providers in a single configuration:
- AWS: EC2, S3, RDS, Lambda, ECS, EKS, VPC, Route53, CloudFront, etc.
- Azure: Virtual Machines, Storage, SQL Database, App Service, AKS, etc.
- GCP: Compute Engine, Cloud Storage, Cloud SQL, GKE, etc.
- Kubernetes: Deployments, Services, ConfigMaps, Secrets, etc.
- 100+ other providers: GitHub, Datadog, PagerDuty, Cloudflare, etc.
Resource Graph
Terraform builds a dependency graph of resources and executes operations in parallel when possible:
VPC → Subnets → Security Groups → EC2 Instances
↓ ↓ ↓ ↓
Internet Gateway Route Tables Load BalancerState Management
Terraform tracks infrastructure state to detect drift and plan changes:
- Local State: Simple single-user workflows
- Remote State: Team collaboration with S3, Azure Storage, GCS, Terraform Cloud
- State Locking: Prevent concurrent modifications with DynamoDB, Azure Blob, etc.
- State Versioning: Rollback capability with S3 versioning
Modules
Create reusable infrastructure components:
module "vpc" {
source = "./modules/vpc"
vpc_name = "production"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b"]
}
# Use module outputs
resource "aws_instance" "web" {
subnet_id = module.vpc.public_subnet_ids[0]
}Core Workflow
1. Write
Create .tf files with your infrastructure configuration:
# main.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "data" {
bucket = "my-application-data"
}2. Initialize
Download provider plugins and prepare the working directory:
terraform init3. Plan
Preview changes before applying:
terraform planOutput shows:
- Resources to be created (+)
- Resources to be modified (~)
- Resources to be destroyed (-)
4. Apply
Execute the planned changes:
terraform apply5. Destroy (when needed)
Remove infrastructure when no longer needed:
terraform destroyInstallation
macOS
# Using Homebrew
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
# Verify installation
terraform versionLinux
# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
# RHEL/CentOS/Fedora
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install terraformWindows
# Using Chocolatey
choco install terraform
# Using Scoop
scoop install terraformDocker
docker run -it --rm hashicorp/terraform:latest versionQuick Start
Example 1: AWS S3 Bucket
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "example" {
bucket = "my-unique-bucket-name-12345"
tags = {
Name = "My bucket"
Environment = "Dev"
}
}
resource "aws_s3_bucket_versioning" "example" {
bucket = aws_s3_bucket.example.id
versioning_configuration {
status = "Enabled"
}
}
output "bucket_name" {
value = aws_s3_bucket.example.id
}Run:
terraform init
terraform plan
terraform applyExample 2: AWS EC2 Instance
# variables.tf
variable "instance_type" {
type = string
default = "t3.micro"
}
# main.tf
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
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
tags = {
Name = "WebServer"
}
}
# outputs.tf
output "instance_id" {
value = aws_instance.web.id
}
output "instance_public_ip" {
value = aws_instance.web.public_ip
}Example 3: Multi-Environment Setup
# Create workspaces for environments
# terraform workspace new dev
# terraform workspace new staging
# terraform workspace new prod
locals {
environment_config = {
dev = {
instance_type = "t3.micro"
instance_count = 1
}
staging = {
instance_type = "t3.small"
instance_count = 2
}
prod = {
instance_type = "t3.large"
instance_count = 5
}
}
config = local.environment_config[terraform.workspace]
}
resource "aws_instance" "app" {
count = local.config.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = local.config.instance_type
tags = {
Name = "app-${terraform.workspace}-${count.index + 1}"
Environment = terraform.workspace
}
}Project Structure
Basic Project
my-infrastructure/
├── main.tf # Primary resources
├── variables.tf # Input variables
├── outputs.tf # Output values
├── terraform.tfvars # Variable values (add to .gitignore if sensitive)
├── versions.tf # Provider version constraints
└── README.md # DocumentationModule-Based Project
terraform-infrastructure/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── terraform.tfvars
│ │ └── backend.tf
│ ├── staging/
│ │ └── ...
│ └── production/
│ └── ...
├── modules/
│ ├── vpc/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── README.md
│ ├── compute/
│ │ └── ...
│ ├── database/
│ │ └── ...
│ └── networking/
│ └── ...
├── global/
│ ├── iam/
│ │ └── main.tf
│ └── route53/
│ └── main.tf
└── README.mdEssential Commands
Initialization and Planning
# Initialize working directory
terraform init
# Initialize with backend config
terraform init -backend-config=backend.hcl
# Upgrade provider plugins
terraform init -upgrade
# Validate configuration syntax
terraform validate
# Format code (auto-fix)
terraform fmt -recursive
# Preview changes
terraform plan
# Save plan to file
terraform plan -out=tfplan
# Plan with specific variables
terraform plan -var="instance_type=t3.large"Applying Changes
# Apply changes (with confirmation)
terraform apply
# Apply saved plan (no confirmation)
terraform apply tfplan
# Apply with auto-approve
terraform apply -auto-approve
# Apply with variable file
terraform apply -var-file=production.tfvars
# Target specific resource
terraform apply -target=aws_instance.webState Management
# List resources in state
terraform state list
# Show resource details
terraform state show aws_instance.web
# Move resource in state
terraform state mv aws_instance.old aws_instance.new
# Remove resource from state (keeps actual resource)
terraform state rm aws_instance.web
# Pull remote state
terraform state pull
# Push local state to remote
terraform state push
# Refresh state from real infrastructure
terraform refreshWorkspace Management
# List workspaces
terraform workspace list
# Create new workspace
terraform workspace new production
# Select workspace
terraform workspace select production
# Show current workspace
terraform workspace show
# Delete workspace
terraform workspace delete stagingImporting and Output
# Import existing resource
terraform import aws_instance.web i-1234567890abcdef0
# Show all outputs
terraform output
# Show specific output
terraform output instance_ip
# Output in JSON format
terraform output -jsonDestruction
# Destroy all resources (with confirmation)
terraform destroy
# Destroy with auto-approve
terraform destroy -auto-approve
# Destroy specific resource
terraform destroy -target=aws_instance.webConfiguration Files
.gitignore
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files (may 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
# Lock files (commit .terraform.lock.hcl to version control)
# .terraform.lock.hclterraform.tfvars.example
# Copy this file to terraform.tfvars and fill in your values
aws_region = "us-east-1"
environment = "production"
project_name = "my-app"
instance_type = "t3.micro"
database_password = "CHANGE_ME"Environment Variables
# AWS credentials
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"
export AWS_DEFAULT_REGION="us-east-1"
# Azure credentials
export ARM_CLIENT_ID="00000000-0000-0000-0000-000000000000"
export ARM_CLIENT_SECRET="your-client-secret"
export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
export ARM_TENANT_ID="00000000-0000-0000-0000-000000000000"
# GCP credentials
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"
export GOOGLE_PROJECT="your-project-id"
# Terraform-specific
export TF_LOG=DEBUG # Enable debug logging (TRACE, DEBUG, INFO, WARN, ERROR)
export TF_LOG_PATH="terraform.log" # Log to file
export TF_VAR_instance_type="t3.large" # Set variable via environmentBest Practices
1. Version Control Everything
- Commit all
.tffiles to Git - Use
.gitignorefor state files and sensitive data - Tag releases for infrastructure versions
- Use pull requests for infrastructure changes
2. Use Remote State
- Never commit state files to Git
- Use S3, Azure Storage, or Terraform Cloud for state
- Enable state locking to prevent concurrent modifications
- Enable state encryption for sensitive data
3. Organize with Modules
- Create reusable modules for common patterns
- Use Terraform Registry for community modules
- Version your modules
- Document module inputs and outputs
4. Implement Security
- Never hardcode credentials
- Use IAM roles and managed identities when possible
- Mark sensitive variables with
sensitive = true - Scan code with security tools (checkov, tfsec, terrascan)
- Encrypt state files
5. Test Before Applying
- Always run
terraform planbeforeapply - Review plan output carefully
- Use
terraform validateandterraform fmt - Test in dev environment first
- Use
-targetfor incremental changes
6. Use Workspaces for Environments
- Separate dev/staging/prod with workspaces or directories
- Use consistent naming conventions
- Implement environment-specific configurations
- Avoid sharing state between environments
7. Document Your Code
- Add descriptions to variables and outputs
- Include README.md in modules
- Use comments to explain complex logic
- Keep documentation up to date
Common Use Cases
1. AWS Infrastructure: EC2, VPC, RDS, S3, Lambda, ECS/EKS 2. Azure Resources: Virtual Machines, App Service, SQL Database, AKS 3. GCP Deployments: Compute Engine, GKE, Cloud SQL, Cloud Storage 4. Kubernetes Management: Deployments, Services, Ingress, ConfigMaps 5. Multi-Cloud Architectures: Resources across multiple providers 6. Disaster Recovery: Multi-region deployments 7. GitOps Workflows: Infrastructure changes via pull requests 8. Compliance as Code: Policy enforcement with Sentinel or OPA
Learning Resources
Official Documentation
Provider Documentation
Community Resources
Tools and Extensions
- tflint: Terraform linter
- checkov: Security scanner
- terraform-docs: Documentation generator
- terrascan: Policy as code scanner
- infracost: Cost estimation
- terragrunt: Terraform wrapper for DRY configurations
- VS Code Terraform Extension: Syntax highlighting and IntelliSense
Getting Help
If you encounter issues:
1. Run terraform validate to check syntax 2. Check provider documentation for resource arguments 3. Review Terraform logs with TF_LOG=DEBUG 4. Search Terraform Registry for modules 5. Ask questions on HashiCorp Discuss 6. Review GitHub Issues
Next Steps
After mastering the basics:
1. Explore advanced state management with Terraform Cloud 2. Implement CI/CD pipelines with Terraform 3. Create custom providers for internal systems 4. Study Sentinel policies for governance 5. Build a module library for your organization 6. Implement GitOps workflows 7. Explore Terratest for infrastructure testing 8. Learn about drift detection and remediation
---
Version: 1.0.0 Last Updated: October 2025 Maintainer: Infrastructure Team
For detailed examples and patterns, see EXAMPLES.md. For complete skill documentation, see SKILL.md.