Pages

Terraform

Terraform is an open-source Infrastructure as Code (IaC) tool created by HashiCorp that lets you define and manage infrastructure using configuration files instead of manual setup.

Terraform uses a declarative language (HCL – HashiCorp Configuration Language).

Terraform builds a dependency graph and executes resources in the correct order automatically.

Install Terraform:
Install yum-config-manager to manage your repositories.
$ sudo yum install -y yum-utils
Use yum-config-manager to add the official HashiCorp RHEL repository.
$ sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
Install Terraform from the new repository.
$ sudo yum -y install terraform
Verify the Installation
$ terraform -help

Terraform works in a lifecycle workflow:
Write config → Initialize → Validate → Plan → Apply → Manage state → Destroy (if needed)

terraform init → Initialize Project
  • Downloads required provider plugins (AWS, Azure, etc.)
  • Initializes backend (where state file is stored)
  • Creates hidden .terraform/ directory
  • Downloads modules
  • Prepares working directory
terraform validate → Validate Configuration
  • Checks syntax and internal consistency
  • Does NOT access cloud provider
terraform fmt → Format Code
Automatically formats .tf files to standard style

terraform plan → Preview Changes
  • Reads .tf configuration files
  • Reads current state file
  • Refreshes real infrastructure state
  • Connects to cloud provider (e.g., Amazon Web Services)
  • Shows execution plan (create/update/delete)
terraform apply → Apply Changes
  • Re-runs plan
  • Asks for confirmation (unless -auto-approve)
  • Calls provider APIs
  • Creates/updates/deletes resources
  • Updates terraform.tfstate file
terraform destroy → Remove Infrastructure
  • Reads state file
  • Creates destruction plan
  • Calls provider APIs to delete resources
  • Updates or removes state
terraform import → Import Existing Resource
Brings manually created infrastructure into Terraform state

terraform workspace → Manage Environments
Create/select isolated state environments (dev/qa/prod)
==========================================================================
Terraform Core Concepts:

Provider ---> Plugin that interacts with cloud APIs
Resource ---> Infrastructure object (EC2, VNet, VM, etc.)
Data Source ---> Read-only lookup of existing resources
Module ---> Reusable Terraform configuration
State File ---> Maps configuration to real infrastructure
Backend ---> Where state is stored (local or remote)
Provisioner ---> Executes scripts on resources (last resort)
Variables ---> Input parameters
Outputs ---> Return values after apply
Locals ---> Computed reusable values
Dependency Graph ---> Determines execution order automatically

=========================================================================
Terraform State Management:

terraform.tfstate contains:
  • Resource mappings
  • Metadata
  • Sometimes sensitive values
Remote backends recommended:
AWS ---> S3 + DynamoDB locking
Azure ---> Blob Storage backend
GCP ---> GCS backend

State Locking prevents concurrent modifications.

Sensitive variables should use:
sensitive = true
==========================================================================
Terraform configuration files and structure components:
(Note: File names are conventions; Terraform loads all .tf files automatically.)

main.tf ---> Primary configuration (resources + modules wiring)
variables.tf ---> Input variable definitions
terraform.tfvars ---> Variable values
backend.tf ---> Remote backend configuration
providers.tf ---> Provider configuration & authentication
outputs.tf ---> Output values
data.tf ---> Data sources
locals.tf ---> Local computed values
versions.tf ---> Required Terraform & provider versions
scripts/ ---> User data or bootstrap scripts
modules/ ---> Reusable Terraform modules

=========================================================================
Terraform Variable Types:
string ---> Text value
number ---> Integer or float
bool ---> true / false
list(type) ---> Ordered same-type values
map(type) ---> Key-value same-type values
set(type) ---> Unordered unique values
object({}) ---> Structured attributes
tuple([]) ---> Fixed position mixed types
any ---> Any type

Additional Features:
optional() ---> Optional object attributes
validation {} ---> Custom validation rules
nullable ---> Allow null values
=========================================================================

Complete structured list of Terraform input variables for AWS, Azure, GCP, and VMware:

==================================AWS Inputs ==================================
Provider: Amazon Web Services

aws_region --> string --> AWS region
env --> string --> Environment name (sandbox, qa, prod)
tags --> map(string) --> Common resource tags
tfstate_bucket --> string --> S3 remote state bucket
tfstate_lock_table --> string --> DynamoDB lock table
vpc_cidr --> string --> VPC CIDR
public_subnets --> list(string) --> Public subnet CIDRs
app_subnets --> list(string) --> App subnet CIDRs
db_subnets --> list(string) --> DB subnet CIDRs
azs --> list(string) --> Availability Zones
enable_nat_gateway --> bool --> Enable NAT
nat_gateway_count --> number --> Number of NAT gateways
enable_flow_logs --> bool --> Enable flow logs
kms_key_id --> string --> KMS key for encryption
golden_ami_id --> string --> AMI ID
instance_type --> string --> EC2 type
iam_role_arn --> string --> IAM role
asg_min_size --> number --> ASG min
asg_max_size --> number --> ASG max
asg_desired_count --> number --> ASG desired
rds_engine --> string --> DB engine
rds_instance_class --> string --> DB class
rds_storage_gb --> number --> DB storage
enable_rds_multi_az --> bool --> Multi-AZ RDS
backup_retention_days --> number --> RDS backup retention

=================================== Azure Inputs ===================================
Provider: Microsoft Azure

azure_subscription_id --> string --> Azure subscription ID
azure_tenant_id --> string --> Tenant ID
azure_client_id --> string --> Service principal ID
azure_client_secret --> string --> Service principal secret
azure_location --> string --> Region
azure_resource_group --> string --> Resource group
azure_vnet_cidr --> string --> VNet CIDR
azure_subnets_public --> list(string) --> Public subnets
azure_subnets_app --> list(string) --> App subnets
azure_subnets_db --> list(string) --> DB subnets
azure_vm_size --> string --> VM size
azure_admin_username --> string --> Admin username
azure_admin_password --> string --> Admin password
azure_vm_count --> number --> VM count
azure_tags --> map(string) --> Resource tags
azure_enable_diagnostics --> bool --> Enable diagnostics

================================= GCP Inputs =================================
Provider: Google Cloud Platform
gcp_project_id --> string --> Project ID
gcp_region --> string --> Region
gcp_zone --> string --> Zone
gcp_credentials_file --> string --> Credentials JSON
gcp_network_cidr --> string --> VPC CIDR
gcp_subnets_public --> list(string) --> Public subnets
gcp_subnets_app --> list(string) --> App subnets
gcp_subnets_db --> list(string) --> DB subnets
gcp_instance_type --> string --> VM type
gcp_machine_image --> string --> Image
gcp_disk_size_gb --> number --> Disk size
gcp_disk_type --> string --> Disk type
gcp_vm_count --> number --> VM count
gcp_autoscaling_enabled --> bool --> Autoscaling
gcp_labels --> map(string) --> Resource labels

=============================== VMware Inputs ===============================
Provider: VMware
vsphere_server --> string --> vCenter server
vsphere_user --> string --> Username
vsphere_password --> string --> Password
vsphere_allow_unverified_ssl--> bool --> Allow self-signed SSL
vmware_datacenter --> string --> Datacenter
vmware_cluster --> string --> Cluster
vmware_datastore --> string --> Datastore
vmware_resource_pool --> string --> Resource pool
vmware_network_cidr --> string --> Network CIDR
vmware_template --> string --> VM template
vmware_vm_size --> string --> VM size
vmware_vm_count --> number --> VM count
vmware_dns_servers --> list(string) --> DNS servers
vmware_gateway --> string --> Default gateway
=======================================================================================================================
Enterprise AWS Terraform Landing Zone Structure:
Example:
terraform-aws-landing-zone/
├── modules/
│   ├── network/             # VPC + Subnets (web/app/db) + IGW + NAT HA (4 AZ)
│   ├── nacl/                # Public & private NACLs
│   ├── tgw/                 # Transit Gateway + route propagation + TGW associations/peering
│   ├── flowlogs/            # VPC Flow Logs
│   ├── segmentation/        # Web/App/DB subnet segmentation (optional)
│   ├── security-groups/     # Tiered SG model (Web/App/DB)
│   ├── alb/                 # Application Load Balancer + WAF + ACM
│   ├── nlb/                 # Network Load Balancer
│   ├── compute/             # EC2 + ASG + Launch Template + Golden AMI
│   ├── bastion/             # Bastion / Jump Box
│   ├── cloudwatch-alarms/   # CPU / Memory / Custom CloudWatch alarms
│   ├── pacemaker/           # Overlay/floating IP HA cluster
│   ├── inspection-vpc/      # Firewall / inspection VPC + public/private subnets
│   └── vpc-connection/      # VPC Peering / VPN module
├── envs/
│   ├── project-template/    # Template environment (sandbox)
│   │   ├── main.tf          # Wire all modules together
│   │   ├── variables.tf     # Environment variables
│   │   ├── terraform.tfvars # Example values for sendbox
│   │   ├── backend.tf       # Remote state S3 + DynamoDB
│   │   └── scripts/         # Scripts like pacemaker_setup.sh
└── global/
    ├── accounts.tf          # Hub-Spoke multi-account mapping
    ├── backend.tf           # Shared backend configuration (optional)
    └── providers.tf         # Shared provider configuration (optional)
=======================================================================================================================
Example: modules/network/main.tf

# VPC
resource "aws_vpc" "this" {
  cidr_block           = var.vpc_cidr  # change as needed
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name        = "${var.env}-vpc"  # change env in variables.tf or tfvars
    Environment = var.env           # change as needed
  }
}

# Internet Gateway
resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.this.id
  tags = {
    Name        = "${var.env}-igw"  # change env in variables.tf or tfvars
    Environment = var.env           # change as needed
  }
}

# Public Subnets
resource "aws_subnet" "public" {
  for_each = { for idx, cidr in var.public_subnets : idx => cidr }
  vpc_id                  = aws_vpc.this.id
  cidr_block              = each.value  # change in tfvars
  availability_zone       = var.azs[each.key]  # change in tfvars
  map_public_ip_on_launch = true
  tags = {
    Name        = "${var.env}-public-${each.key}"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

# App (Private) Subnets
resource "aws_subnet" "app" {
  for_each = { for idx, cidr in var.app_subnets : idx => cidr }
  vpc_id                  = aws_vpc.this.id
  cidr_block              = each.value  # change in tfvars
  availability_zone       = var.azs[each.key]  # change in tfvars
  map_public_ip_on_launch = false
  tags = {
    Name        = "${var.env}-app-${each.key}"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

# DB (Private) Subnets
resource "aws_subnet" "db" {
  for_each = { for idx, cidr in var.db_subnets : idx => cidr }
  vpc_id                  = aws_vpc.this.id
  cidr_block              = each.value  # change in tfvars
  availability_zone       = var.azs[each.key]  # change in tfvars
  map_public_ip_on_launch = false
  tags = {
    Name        = "${var.env}-db-${each.key}"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

# NAT Gateways (HA)
resource "aws_eip" "nat" {
  for_each = aws_subnet.public
  vpc = true
  tags = {
    Name        = "${var.env}-nat-${each.key}"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

resource "aws_nat_gateway" "this" {
  for_each = aws_subnet.public
  allocation_id = aws_eip.nat[each.key].id
  subnet_id     = each.value.id
  tags = {
    Name        = "${var.env}-nat-${each.key}"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
  depends_on = [aws_internet_gateway.this]
}

# Public Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.this.id
  tags = {
    Name        = "${var.env}-public-rt"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

resource "aws_route" "public_internet" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"  # default internet route
  gateway_id             = aws_internet_gateway.this.id
}

resource "aws_route_table_association" "public" {
  for_each       = aws_subnet.public
  subnet_id      = each.value.id
  route_table_id = aws_route_table.public.id
}

# Private Route Tables for App Subnets
resource "aws_route_table" "private_app" {
  for_each = aws_subnet.app
  vpc_id = aws_vpc.this.id
  tags = {
    Name        = "${var.env}-private-rt-app-${each.key}"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

resource "aws_route" "private_app_nat" {
  for_each = aws_route_table.private_app
  route_table_id         = each.value.id
  destination_cidr_block = "0.0.0.0/0"  # route through NAT
  nat_gateway_id         = aws_nat_gateway.this[each.key].id
}

resource "aws_route_table_association" "private_app" {
  for_each       = aws_subnet.app
  subnet_id      = each.value.id
  route_table_id = aws_route_table.private_app[each.key].id
}

# Private Route Tables for DB Subnets
resource "aws_route_table" "private_db" {
  for_each = aws_subnet.db
  vpc_id = aws_vpc.this.id
  tags = {
    Name        = "${var.env}-private-rt-db-${each.key}"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

resource "aws_route" "private_db_nat" {
  for_each = aws_route_table.private_db
  route_table_id         = each.value.id
  destination_cidr_block = "0.0.0.0/0"  # route through NAT
  nat_gateway_id         = aws_nat_gateway.this[each.key].id
}

resource "aws_route_table_association" "private_db" {
  for_each       = aws_subnet.db
  subnet_id      = each.value.id
  route_table_id = aws_route_table.private_db[each.key].id
}

Example: modules/network/variables.tf

variable "vpc_cidr" {
  type    = string
  default = "10.0.0.0/16"  # change as needed
}

variable "public_subnets" {
  type    = list(string)
  default = ["10.0.1.0/24","10.0.2.0/24","10.0.3.0/24"]  # change as needed
}

variable "app_subnets" {
  type    = list(string)
  default = ["10.0.11.0/24","10.0.12.0/24","10.0.13.0/24"]  # change as needed
}

variable "db_subnets" {
  type    = list(string)
  default = ["10.0.21.0/24","10.0.22.0/24","10.0.23.0/24"]  # change as needed
}

variable "azs" {
  type    = list(string)
  default = ["us-east-1a","us-east-1b","us-east-1c"]  # change as needed
}

variable "env" {
  type    = string
  default = "sendbox"  # change to your environment name
}

Example: modules/network/outputs.tf

output "vpc_id" {
  value = aws_vpc.this.id  # VPC ID
}

output "public_subnets" {
  value = [for s in aws_subnet.public : s.id]  # list of public subnet IDs
}

output "app_subnets" {
  value = [for s in aws_subnet.app : s.id]  # list of app (private) subnet IDs
}

output "db_subnets" {
  value = [for s in aws_subnet.db : s.id]  # list of db (private) subnet IDs
}

output "nat_gateway_ids" {
  value = [for ngw in aws_nat_gateway.this : ngw.id]  # NAT Gateway IDs
}

output "private_app_route_table_ids" {
  value = [for rt in aws_route_table.private_app : rt.id]  # private route tables for app subnets
}

output "private_db_route_table_ids" {
  value = [for rt in aws_route_table.private_db : rt.id]  # private route tables for db subnets
}

What to change:
  • env → in variables.tf or via terraform.tfvars
  • vpc_cidr → main VPC CIDR
  • public_subnets, app_subnets, db_subnets → subnet CIDRs
  • azs → availability zones for your subnets
========================================================================================================================
Example: modules/nacl/main.tf

# Public NACL
resource "aws_network_acl" "public" {
  vpc_id = var.vpc_id  # change as needed (pass from network module output)
  tags = {
    Name        = "${var.env}-public-nacl"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

# Public NACL Rules
resource "aws_network_acl_rule" "public_inbound" {
  network_acl_id = aws_network_acl.public.id
  rule_number    = 100
  egress         = false
  protocol       = "-1"  # all protocols
  rule_action    = "allow"
  cidr_block     = "0.0.0.0/0"  # change if you want to restrict access
}

resource "aws_network_acl_rule" "public_outbound" {
  network_acl_id = aws_network_acl.public.id
  rule_number    = 100
  egress         = true
  protocol       = "-1"  # all protocols
  rule_action    = "allow"
  cidr_block     = "0.0.0.0/0"  # change if you want to restrict egress
}

# Associate Public NACL with public subnets
resource "aws_network_acl_association" "public" {
  for_each       = toset(var.public_subnets)  # change via tfvars
  subnet_id      = each.value
  network_acl_id = aws_network_acl.public.id
}

# Private NACL (App + DB)
resource "aws_network_acl" "private" {
  vpc_id = var.vpc_id  # change as needed (pass from network module output)
  tags = {
    Name        = "${var.env}-private-nacl"  # change env in variables.tf or tfvars
    Environment = var.env  # change as needed
  }
}

# Private NACL Rules (allow internal traffic)
resource "aws_network_acl_rule" "private_inbound" {
  network_acl_id = aws_network_acl.private.id
  rule_number    = 100
  egress         = false
  protocol       = "-1"
  rule_action    = "allow"
  cidr_block     = var.internal_cidr  # change in variables.tf or tfvars
}

resource "aws_network_acl_rule" "private_outbound" {
  network_acl_id = aws_network_acl.private.id
  rule_number    = 100
  egress         = true
  protocol       = "-1"
  rule_action    = "allow"
  cidr_block     = var.internal_cidr  # change in variables.tf or tfvars
}

# Optional: allow NAT access to the internet
resource "aws_network_acl_rule" "private_outbound_nat" {
  network_acl_id = aws_network_acl.private.id
  rule_number    = 110
  egress         = true
  protocol       = "-1"
  rule_action    = "allow"
  cidr_block     = "0.0.0.0/0"  # change if you want restricted internet access
}

# Associate Private NACL with app subnets
resource "aws_network_acl_association" "private_app" {
  for_each       = toset(var.app_subnets)  # change via tfvars
  subnet_id      = each.value
  network_acl_id = aws_network_acl.private.id
}

# Associate Private NACL with DB subnets
resource "aws_network_acl_association" "private_db" {
  for_each       = toset(var.db_subnets)  # change via tfvars
  subnet_id      = each.value
  network_acl_id = aws_network_acl.private.id
}

Example: modules/nacl/variables.tf

variable "vpc_id" {
  type = string  # pass VPC ID from network module output
}

variable "public_subnets" {
  type = list(string)  # pass public subnet IDs
}

variable "app_subnets" {
  type = list(string)  # pass app subnet IDs
}

variable "db_subnets" {
  type = list(string)  # pass db subnet IDs
}

variable "internal_cidr" {
  type    = string
  default = "10.0.0.0/16"  # change to match your VPC
}

variable "env" {
  type    = string
  default = "sendbox"  # change to your environment name
}

Example: modules/nacl/outputs.tf

output "public_nacl_id" {
  value = aws_network_acl.public.id  # Public NACL ID
}

output "private_nacl_id" {
  value = aws_network_acl.private.id  # Private NACL ID
}

Summary of Updates
  • Added # change as needed comments to all resources, rules, and variables.
  • Added Environment = var.env tags for consistency.
  • Clarified which variables come from the network module outputs.
  • Ensured all associations (public/app/db) are explicit and ready to customize.
========================================================================================================================
Example: modules/tgw/main.tf

# Create Transit Gateway
resource "aws_ec2_transit_gateway" "tgw" {
  description = "${var.env}-${var.resource_name}-tgw"    # change environment name and resource
  amazon_side_asn = var.amazon_side_asn
  auto_accept_shared_attachments = var.auto_accept_shared
  default_route_table_association = var.default_association
  default_route_table_propagation = var.default_propagation
  dns_support = var.dns_support
  vpn_ecmp_support = var.vpn_ecmp_support
  tags = {
    Name = "${var.env}-${var.resource_name}-tgw"        # change environment name and resource
    Env  = var.env                                      # change environment name
  }
}

# Attach VPCs to TGW
resource "aws_ec2_transit_gateway_vpc_attachment" "tgw_vpc" {
  for_each = var.vpc_attachments

  transit_gateway_id = aws_ec2_transit_gateway.tgw.id
  vpc_id             = each.value.vpc_id                 # change VPC ID
  subnet_ids         = each.value.subnet_ids             # change Subnet IDs
  tags = {
    Name = "${var.env}-${var.resource_name}-vpc-${each.key}"  # change environment name and resource
    Env  = var.env
  }
}

# Associate VPC attachments with TGW Route Tables
resource "aws_ec2_transit_gateway_route_table_association" "tgw_assoc" {
  for_each = var.vpc_attachments

  transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.tgw_vpc[each.key].id
  transit_gateway_route_table_id = var.route_table_id   # change TGW route table ID
}

# Transit Gateway Peering (optional)
resource "aws_ec2_transit_gateway_peering_attachment" "tgw_peer" {
  count = var.enable_peering ? 1 : 0

  peer_transit_gateway_id = var.peer_tgw_id           # change peer TGW ID
  transit_gateway_id      = aws_ec2_transit_gateway.tgw.id
  peer_account_id         = var.peer_account_id       # change AWS account ID
  peer_region             = var.peer_region          # change peer region
  tags = {
    Name = "${var.env}-${var.resource_name}-tgw-peer"  # change environment name and resource
    Env  = var.env
  }
}

# Optionally associate peering with TGW route table
resource "aws_ec2_transit_gateway_peering_attachment_accepter" "peer_accept" {
  count = var.enable_peering ? 1 : 0
  transit_gateway_attachment_id = aws_ec2_transit_gateway_peering_attachment.tgw_peer[0].id
  auto_accept = true
}

Example: modules/tgw/variables.tf

# Environment name
variable "env" {
  type    = string
  default = "sendbox"       # change environment name
}

# Resource identifier
variable "resource_name" {
  type = string             # change resource name
}

# TGW options
variable "amazon_side_asn" {
  type    = number
  default = 64512
}
variable "auto_accept_shared" {
  type    = string
  default = "disable"
}
variable "default_association" {
  type    = string
  default = "enable"
}
variable "default_propagation" {
  type    = string
  default = "enable"
}
variable "dns_support" {
  type    = string
  default = "enable"
}
variable "vpn_ecmp_support" {
  type    = string
  default = "enable"
}

# VPC Attachments
variable "vpc_attachments" {
  type = map(object({
    vpc_id     = string
    subnet_ids = list(string)
  }))
  default = {}   # example: { "app" = { vpc_id = "vpc-123", subnet_ids = ["subnet-1","subnet-2"] } }
}

# TGW Route Table ID for associations
variable "route_table_id" {
  type = string   # change TGW route table ID
}

# TGW Peering options
variable "enable_peering" {
  type    = bool
  default = false
}
variable "peer_tgw_id" {
  type    = string
  default = ""     # change peer TGW ID
}
variable "peer_account_id" {
  type    = string
  default = ""     # change peer AWS account ID
}
variable "peer_region" {
  type    = string
  default = ""     # change peer region
}

Example: modules/tgw/outputs.tf

# TGW ID
output "tgw_id" {
  value = aws_ec2_transit_gateway.tgw.id
}

# VPC attachment IDs
output "tgw_vpc_attachment_ids" {
  value = { for k, v in aws_ec2_transit_gateway_vpc_attachment.tgw_vpc : k => v.id }
}

# TGW Peering ID
output "tgw_peering_id" {
  value = var.enable_peering ? aws_ec2_transit_gateway_peering_attachment.tgw_peer[0].id : ""
}
Example Usage
module "tgw" {
  source        = "./modules/tgw"
  env           = "prod"                             # change environment name
  resource_name = "core-network"                     # change resource name

  amazon_side_asn = 64512
  auto_accept_shared = "disable"
  default_association = "enable"
  default_propagation = "enable"
  dns_support = "enable"
  vpn_ecmp_support = "enable"

  vpc_attachments = {
    "app" = { vpc_id = aws_vpc.app.id, subnet_ids = [aws_subnet.app1.id, aws_subnet.app2.id] }
    "db"  = { vpc_id = aws_vpc.db.id, subnet_ids  = [aws_subnet.db1.id, aws_subnet.db2.id] }
  }

  route_table_id = aws_ec2_transit_gateway_route_table.main.id

  enable_peering   = true
  peer_tgw_id      = "tgw-0123456789abcdef0"
  peer_account_id  = "123456789012"
  peer_region      = "us-east-1"
}

What to change:
  • env → environment name (tags & resource names)
  • resource_name → TGW resource identifier
  • VPC Attachments: vpc_id, subnet_ids, route_table_id
  • TGW Peering: peer_tgw_id, peer_account_id, peer_region
  • Optional TGW settings: amazon_side_asn, dns_support, etc.
========================================================================================================================
Example: modules/flowlogs/main.tf

# CloudWatch Log Group for VPC Flow Logs
resource "aws_cloudwatch_log_group" "vpc_flowlogs" {
  name              = var.log_group            # change log group name if needed
  retention_in_days = var.retention_in_days    # change retention period if needed
  tags = {
    Name        = "${var.env}-vpc-flowlogs"  # change env in variables.tf or tfvars
    Environment = var.env                    # change as needed
  }
}

# VPC Flow Log
resource "aws_flow_log" "vpc" {
  vpc_id               = var.vpc_id           # change VPC ID (pass from network module)
  traffic_type         = var.traffic_type     # ACCEPT, REJECT, or ALL
  log_destination_type = "cloud-watch-logs"
  log_group_name       = aws_cloudwatch_log_group.vpc_flowlogs.name
  iam_role_arn         = var.iam_role_arn     # IAM role ARN with CloudWatch permissions
  tags = {
    Name        = "${var.env}-vpc-flowlog"  # change env in variables.tf or tfvars
    Environment = var.env                  # change as needed
  }
}

Example: modules/flowlogs/variables.tf

# VPC ID to attach Flow Logs
variable "vpc_id" {
  type = string  # pass the VPC ID (e.g., module.network.vpc_id)
}

# CloudWatch Log Group name
variable "log_group" {
  type    = string
  default = "/vpc/flowlogs/sendbox"  # change as needed (e.g., "/vpc/flowlogs/prod")
}

# Log retention in days
variable "retention_in_days" {
  type    = number
  default = 90  # change as needed
}

# Traffic type to capture
variable "traffic_type" {
  type        = string
  default     = "ALL"  # change to ACCEPT or REJECT if desired
  description = "Type of traffic to capture: ACCEPT, REJECT, or ALL"
}

# IAM Role ARN for Flow Logs
variable "iam_role_arn" {
  type        = string
  description = "IAM role ARN for Flow Logs to publish to CloudWatch Logs"  # change as needed
}

# Environment name
variable "env" {
  type    = string
  default = "sendbox"  # change to your environment name (prod, dev, etc.)
}

Example: modules/flowlogs/outputs.tf

# Flow Log ID
output "flow_log_id" {
  value = aws_flow_log.vpc.id  # VPC Flow Log ID
}

# CloudWatch Log Group name
output "log_group_name" {
  value = aws_cloudwatch_log_group.vpc_flowlogs.name  # CloudWatch Log Group name
}

Summary of Updates
  • Added # change as needed comments for all key parameters (VPC ID, log group, retention, traffic type, IAM role, env).
  • Added Environment = var.env tags for consistency across modules.
  • Clarified how variables map to outputs from the network module.
  • Fully ready customization (prod/dev) with minimal edits.
========================================================================================================================
Example: modules/security-groups/main.tf

# Web Security Group
resource "aws_security_group" "web" {
  name        = "${var.env}-web-sg"      # change env as needed
  description = "Web tier security group"
  vpc_id      = var.vpc_id               # change VPC ID
  tags = {
    Name        = "${var.env}-web-sg"    # change env as needed
    Environment = var.env                 # change as needed
  }
}

# Inbound rules for Web SG
resource "aws_security_group_rule" "web_inbound_http" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = var.public_cidr_blocks  # change source CIDRs as needed
  security_group_id = aws_security_group.web.id
}

resource "aws_security_group_rule" "web_inbound_https" {
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = var.public_cidr_blocks  # change source CIDRs as needed
  security_group_id = aws_security_group.web.id
}

# Outbound: allow all
resource "aws_security_group_rule" "web_outbound" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]          # change if needed
  security_group_id = aws_security_group.web.id
}

# App Security Group
resource "aws_security_group" "app" {
  name        = "${var.env}-app-sg"      # change env as needed
  description = "App tier security group"
  vpc_id      = var.vpc_id               # change VPC ID
  tags = {
    Name        = "${var.env}-app-sg"    # change env as needed
    Environment = var.env                 # change as needed
  }
}

# Inbound from Web SG
resource "aws_security_group_rule" "app_inbound_from_web" {
  type                     = "ingress"
  from_port                = 0
  to_port                  = 65535
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.web.id
  security_group_id        = aws_security_group.app.id
}

# Outbound to DB SG
resource "aws_security_group_rule" "app_outbound_to_db" {
  type                          = "egress"
  from_port                     = 0
  to_port                       = 65535
  protocol                      = "tcp"
  destination_security_group_id = aws_security_group.db.id
  security_group_id             = aws_security_group.app.id
}

# DB Security Group
resource "aws_security_group" "db" {
  name        = "${var.env}-db-sg"       # change env as needed
  description = "DB tier security group"
  vpc_id      = var.vpc_id               # change VPC ID
  tags = {
    Name        = "${var.env}-db-sg"     # change env as needed
    Environment = var.env                 # change as needed
  }
}

# Inbound from App SG
resource "aws_security_group_rule" "db_inbound_from_app" {
  type                     = "ingress"
  from_port                = 0
  to_port                  = 65535
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.app.id
  security_group_id        = aws_security_group.db.id
}

# Outbound: allow all
resource "aws_security_group_rule" "db_outbound" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]        # change if needed
  security_group_id = aws_security_group.db.id
}

Example: modules/security-groups/variables.tf

# VPC ID
variable "vpc_id" {
  type = string  # pass from network module output (e.g., module.network.vpc_id)
}

# Public CIDR blocks allowed to access Web SG
variable "public_cidr_blocks" {
  type    = list(string)
  default = ["0.0.0.0/0"]  # change to restrict access
}

# Environment name
variable "env" {
  type    = string
  default = "sendbox"  # change to your environment name (prod, dev, etc.)
}

Example: modules/security-groups/outputs.tf

# Web SG ID
output "web_sg_id" {
  value = aws_security_group.web.id  # Web Security Group ID
}

# App SG ID
output "app_sg_id" {
  value = aws_security_group.app.id  # App Security Group ID
}

# DB SG ID
output "db_sg_id" {
  value = aws_security_group.db.id   # DB Security Group ID
}

Summary of Updates
  • Added # change as needed comments for all names, CIDRs, and VPC IDs.
  • Added consistent Environment = var.env tags.
  • Clarified which variables come from the network module outputs.
  • Rules (inbound/outbound) fully annotated for easy customization (ports, source/destination, CIDRs).
========================================================================================================================
Example: modules/alb/main.tf

# Application Load Balancer
resource "aws_lb" "this" {
  name                       = "${var.env}-alb"        # change env as needed
  internal                   = false                    # set true if internal ALB
  load_balancer_type         = "application"
  security_groups            = [var.web_sg_id]         # pass Web SG ID
  subnets                    = var.public_subnets      # pass public subnet IDs
  enable_deletion_protection = false                    # change if needed
  tags = {
    Name        = "${var.env}-alb"                    # change env as needed
    Environment = var.env                              # change as needed
  }
}

# ALB Listener (HTTPS)
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.this.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = var.certificate_arn            # change to ACM certificate ARN
  default_action {
    type = "fixed-response"
    fixed_response {
      content_type = "text/plain"
      message_body = "OK"                             # change default response if needed
      status_code  = 200
    }
  }
}

# Optional WAFv2 Web ACL association
resource "aws_wafv2_web_acl_association" "this" {
  count        = var.waf_acl_id != "" ? 1 : 0
  resource_arn = aws_lb.this.arn
  web_acl_arn  = var.waf_acl_id                      # optional WAF ACL ARN
}

Example: modules/alb/variables.tf

variable "vpc_id" {
  type = string           # pass VPC ID (not used directly, keep for reference)
}

variable "public_subnets" {
  type = list(string)     # pass public subnet IDs from network module
}

variable "web_sg_id" {
  type = string           # pass Web SG ID from security-groups module
}

variable "certificate_arn" {
  type        = string
  description = "ACM certificate ARN for HTTPS"  # change to your certificate ARN
}

variable "waf_acl_id" {
  type        = string
  default     = ""                               # optional WAFv2 ACL ARN
  description = "Optional WAFv2 Web ACL ARN"
}

variable "env" {
  type    = string
  default = "sendbox"                            # change to your environment name
}

Example: modules/alb/outputs.tf

# ALB ARN
output "alb_arn" {
  value = aws_lb.this.arn  # Application Load Balancer ARN
}

# ALB DNS Name
output "alb_dns_name" {
  value = aws_lb.this.dns_name  # ALB DNS Name
}

# ALB Listener ARN
output "alb_listener_arn" {
  value = aws_lb_listener.https.arn  # ALB Listener ARN
}

Summary of Updates
  • Added # change as needed for all key parameters (env, subnets, security_groups, certificate_arn, internal).
  • Added Environment = var.env tag for consistency with other modules.
  • Optional WAF association annotated for clarity (waf_acl_id).
  • Fully customizable for prod/dev with public subnet and Web SG inputs from other modules.
========================================================================================================================
Example: modules/nlb/main.tf

# Network Load Balancer
resource "aws_lb" "this" {
  name                       = "${var.env}-nlb"      # change env as needed
  internal                   = var.internal          # set true for internal NLB
  load_balancer_type         = "network"
  subnets                    = var.subnets          # pass subnet IDs
  enable_deletion_protection = false                 # change if needed
  tags = {
    Name        = "${var.env}-nlb"                 # change env as needed
    Environment = var.env                           # change as needed
  }
}

# TCP Listener (default port 80, can be overridden)
resource "aws_lb_listener" "tcp" {
  load_balancer_arn = aws_lb.this.arn
  port              = var.listener_port           # change listener port if needed
  protocol          = "TCP"
  default_action {
    type = "fixed-response"
    fixed_response {
      content_type = "text/plain"
      message_body = "OK"                          # change response if needed
      status_code  = 200
    }
  }
}

# Optional Target Group
resource "aws_lb_target_group" "this" {
  name        = "${var.env}-tg"                  # change env as needed
  port        = var.target_port                  # change target port if needed
  protocol    = "TCP"
  vpc_id      = var.vpc_id                       # pass VPC ID
  target_type = "instance"
  health_check {
    protocol            = "TCP"
    interval            = 30
    timeout             = 10
    healthy_threshold   = 3
    unhealthy_threshold = 3
  }
  tags = {
    Name        = "${var.env}-tg"               # change env as needed
    Environment = var.env                        # change as needed
  }
}

# Listener forwarding to Target Group
resource "aws_lb_listener_rule" "forward" {
  listener_arn = aws_lb_listener.tcp.arn
  priority     = 100
  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.this.arn
  }
  condition {
    host_header {
      values = ["*"]                             # change if needed for host-based routing
    }
  }
}

Example: modules/nlb/variables.tf

variable "vpc_id" {
  type = string           # pass VPC ID from network module
}

variable "subnets" {
  type = list(string)     # pass subnet IDs (public or private as needed)
}

variable "listener_port" {
  type    = number
  default = 80             # change listener port if needed
}

variable "target_port" {
  type    = number
  default = 80             # change target port if needed
}

variable "internal" {
  type    = bool
  default = false          # set true if internal NLB
}

variable "env" {
  type    = string
  default = "sendbox"      # change environment name (prod, dev, etc.)
}

Example: modules/nlb/outputs.tf

# NLB ARN
output "nlb_arn" {
  value = aws_lb.this.arn      # Network Load Balancer ARN
}

# NLB DNS Name
output "nlb_dns_name" {
  value = aws_lb.this.dns_name # NLB DNS Name
}

# Target Group ARN
output "target_group_arn" {
  value = aws_lb_target_group.this.arn # Target Group ARN
}

Summary of Updates
  • Added # change as needed for all critical parameters (env, subnets, internal, listener_port, target_port, vpc_id).
  • Added consistent Environment = var.env tags.
  • Clarified optional changes in listener rules (host headers, TCP port, response).
  • Fully ready for prod or dev deployment with NLB, listener, and target group connected to your network module.
========================================================================================================================
Example: modules/compute/main.tf

# Launch Template
resource "aws_launch_template" "this" {
  name_prefix   = "${var.env}-lt-"                  # change env as needed
  image_id      = var.ami_id                        # change AMI ID as needed
  instance_type = var.instance_type                 # change instance type as needed
  vpc_security_group_ids = [
    var.web_sg_id,                                  # Web SG ID
    var.app_sg_id,                                  # App SG ID
    var.db_sg_id                                    # DB SG ID
  ]
  key_name = var.key_name                            # change key pair if needed
  tag_specifications {
    resource_type = "instance"
    tags = {
      Name = "${var.env}-instance"                  # change env or instance name if needed
      Env  = var.env                                # environment tag
    }
  }
}

# Auto Scaling Group
resource "aws_autoscaling_group" "this" {
  name_prefix          = "${var.env}-asg-"         # change env as needed
  launch_template {
    id      = aws_launch_template.this.id
    version = "$Latest"
  }
  min_size                = var.asg_min_size       # change min size as needed
  max_size                = var.asg_max_size       # change max size as needed
  desired_capacity        = var.asg_desired_count  # change desired capacity as needed
  vpc_zone_identifier     = var.app_subnets        # subnets for ASG
  health_check_type       = "EC2"
  health_check_grace_period = 300
  force_delete            = true
  tag {
    key                 = "Name"
    value               = "${var.env}-asg-instance"  # change env or name if needed
    propagate_at_launch = true
  }
}

Example: modules/compute/variables.tf

variable "vpc_id" {
  type = string              # pass VPC ID from network module
}

variable "app_subnets" {
  type = list(string)        # pass private app subnet IDs
}

variable "db_subnets" {
  type = list(string)        # pass private DB subnet IDs (optional if used)
}

variable "web_sg_id" {
  type = string              # Web SG ID from security-groups module
}

variable "app_sg_id" {
  type = string              # App SG ID from security-groups module
}

variable "db_sg_id" {
  type = string              # DB SG ID from security-groups module
}

variable "ami_id" {
  type = string              # AMI ID to launch instances
}

variable "instance_type" {
  type    = string
  default = "t3.medium"      # change instance type as needed
}

variable "asg_min_size" {
  type    = number
  default = 1                 # change min size as needed
}

variable "asg_max_size" {
  type    = number
  default = 3                 # change max size as needed
}

variable "asg_desired_count" {
  type    = number
  default = 2                 # change desired capacity as needed
}

variable "key_name" {
  type    = string
  default = ""                # set key pair if SSH access is needed
}

variable "env" {
  type    = string
  default = "sendbox"         # change environment name (prod, dev, etc.)
}

Example: modules/compute/outputs.tf

# Launch Template ID
output "launch_template_id" {
  value = aws_launch_template.this.id  # Launch Template ID
}

# Auto Scaling Group Name
output "asg_name" {
  value = aws_autoscaling_group.this.name  # ASG Name
}

Summary of Updates
  • Added # change as needed for all key parameters (env, ami_id, instance_type, ASG sizes, key_name).
  • Added Env = var.env tag for instances.
  • Clearly referenced Security Groups and subnets from other modules.
  • Fully ready for prod/dev deployment with ASG and Launch Template using your network and security group modules.
========================================================================================================================
Example: modules/pacemaker/main.tf

# Security Group for Pacemaker nodes
resource "aws_security_group" "pacemaker" {
  name        = "${var.env}-pacemaker-sg"        # change env as needed
  description = "Security group for Pacemaker HA overlay nodes"
  vpc_id      = var.vpc_id                       # change VPC ID as needed

  # Corosync & Heartbeat traffic
  ingress {
    description = "Corosync & Heartbeat"
    from_port   = 5405
    to_port     = 5405
    protocol    = "tcp"
    cidr_blocks = var.internal_cidr_blocks       # change internal network CIDR as needed
  }

  # SSH access
  ingress {
    description = "SSH access"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = var.ssh_cidr_blocks            # restrict to your IPs as needed
  }

  # Outbound: allow all
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]                  # change if needed
  }

  tags = {
    Name = "${var.env}-pacemaker-sg"            # change env as needed
    Env  = var.env                               # environment tag
  }
}

# Pacemaker EC2 nodes
resource "aws_instance" "pacemaker_nodes" {
  count = length(var.private_subnets)            # one node per private subnet
  ami           = var.ami_id                     # change AMI ID as needed
  instance_type = var.instance_type              # change instance type as needed
  subnet_id     = var.private_subnets[count.index]
  vpc_security_group_ids = [aws_security_group.pacemaker.id]
  key_name      = var.key_name                   # change key pair if needed
  tags = {
    Name = "${var.env}-pacemaker-${count.index}" # change env or node naming if needed
    Env  = var.env
  }
  user_data = file(var.user_data_script)         # change path to Pacemaker setup script
}

# Elastic IP for Floating VIP
resource "aws_eip" "vip" {
  vpc = true
  depends_on = [aws_instance.pacemaker_nodes]    # ensure instances exist before allocation
}

# Note: Floating VIP configuration must be handled in Pacemaker/Corosync user_data script

Example: modules/pacemaker/variables.tf

variable "vpc_id" {
  type = string               # pass VPC ID from network module
}

variable "private_subnets" {
  type = list(string)         # private subnets for Pacemaker nodes
}

variable "ami_id" {
  type = string               # AMI ID for Pacemaker EC2 instances
}

variable "instance_type" {
  type    = string
  default = "t3.medium"       # change instance type as needed
}

variable "key_name" {
  type    = string
  default = ""                 # SSH key pair
}

variable "internal_cidr_blocks" {
  type    = list(string)
  default = ["10.0.0.0/16"]   # internal network CIDR for Corosync/Heartbeat
}

variable "ssh_cidr_blocks" {
  type    = list(string)
  default = ["0.0.0.0/0"]     # change to restricted IP range for SSH
}

variable "user_data_script" {
  type        = string
  description = "Path to script installing/configuring Pacemaker and Floating VIP"  # change as needed
}

variable "env" {
  type    = string
  default = "sendbox"         # change environment name (prod, dev, etc.)
}

Example: modules/pacemaker/outputs.tf

# Pacemaker EC2 Node IDs
output "pacemaker_node_ids" {
  value = [for node in aws_instance.pacemaker_nodes : node.id]  # list of node IDs
}

# Floating VIP Elastic IP
output "vip_eip" {
  value = aws_eip.vip.public_ip  # Elastic IP for floating VIP
}

Summary of Updates
  • Added # change as needed for all key parameters (env, ami_id, instance_type, key_name, internal_cidr_blocks, ssh_cidr_blocks, user_data_script).
  • Added Env = var.env tag to all EC2 nodes and security group.
  • Clarified which CIDRs are internal vs. SSH.
  • Configured Pacemaker nodes per private subnet with a floating VIP EIP ready for HA setup.
========================================================================================================================
Example: modules/inspection-vpc/main.tf

# Inspection VPC
resource "aws_vpc" "inspection" {
  cidr_block           = var.vpc_cidr              # change VPC CIDR as needed
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "${var.env}-inspection-vpc"           # change env as needed
    Env  = var.env                               # environment tag
  }
}

# Public subnets for firewall/load balancer
resource "aws_subnet" "public" {
  for_each = { for idx, cidr in var.public_subnets : idx => cidr }
  vpc_id            = aws_vpc.inspection.id
  cidr_block        = each.value
  availability_zone = var.azs[each.key]          # change AZs as needed
  map_public_ip_on_launch = true
  tags = {
    Name = "${var.env}-inspection-public-${each.key}"  # change env as needed
    Env  = var.env
  }
}

# Private subnets for internal inspection traffic
resource "aws_subnet" "private" {
  for_each = { for idx, cidr in var.private_subnets : idx => cidr }
  vpc_id            = aws_vpc.inspection.id
  cidr_block        = each.value
  availability_zone = var.azs[each.key]
  map_public_ip_on_launch = false
  tags = {
    Name = "${var.env}-inspection-private-${each.key}" # change env as needed
    Env  = var.env
  }
}

# Internet Gateway for inspection VPC (optional)
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.inspection.id
  tags = {
    Name = "${var.env}-inspection-igw"          # change env as needed
    Env  = var.env
  }
}

# Public Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.inspection.id
  tags = {
    Name = "${var.env}-inspection-public-rt"   # change env as needed
    Env  = var.env
  }
}

resource "aws_route" "public_internet" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.igw.id
}

resource "aws_route_table_association" "public_assoc" {
  for_each       = aws_subnet.public
  subnet_id      = each.value.id
  route_table_id = aws_route_table.public.id
}

# Private Route Table
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.inspection.id
  tags = {
    Name = "${var.env}-inspection-private-rt"  # change env as needed
    Env  = var.env
  }
}

resource "aws_route_table_association" "private_assoc" {
  for_each       = aws_subnet.private
  subnet_id      = each.value.id
  route_table_id = aws_route_table.private.id
}

Example: modules/inspection-vpc/variables.tf

variable "vpc_cidr" {
  type = string                  # CIDR block for Inspection VPC
}

variable "public_subnets" {
  type = list(string)            # list of public subnet CIDRs for firewall/load balancer
}

variable "private_subnets" {
  type = list(string)            # list of private subnet CIDRs for internal inspection traffic
}

variable "azs" {
  type = list(string)            # availability zones for the subnets
}

variable "env" {
  type    = string
  default = "sendbox"            # change environment name (prod, dev, etc.)
}

Example: modules/inspection-vpc/outputs.tf

# Inspection VPC ID
output "inspection_vpc_id" {
  value = aws_vpc.inspection.id
}

# Public Subnet IDs
output "public_subnet_ids" {
  value = [for s in aws_subnet.public : s.id]
}

# Private Subnet IDs
output "private_subnet_ids" {
  value = [for s in aws_subnet.private : s.id]
}

What to change
  • env → environment name for tags (prod, dev, etc.).
  • vpc_cidr → CIDR block for the Inspection VPC.
  • public_subnets → list of public subnet CIDRs for firewall/load balancer.
  • private_subnets → list of private subnet CIDRs for internal inspection traffic.
  • azs → availability zones corresponding to each subnet.
========================================================================================================================
Example: modules/rds/main.tf

# RDS Subnet Group
resource "aws_db_subnet_group" "this" {
  name       = "${var.env}-rds-subnet-group"       # change env as needed
  subnet_ids = var.db_subnets                      # private DB subnet IDs
  tags = {
    Name = "${var.env}-rds-subnet-group"          # change env as needed
    Env  = var.env
  }
}

# RDS Security Group (optional, default allows all in VPC)
resource "aws_security_group" "rds_sg" {
  name        = "${var.env}-rds-sg"               # change env as needed
  description = "Security group for RDS instances"
  vpc_id      = var.vpc_id                        # VPC ID
  # inbound from app servers
  ingress {
    from_port                = var.db_port
    to_port                  = var.db_port
    protocol                 = "tcp"
    source_security_group_id = var.app_sg_id      # allow App SG
  }
  # outbound: allow all
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "${var.env}-rds-sg"                   # change env as needed
    Env  = var.env
  }
}

# RDS Instance
resource "aws_db_instance" "this" {
  identifier              = "${var.env}-rds"             # change env as needed
  engine                  = var.engine                   # e.g., "mysql" or "postgres"
  engine_version          = var.engine_version           # optional, e.g., "8.0.33"
  instance_class          = var.instance_class           # e.g., "db.t3.medium"
  allocated_storage       = var.allocated_storage        # in GB
  storage_type            = var.storage_type             # gp3, gp2, etc.
  db_subnet_group_name    = aws_db_subnet_group.this.name
  vpc_security_group_ids  = [aws_security_group.rds_sg.id]
  multi_az                = var.multi_az                 # high availability
  publicly_accessible     = var.publicly_accessible      # usually false for DB
  username                = var.db_username              # master user
  password                = var.db_password              # master password
  skip_final_snapshot     = var.skip_final_snapshot      # change as needed
  backup_retention_period = var.backup_retention_period  # in days
  tags = {
    Name = "${var.env}-rds"                            # change env as needed
    Env  = var.env
  }
}

Example: modules/rds/variables.tf

# VPC ID
variable "vpc_id" {
  type = string  # pass VPC ID
}

# Private DB subnet IDs
variable "db_subnets" {
  type = list(string)  # private subnets for RDS
}

# App SG ID (to allow inbound DB access)
variable "app_sg_id" {
  type = string
}

# Environment name
variable "env" {
  type    = string
  default = "sendbox"  # change to prod/dev
}

# RDS engine and version
variable "engine" {
  type    = string
  default = "mysql"  # change as needed
}
variable "engine_version" {
  type    = string
  default = "8.0.33"  # change as needed
}

# RDS instance class
variable "instance_class" {
  type    = string
  default = "db.t3.medium"  # change as needed
}

# Storage configuration
variable "allocated_storage" {
  type    = number
  default = 20  # GB, change as needed
}
variable "storage_type" {
  type    = string
  default = "gp3"  # change as needed
}

# Multi-AZ
variable "multi_az" {
  type    = bool
  default = false  # set true for HA
}

# Public access
variable "publicly_accessible" {
  type    = bool
  default = false  # usually false
}

# DB credentials
variable "db_username" {
  type    = string
  default = "admin"  # change as needed
}
variable "db_password" {
  type      = string
  sensitive = true
}

# Backup retention
variable "backup_retention_period" {
  type    = number
  default = 7  # days
}

# Skip final snapshot on destroy
variable "skip_final_snapshot" {
  type    = bool
  default = true
}

Example: modules/rds/outputs.tf

# RDS instance ID
output "rds_instance_id" {
  value = aws_db_instance.this.id
}

# RDS endpoint
output "rds_endpoint" {
  value = aws_db_instance.this.endpoint
}

# RDS security group ID
output "rds_sg_id" {
  value = aws_security_group.rds_sg.id
}

# DB subnet group
output "rds_subnet_group_name" {
  value = aws_db_subnet_group.this.name
}

What to change
  • env → environment name (prod, dev, etc.).
  • vpc_id → VPC ID where RDS will reside.
  • db_subnets → private subnet IDs for RDS.
  • app_sg_id → Security Group ID for your application servers to allow inbound DB traffic.
  • engine / engine_version → RDS engine type and version.
  • instance_class → instance type (e.g., db.t3.medium).
  • allocated_storage / storage_type → storage size and type.
  • multi_az → true if high availability required.
  • publicly_accessible → false for private DB.
  • db_username / db_password → master credentials.
  • backup_retention_period → number of backup days.
  • skip_final_snapshot → true or false on destroy.
========================================================================================================================
Example: modules/dns/main.tf

# Route 53 Hosted Zone
resource "aws_route53_zone" "this" {
  name = var.domain_name                   # change domain name as needed
  comment = "${var.env} hosted zone"      # change env as needed
  tags = {
    Name = "${var.env}-hosted-zone"       # change env as needed
    Env  = var.env
  }
}

# Optional Record Sets: A records pointing to ALB / NLB
resource "aws_route53_record" "app_a_record" {
  for_each = var.app_endpoints             # map of {name = target} 
  zone_id  = aws_route53_zone.this.id
  name     = each.key                       # subdomain name
  type     = "A"
  alias {
    name                   = each.value    # DNS name of ALB/NLB
    zone_id                = var.endpoint_zone_ids[each.key]  # ALB/NLB zone ID
    evaluate_target_health = true
  }
}

# Optional Record Sets: CNAME for external endpoints
resource "aws_route53_record" "app_cname_record" {
  for_each = var.cname_endpoints          # map of {name = value}
  zone_id  = aws_route53_zone.this.id
  name     = each.key
  type     = "CNAME"
  ttl      = 300                           # change TTL if needed
  records  = [each.value]
}

Example: modules/dns/variables.tf

# Environment name
variable "env" {
  type    = string
  default = "sendbox"                      # change environment name
}

# Domain name for hosted zone
variable "domain_name" {
  type = string                             # e.g., "example.com"
}

# Map of subdomain → target for ALB/NLB alias A records
variable "app_endpoints" {
  type    = map(string)
  default = {}                              # example: { "app" = module.alb.alb_dns_name }
}

# Map of subdomain → zone ID for ALB/NLB alias record
variable "endpoint_zone_ids" {
  type    = map(string)
  default = {}                              # example: { "app" = module.alb.alb_zone_id }
}

# Map of subdomain → CNAME value
variable "cname_endpoints" {
  type    = map(string)
  default = {}                              # optional CNAMEs
}

Example: modules/dns/outputs.tf

# Hosted Zone ID
output "hosted_zone_id" {
  value = aws_route53_zone.this.id
}

# Hosted Zone Name
output "hosted_zone_name" {
  value = aws_route53_zone.this.name
}

# A Record FQDNs
output "a_record_fqdns" {
  value = [for r in aws_route53_record.app_a_record : r.fqdn]
}

# CNAME Record FQDNs
output "cname_record_fqdns" {
  value = [for r in aws_route53_record.app_cname_record : r.fqdn]
}

What to change
  • env → environment name for tags (prod, dev, etc.)
  • domain_name → your domain for the hosted zone (example.com)
  • app_endpoints → map of subdomain → ALB/NLB DNS names
  • endpoint_zone_ids → map of subdomain → ALB/NLB zone IDs (required for alias records)
  • cname_endpoints → optional subdomain → external CNAME targets
  • ttl → for CNAME records (default 300 seconds)
========================================================================================================================
Example: modules/iam/main.tf

# IAM Role
resource "aws_iam_role" "this" {
  name               = "${var.env}-${var.role_name}"        # change env or role name
  assume_role_policy = var.assume_role_policy              # JSON policy defining who can assume
  tags = {
    Name = "${var.env}-${var.role_name}"                  # change env or role name
    Env  = var.env
  }
}

# IAM Policy (optional inline)
resource "aws_iam_policy" "this" {
  count = var.inline_policy != "" ? 1 : 0
  name  = "${var.env}-${var.role_name}-policy"            # change env or policy name
  policy = var.inline_policy                               # JSON policy document
  tags = {
    Name = "${var.env}-${var.role_name}-policy"
    Env  = var.env
  }
}

# Attach policy to role (optional)
resource "aws_iam_role_policy_attachment" "attach" {
  count      = var.inline_policy != "" ? 1 : 0
  role       = aws_iam_role.this.name
  policy_arn = aws_iam_policy.this[0].arn
}

# Attach managed policies (optional list of ARNs)
resource "aws_iam_role_policy_attachment" "managed" {
  for_each = toset(var.managed_policy_arns)
  role      = aws_iam_role.this.name
  policy_arn = each.value
}

Example: modules/iam/variables.tf

# Environment name
variable "env" {
  type    = string
  default = "sendbox"                     # change environment name
}

# Role name
variable "role_name" {
  type = string                            # e.g., "flowlogs-role"
}

# JSON assume role policy
variable "assume_role_policy" {
  type = string
  description = "JSON policy defining who can assume this role"
}

# Optional inline policy JSON
variable "inline_policy" {
  type    = string
  default = ""                             # leave empty if not needed
}

# Optional list of managed policy ARNs to attach
variable "managed_policy_arns" {
  type    = list(string)
  default = []                             # e.g., ["arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"]
}

Example: modules/iam/outputs.tf

# IAM Role ARN
output "role_arn" {
  value = aws_iam_role.this.arn
}

# IAM Role Name
output "role_name" {
  value = aws_iam_role.this.name
}

# Inline Policy ARN (if created)
output "inline_policy_arn" {
  value = length(aws_iam_policy.this) > 0 ? aws_iam_policy.this[0].arn : ""
}

# List of Managed Policy ARNs attached
output "managed_policy_arns_attached" {
  value = [for a in aws_iam_role_policy_attachment.managed : a.policy_arn]
}

What to change
  • env → environment name for tags (prod, dev, etc.)
  • role_name → descriptive name for the role (e.g., flowlogs-role)
  • assume_role_policy → JSON defining who can assume the role (e.g., EC2, Lambda, Flow Logs)
  • inline_policy → optional inline JSON policy for the role
  • managed_policy_arns → optional list of AWS-managed policy ARNs to attach
========================================================================================================================
Example: modules/s3/main.tf

# S3 Bucket
resource "aws_s3_bucket" "this" {
  bucket = var.bucket_name                # change bucket name as needed
  acl    = var.acl                        # e.g., "private", "public-read"
  tags = {
    Name = "${var.env}-${var.bucket_name}" # change env as needed
    Env  = var.env
  }
}

# Enable versioning (optional)
resource "aws_s3_bucket_versioning" "this" {
  bucket = aws_s3_bucket.this.id
  versioning_configuration {
    status = var.versioning ? "Enabled" : "Suspended"  # enable or disable
  }
}

# Enable server-side encryption (optional)
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
  count  = var.enable_encryption ? 1 : 0
  bucket = aws_s3_bucket.this.id
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = var.encryption_algorithm  # e.g., "AES256" or "aws:kms"
      kms_master_key_id = var.kms_key_id        # optional if using KMS
    }
  }
}

# Optional bucket policy
resource "aws_s3_bucket_policy" "this" {
  count  = var.bucket_policy != "" ? 1 : 0
  bucket = aws_s3_bucket.this.id
  policy = var.bucket_policy                  # JSON bucket policy
}

Example: modules/s3/variables.tf

# Environment name
variable "env" {
  type    = string
  default = "sendbox"                       # change environment name
}

# Bucket name (must be globally unique)
variable "bucket_name" {
  type = string
}

# ACL for bucket
variable "acl" {
  type    = string
  default = "private"                        # e.g., "private", "public-read"
}

# Enable versioning
variable "versioning" {
  type    = bool
  default = true
}

# Enable server-side encryption
variable "enable_encryption" {
  type    = bool
  default = true
}

# Encryption algorithm
variable "encryption_algorithm" {
  type    = string
  default = "AES256"                         # "AES256" or "aws:kms"
}

# Optional KMS key for encryption
variable "kms_key_id" {
  type    = string
  default = ""                               # leave empty if not using KMS
}

# Optional bucket policy JSON
variable "bucket_policy" {
  type    = string
  default = ""                               # leave empty if not needed
}

Example: modules/s3/outputs.tf

# Bucket ARN
output "bucket_arn" {
  value = aws_s3_bucket.this.arn
}

# Bucket Name
output "bucket_name" {
  value = aws_s3_bucket.this.bucket
}

# Versioning status
output "versioning_status" {
  value = aws_s3_bucket_versioning.this.status
}

# Encryption enabled (true/false)
output "encryption_enabled" {
  value = var.enable_encryption
}

What to change
  • env → environment name for tags (prod, dev, etc.)
  • bucket_name → globally unique S3 bucket name
  • acl → bucket ACL (default private)
  • versioning → enable or disable versioning (true/false)
  • enable_encryption → enable server-side encryption
  • encryption_algorithm → "AES256" or "aws:kms"
  • kms_key_id → optional KMS key ID if using KMS
  • bucket_policy → optional JSON policy for custom access rules
========================================================================================================================
Example: modules/secrets/main.tf

# Secrets Manager secret
resource "aws_secretsmanager_secret" "this" {
  name        = var.secret_name                    # change secret name
  description = var.description
  tags = {
    Name = "${var.env}-${var.secret_name}"        # change env as needed
    Env  = var.env
  }
}

# Secret value
resource "aws_secretsmanager_secret_version" "this" {
  secret_id     = aws_secretsmanager_secret.this.id
  secret_string = var.secret_value                # actual secret value
}

# Optional: SSM Parameter for secret reference
resource "aws_ssm_parameter" "this" {
  count      = var.create_ssm_parameter ? 1 : 0
  name       = var.ssm_name
  description = var.description
  type       = var.ssm_type                        # String, SecureString, StringList
  value      = var.secret_value
  overwrite  = true
  tags = {
    Name = "${var.env}-${var.ssm_name}"           # change env as needed
    Env  = var.env
  }
}

Example: modules/secrets/variables.tf

# Environment name
variable "env" {
  type    = string
  default = "sendbox"                       # change environment name
}

# Secrets Manager secret name
variable "secret_name" {
  type = string
}

# Secret value (JSON or string)
variable "secret_value" {
  type = string
}

# Optional description
variable "description" {
  type    = string
  default = "Managed by Terraform"
}

# Create optional SSM parameter
variable "create_ssm_parameter" {
  type    = bool
  default = false
}

# SSM parameter name (optional)
variable "ssm_name" {
  type    = string
  default = ""
}

# SSM parameter type: String, SecureString, StringList
variable "ssm_type" {
  type    = string
  default = "SecureString"
}

Example: modules/secrets/outputs.tf

# Secrets Manager Secret ARN
output "secret_arn" {
  value = aws_secretsmanager_secret.this.arn
}

# Secrets Manager Secret Name
output "secret_name" {
  value = aws_secretsmanager_secret.this.name
}

# SSM Parameter Name (if created)
output "ssm_name" {
  value = var.create_ssm_parameter ? aws_ssm_parameter.this[0].name : ""
}

# SSM Parameter ARN (if created)
output "ssm_arn" {
  value = var.create_ssm_parameter ? aws_ssm_parameter.this[0].arn : ""
}

What to change
  • env → environment name for tags (prod, dev, etc.)
  • secret_name → Secrets Manager secret name
  • secret_value → actual secret content (JSON or string)
  • description → optional description of the secret
  • create_ssm_parameter → whether to create a corresponding SSM Parameter
  • ssm_name → name of SSM Parameter (if used)
  • ssm_type → String, SecureString, or StringList
========================================================================================================================
Example: modules/cloudwatch-alarms/main.tf

# CloudWatch Alarm for CPU Utilization
resource "aws_cloudwatch_metric_alarm" "cpu" {
  alarm_name          = "${var.env}-${var.resource_name}-cpu"  # change environment name and resource
  comparison_operator = var.cpu_comparison_operator            # e.g., GreaterThanThreshold
  evaluation_periods  = var.cpu_evaluation_periods
  metric_name         = "CPUUtilization"
  namespace           = "AWS/EC2"
  period              = var.cpu_period
  statistic           = var.cpu_statistic
  threshold           = var.cpu_threshold
  alarm_description   = var.description
  alarm_actions       = var.alarm_actions                      # SNS topic ARNs or ARNs of actions
  dimensions          = var.dimensions
  treat_missing_data  = "missing"

  tags = {
    Name = "${var.env}-${var.resource_name}-cpu"             # change environment name and resource
    Env  = var.env                                           # change environment name
  }
}

# Optional CloudWatch Alarm for Memory Utilization (requires custom metric)
resource "aws_cloudwatch_metric_alarm" "memory" {
  count               = var.enable_memory_alarm ? 1 : 0
  alarm_name          = "${var.env}-${var.resource_name}-memory"  # change environment name and resource
  comparison_operator = var.memory_comparison_operator
  evaluation_periods  = var.memory_evaluation_periods
  metric_name         = "MemoryUtilization"
  namespace           = var.memory_namespace
  period              = var.memory_period
  statistic           = var.memory_statistic
  threshold           = var.memory_threshold
  alarm_description   = var.description
  alarm_actions       = var.alarm_actions
  dimensions          = var.dimensions
  treat_missing_data  = "missing"

  tags = {
    Name = "${var.env}-${var.resource_name}-memory"       # change environment name and resource
    Env  = var.env                                         # change environment name
  }
}

Example: modules/cloudwatch-alarms/variables.tf

# Environment name
variable "env" {
  type    = string
  default = "sendbox"  # change environment name (e.g., "prod", "dev", "staging")
}

# Resource identifier (EC2, RDS, etc.)
variable "resource_name" {
  type = string  # change to identify your resource
}

# Alarm actions (SNS topic ARNs or other ARNs)
variable "alarm_actions" {
  type    = list(string)
  default = []
}

# Dimensions for the metric (e.g., {"InstanceId" = "i-123456"})
variable "dimensions" {
  type    = map(string)
  default = {}
}

# Optional description
variable "description" {
  type    = string
  default = "CloudWatch Alarm managed by Terraform"
}

# CPU Alarm variables
variable "cpu_comparison_operator" {
  type    = string
  default = "GreaterThanThreshold"
}
variable "cpu_evaluation_periods" {
  type    = number
  default = 3
}
variable "cpu_period" {
  type    = number
  default = 300
}
variable "cpu_statistic" {
  type    = string
  default = "Average"
}
variable "cpu_threshold" {
  type    = number
  default = 80
}

# Memory Alarm variables
variable "enable_memory_alarm" {
  type    = bool
  default = false
}
variable "memory_comparison_operator" {
  type    = string
  default = "GreaterThanThreshold"
}
variable "memory_evaluation_periods" {
  type    = number
  default = 3
}
variable "memory_period" {
  type    = number
  default = 300
}
variable "memory_statistic" {
  type    = string
  default = "Average"
}
variable "memory_threshold" {
  type    = number
  default = 80
}
variable "memory_namespace" {
  type    = string
  default = "System/Linux"
}

Example: modules/cloudwatch-alarms/outputs.tf

# CPU Alarm ARN
output "cpu_alarm_arn" {
  value = aws_cloudwatch_metric_alarm.cpu.arn
}

# Memory Alarm ARN (if created)
output "memory_alarm_arn" {
  value = var.enable_memory_alarm ? aws_cloudwatch_metric_alarm.memory[0].arn : ""
}

Where to change:
  • env → environment name for tags and alarm names (# change environment name)
  • resource_name → resource identifier (# change to identify your resource)
  • alarm_actions → SNS topic or other action ARNs
  • dimensions → resource identifiers (EC2 instance ID, RDS instance, etc.)
========================================================================================================================
Example: modules/bastion/main.tf

# Bastion / Jump Box EC2 Instance
resource "aws_instance" "bastion" {
  ami                         = var.ami_id                     # change AMI ID
  instance_type               = var.instance_type              # change instance type
  subnet_id                   = var.subnet_id                  # change subnet ID
  key_name                    = var.key_name                   # change SSH key name
  associate_public_ip_address = var.associate_public_ip        # true if public bastion

  vpc_security_group_ids      = [aws_security_group.bastion.id]
  tags = {
    Name = "${var.env}-${var.resource_name}-bastion"         # change environment name and resource
    Env  = var.env                                           # change environment name
  }
}

# Security Group for Bastion
resource "aws_security_group" "bastion" {
  name        = "${var.env}-${var.resource_name}-bastion-sg"  # change environment name and resource
  description = "Security group for Bastion host"
  vpc_id      = var.vpc_id                                    # change VPC ID

  # Allow SSH from trusted IPs
  ingress {
    description      = "SSH from trusted IPs"
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = var.allowed_ips
  }

  # Allow all outbound (optional)
  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.env}-${var.resource_name}-bastion-sg"    # change environment name and resource
    Env  = var.env                                         # change environment name
  }
}

Example: modules/bastion/variables.tf

# Environment name
variable "env" {
  type    = string
  default = "sendbox"   # change environment name
}

# Resource name identifier
variable "resource_name" {
  type = string       # change resource name
}

# VPC and subnet
variable "vpc_id" {
  type = string       # change VPC ID
}
variable "subnet_id" {
  type = string       # change subnet ID
}

# EC2 configuration
variable "ami_id" {
  type = string       # change AMI ID (Linux recommended)
}
variable "instance_type" {
  type    = string
  default = "t3.micro"   # change instance type if needed
}
variable "key_name" {
  type = string       # change SSH key name
}
variable "associate_public_ip" {
  type    = bool
  default = true      # true if bastion should be public
}

# Allowed IPs for SSH
variable "allowed_ips" {
  type    = list(string)
  default = ["0.0.0.0/0"]  # change to your trusted IPs
}

Example: modules/bastion/outputs.tf

# Bastion public IP
output "bastion_public_ip" {
  value = aws_instance.bastion.public_ip
}

# Bastion private IP
output "bastion_private_ip" {
  value = aws_instance.bastion.private_ip
}

# Security group ID
output "bastion_sg_id" {
  value = aws_security_group.bastion.id
}
Example Usage
module "bastion" {
  source        = "./modules/bastion"
  env           = "prod"                                # change environment name
  resource_name = "jumpbox"                              # change resource name

  vpc_id        = aws_vpc.main.id                        # change VPC
  subnet_id     = aws_subnet.public.id                   # change subnet
  ami_id        = "ami-0abcdef1234567890"               # change AMI
  instance_type = "t3.micro"                             # change if needed
  key_name      = "my-key"                               # change SSH key name
  associate_public_ip = true

  allowed_ips   = ["1.2.3.4/32"]                         # change to trusted IPs
}

Where to change:
  • env → environment name for tags (prod, dev, etc.)
  • resource_name → name of the bastion/jump box
  • vpc_id / subnet_id → VPC and subnet for the bastion
  • ami_id → Linux AMI ID for your region
  • instance_type → EC2 instance type
  • key_name → SSH key name for access
  • allowed_ips → trusted IPs allowed to SSH
========================================================================================================================
Example: modules/vpc-connection/main.tf

# VPC Peering Connection
resource "aws_vpc_peering_connection" "peer" {
  count = var.connection_type == "vpc_peering" ? 1 : 0

  vpc_id        = var.vpc_id                     # change source VPC ID
  peer_vpc_id   = var.peer_vpc_id                # change target VPC ID
  peer_region   = var.peer_region                # change target VPC region if cross-region
  auto_accept   = var.auto_accept                # true if automatic acceptance

  tags = {
    Name = "${var.env}-${var.resource_name}-vpc-peering"  # change environment name and resource
    Env  = var.env                                         # change environment name
  }
}

# VPC Peering Route Table Associations (optional)
resource "aws_route" "peer_routes" {
  count              = var.connection_type == "vpc_peering" ? length(var.route_table_ids) : 0
  route_table_id     = var.route_table_ids[count.index]   # change your route table IDs
  destination_cidr_block = var.peer_cidr_block           # target VPC CIDR
  vpc_peering_connection_id = aws_vpc_peering_connection.peer[0].id
}

# VPN Connection (for remote on-prem / transit)
resource "aws_vpn_connection" "vpn" {
  count              = var.connection_type == "vpn" ? 1 : 0

  customer_gateway_id = var.customer_gateway_id         # change customer gateway ID
  vpn_gateway_id      = var.vpn_gateway_id              # change VPN gateway ID
  type                = var.vpn_type                    # e.g., "ipsec.1"
  static_routes_only  = var.static_routes_only         # true/false
  enable_acceleration = var.enable_acceleration

  tags = {
    Name = "${var.env}-${var.resource_name}-vpn"      # change environment name and resource
    Env  = var.env                                     # change environment name
  }
}

# Optional VPN Routes
resource "aws_vpn_connection_route" "vpn_routes" {
  count                  = var.connection_type == "vpn" ? length(var.vpn_routes) : 0
  vpn_connection_id      = aws_vpn_connection.vpn[0].id
  destination_cidr_block = var.vpn_routes[count.index]   # change destination CIDRs
}

Example: modules/vpc-connection/variables.tf

# Environment name
variable "env" {
  type    = string
  default = "sendbox"   # change environment name (prod, dev, etc.)
}

# Resource identifier
variable "resource_name" {
  type = string       # change resource name
}

# Connection type: "vpc_peering" or "vpn"
variable "connection_type" {
  type    = string
  default = "vpc_peering"
}

# VPC Peering specific
variable "vpc_id" {
  type = string       # change source VPC ID
}
variable "peer_vpc_id" {
  type = string       # change target VPC ID
}
variable "peer_region" {
  type    = string
  default = ""        # optional for same-region peering
}
variable "auto_accept" {
  type    = bool
  default = true
}
variable "route_table_ids" {
  type    = list(string)
  default = []        # list of route table IDs to add peering routes
}
variable "peer_cidr_block" {
  type = string       # target VPC CIDR for route
}

# VPN specific
variable "customer_gateway_id" {
  type    = string
  default = ""        # change customer gateway ID for VPN
}
variable "vpn_gateway_id" {
  type    = string
  default = ""        # change VPN gateway ID
}
variable "vpn_type" {
  type    = string
  default = "ipsec.1"
}
variable "static_routes_only" {
  type    = bool
  default = true
}
variable "enable_acceleration" {
  type    = bool
  default = false
}
variable "vpn_routes" {
  type    = list(string)
  default = []        # list of destination CIDRs for VPN
}

Example: modules/vpc-connection/outputs.tf

# VPC Peering ID
output "vpc_peering_id" {
  value = var.connection_type == "vpc_peering" ? aws_vpc_peering_connection.peer[0].id : ""
}

# VPN Connection ID
output "vpn_connection_id" {
  value = var.connection_type == "vpn" ? aws_vpn_connection.vpn[0].id : ""
}
Example Usage for VPC Peering
module "vpc_peering" {
  source        = "./modules/vpc-connection"
  env           = "prod"                         # change environment name
  resource_name = "app-vpc-peer"                 # change resource name
  connection_type = "vpc_peering"

  vpc_id          = aws_vpc.app.id               # source VPC
  peer_vpc_id     = aws_vpc.shared.id            # target VPC
  peer_region     = "us-east-1"                  # optional
  auto_accept     = true
  route_table_ids = [aws_route_table.app.id]     # route tables to update
  peer_cidr_block = "10.1.0.0/16"               # target VPC CIDR
}
Example Usage for VPN
module "vpn_connection" {
  source        = "./modules/vpc-connection"
  env           = "prod"                         # change environment name
  resource_name = "app-vpn"                      # change resource name
  connection_type = "vpn"

  customer_gateway_id = aws_customer_gateway.onprem.id
  vpn_gateway_id      = aws_vpn_gateway.app.id
  vpn_type            = "ipsec.1"
  static_routes_only  = true
  enable_acceleration = false
  vpn_routes          = ["10.1.0.0/16", "10.2.0.0/16"]
}

Where to change:
  • env → environment name (tags & resource names)
  • resource_name → module resource identifier
  • VPC Peering: vpc_id, peer_vpc_id, peer_region, route_table_ids, peer_cidr_block
  • VPN: customer_gateway_id, vpn_gateway_id, vpn_routes
========================================================================================================================

Folder Structure:
envs/project-template/
├── main.tf
├── variables.tf
├── terraform.tfvars
├── backend.tf
└── scripts/
    └── pacemaker_setup.sh

Example: envs/project-template/main.tf

provider "aws" {
  region = var.aws_region
}

########################
# <<< CHANGE ENVIRONMENT NAME HERE >>>
# var.env controls naming, tags, cluster names, alarms
########################

# Network Module
module "network" {
  source         = "../../modules/network"
  vpc_cidr       = var.vpc_cidr
  public_subnets = var.public_subnets
  app_subnets    = var.app_subnets
  db_subnets     = var.db_subnets
  azs            = var.azs
  env            = var.env
}

# NACL Module
module "nacl" {
  source         = "../../modules/nacl"
  vpc_id         = module.network.vpc_id
  public_subnets = module.network.public_subnets
  app_subnets    = module.network.app_subnets
  db_subnets     = module.network.db_subnets
  internal_cidr  = var.vpc_cidr
  env            = var.env
}

# Inspection VPC
module "inspection_vpc" {
  source          = "../../modules/inspection-vpc"
  vpc_cidr        = var.inspection_vpc_cidr
  public_subnets  = var.inspection_public_subnets
  private_subnets = var.inspection_private_subnets
  azs             = var.azs
  env             = var.env
}

# TGW Module
module "tgw" {
  source = "../../modules/tgw"
  env    = var.env
  vpc_attachments = var.tgw_vpc_attachments
  route_table_id  = var.tgw_route_table_id
  enable_peering  = var.tgw_enable_peering
  peer_tgw_id     = var.peer_tgw_id
  peer_account_id = var.peer_account_id
  peer_region     = var.peer_region
}

# Flow Logs
module "flowlogs" {
  source       = "../../modules/flowlogs"
  vpc_id       = module.network.vpc_id
  iam_role_arn = var.flowlogs_iam_role_arn
  env          = var.env
}

# Security Groups
module "sg" {
  source             = "../../modules/security-groups"
  vpc_id             = module.network.vpc_id
  public_cidr_blocks = var.public_cidr_blocks
  env                = var.env
}

# ALB
module "alb" {
  source          = "../../modules/alb"
  vpc_id          = module.network.vpc_id
  public_subnets  = module.network.public_subnets
  web_sg_id       = module.sg.web_sg_id
  certificate_arn = var.acm_certificate_arn
  waf_acl_id      = var.waf_acl_id
  env             = var.env
}

# NLB
module "nlb" {
  source        = "../../modules/nlb"
  vpc_id        = module.network.vpc_id
  subnets       = module.network.app_subnets
  listener_port = var.nlb_listener_port
  target_port   = var.nlb_target_port
  internal      = var.nlb_internal
  env           = var.env
}

# Compute
module "compute" {
  source            = "../../modules/compute"
  vpc_id            = module.network.vpc_id
  app_subnets       = module.network.app_subnets
  db_subnets        = module.network.db_subnets
  web_sg_id         = module.sg.web_sg_id
  app_sg_id         = module.sg.app_sg_id
  db_sg_id          = module.sg.db_sg_id
  ami_id            = var.golden_ami_id
  instance_type     = var.instance_type
  key_name          = var.key_name
  asg_min_size      = var.asg_min_size
  asg_max_size      = var.asg_max_size
  asg_desired_count = var.asg_desired_count
  env               = var.env
}

# Bastion / Jump Box
module "bastion" {
  source             = "../../modules/bastion"
  env                = var.env
  resource_name      = "bastion"
  vpc_id             = module.network.vpc_id
  subnet_id          = module.network.public_subnets[0]
  ami_id             = var.bastion_ami_id
  instance_type      = var.bastion_instance_type
  key_name           = var.key_name
  associate_public_ip = true
  allowed_ips        = var.bastion_allowed_ips
}

# CloudWatch Alarms (Compute & Bastion)
module "cloudwatch_alarms_compute" {
  source             = "../../modules/cloudwatch-alarms"
  env                = var.env
  resource_name      = "compute"
  alarm_actions      = var.cloudwatch_sns_arns
  dimensions         = { InstanceId = module.compute.app_instance_ids[0] }
  enable_memory_alarm = true
}

module "cloudwatch_alarms_bastion" {
  source             = "../../modules/cloudwatch-alarms"
  env                = var.env
  resource_name      = "bastion"
  alarm_actions      = var.cloudwatch_sns_arns
  dimensions         = { InstanceId = module.bastion.instance_id }
  enable_memory_alarm = false
}

# VPC Peering / VPN
module "vpc_connection" {
  source          = "../../modules/vpc-connection"
  env             = var.env
  resource_name   = "peer"
  connection_type = var.vpc_connection_type
  vpc_id          = module.network.vpc_id
  peer_vpc_id     = var.peer_vpc_id
  peer_region     = var.peer_region
  auto_accept     = true
  route_table_ids = var.peer_route_table_ids
  peer_cidr_block = var.peer_cidr_block
}

# Pacemaker HA
module "pacemaker" {
  source               = "../../modules/pacemaker"
  vpc_id               = module.network.vpc_id
  private_subnets      = module.network.app_subnets
  ami_id               = var.golden_ami_id
  instance_type        = var.instance_type
  key_name             = var.key_name
  internal_cidr_blocks = [var.vpc_cidr]
  ssh_cidr_blocks      = var.ssh_cidr_blocks
  user_data_script     = var.pacemaker_user_data
  env                  = var.env
}

Example: envs/project-template/variables.tf

############################
# Core Environment
############################
variable "aws_region" {
  type    = string
  default = "us-east-1"             # <<< CHANGE HERE if region differs
}

variable "env" {
  type    = string
  default = "sendbox"               # <<< CHANGE HERE for environment name (dev/qa/prod)
}

############################
# Remote State
############################
variable "tfstate_bucket" {
  type        = string
  description = "S3 bucket for remote Terraform state"
}

variable "tfstate_lock_table" {
  type        = string
  description = "DynamoDB table for state locking"
}

############################
# Networking
############################
variable "vpc_cidr" {
  type    = string
  default = "10.0.0.0/16"          # <<< CHANGE HERE for VPC CIDR
}

variable "public_subnets" {
  type    = list(string)
  default = ["10.0.1.0/24","10.0.2.0/24"]  # <<< CHANGE HERE
}

variable "app_subnets" {
  type    = list(string)
  default = ["10.0.11.0/24","10.0.12.0/24"]  # <<< CHANGE HERE
}

variable "db_subnets" {
  type    = list(string)
  default = ["10.0.21.0/24","10.0.22.0/24"]  # <<< CHANGE HERE
}

variable "azs" {
  type    = list(string)
  default = ["us-east-1a","us-east-1b"]  # <<< CHANGE HERE
}

############################
# Inspection VPC
############################
variable "inspection_vpc_cidr" {
  type    = string
  default = "10.100.0.0/16"          # <<< CHANGE HERE
}

variable "inspection_public_subnets" {
  type    = list(string)
  default = ["10.100.1.0/24","10.100.2.0/24"]  # <<< CHANGE HERE
}

variable "inspection_private_subnets" {
  type    = list(string)
  default = ["10.100.11.0/24","10.100.12.0/24"]  # <<< CHANGE HERE
}

############################
# Transit Gateway
############################
variable "tgw_route_table_id" {
  type    = string
  default = ""                      # <<< CHANGE HERE
}

variable "tgw_vpc_attachments" {
  type    = map(any)
  default = {}                       # <<< CHANGE HERE per project
}

variable "tgw_enable_peering" {
  type    = bool
  default = false                    # <<< CHANGE HERE if TGW peering required
}

variable "peer_tgw_id" {
  type    = string
  default = ""                        # <<< CHANGE HERE if TGW peering
}

variable "peer_account_id" {
  type    = string
  default = ""                        # <<< CHANGE HERE if TGW peering
}

variable "peer_region" {
  type    = string
  default = ""                        # <<< CHANGE HERE if TGW peering
}

############################
# Flow Logs
############################
variable "flowlogs_iam_role_arn" {
  type    = string
  default = ""                        # <<< CHANGE HERE
}

############################
# Security Groups
############################
variable "public_cidr_blocks" {
  type    = list(string)
  default = ["0.0.0.0/0"]             # <<< CHANGE HERE
}

############################
# ALB / WAF / ACM
############################
variable "acm_certificate_arn" {
  type    = string
  default = ""                        # <<< CHANGE HERE
}

variable "waf_acl_id" {
  type    = string
  default = ""                        # <<< CHANGE HERE
}

############################
# NLB
############################
variable "nlb_listener_port" {
  type    = number
  default = 3306                     # <<< CHANGE HERE
}

variable "nlb_target_port" {
  type    = number
  default = 3306                     # <<< CHANGE HERE
}

variable "nlb_internal" {
  type    = bool
  default = true                     # <<< CHANGE HERE
}

############################
# Compute (EC2 + ASG)
############################
variable "golden_ami_id" {
  type    = string
  default = ""                        # <<< CHANGE HERE
}

variable "instance_type" {
  type    = string
  default = "t3.medium"              # <<< CHANGE HERE
}

variable "key_name" {
  type    = string
  default = ""                        # <<< CHANGE HERE
}

variable "asg_min_size" {
  type    = number
  default = 1
}

variable "asg_max_size" {
  type    = number
  default = 3
}

variable "asg_desired_count" {
  type    = number
  default = 2
}

############################
# Bastion / Jump Box
############################
variable "bastion_ami_id" {
  type    = string
  default = ""                        # <<< CHANGE HERE
}

variable "bastion_instance_type" {
  type    = string
  default = "t3.micro"               # <<< CHANGE HERE
}

variable "bastion_allowed_ips" {
  type    = list(string)
  default = ["0.0.0.0/0"]            # <<< CHANGE HERE
}

############################
# CloudWatch
############################
variable "cloudwatch_sns_arns" {
  type    = list(string)
  default = []                        # <<< CHANGE HERE
}

############################
# VPC Peering / VPN
############################
variable "vpc_connection_type" {
  type    = string
  default = "vpc_peering"            # <<< CHANGE HERE if using VPN
}

variable "peer_vpc_id" {
  type    = string
  default = ""                        # <<< CHANGE HERE
}

variable "peer_route_table_ids" {
  type    = list(string)
  default = []                        # <<< CHANGE HERE
}

variable "peer_cidr_block" {
  type    = string
  default = ""                        # <<< CHANGE HERE
}

############################
# Pacemaker HA
############################
variable "ssh_cidr_blocks" {
  type    = list(string)
  default = ["10.0.0.0/16"]           # <<< CHANGE HERE
}

variable "pacemaker_user_data" {
  type    = string
  default = "${path.module}/scripts/pacemaker_setup.sh"
}

Example: envs/project-template/terraform.tfvars

# <<< CHANGE ENVIRONMENT HERE >>>
env = "sendbox"                        # <<< CHANGE HERE for dev/qa/prod

aws_region = "us-east-1"               # <<< CHANGE HERE

# Remote state
tfstate_bucket     = "my-terraform-states"    # <<< CHANGE HERE
tfstate_lock_table = "terraform-locks"        # <<< CHANGE HERE

# Networking
vpc_cidr       = "10.0.0.0/16"                # <<< CHANGE HERE
public_subnets = ["10.0.1.0/24","10.0.2.0/24"] # <<< CHANGE HERE
app_subnets    = ["10.0.11.0/24","10.0.12.0/24"]
db_subnets     = ["10.0.21.0/24","10.0.22.0/24"]
azs            = ["us-east-1a","us-east-1b"]

# Inspection VPC
inspection_vpc_cidr        = "10.100.0.0/16"
inspection_public_subnets  = ["10.100.1.0/24","10.100.2.0/24"]
inspection_private_subnets = ["10.100.11.0/24","10.100.12.0/24"]

# TGW
tgw_route_table_id  = ""
tgw_vpc_attachments = {}        # Fill per project
tgw_enable_peering  = false
peer_tgw_id         = ""
peer_account_id     = ""
peer_region         = ""

# Flow Logs
flowlogs_iam_role_arn = ""

# Security Groups
public_cidr_blocks = ["0.0.0.0/0"]

# ALB/WAF/ACM
acm_certificate_arn = ""
waf_acl_id          = ""

# NLB
nlb_listener_port = 3306
nlb_target_port   = 3306
nlb_internal      = true

# Compute (EC2 + ASG)
golden_ami_id     = ""
instance_type     = "t3.medium"
key_name          = ""
asg_min_size      = 1
asg_max_size      = 3
asg_desired_count = 2

# Bastion
bastion_ami_id       = ""
bastion_instance_type = "t3.micro"
bastion_allowed_ips  = ["0.0.0.0/0"]

# CloudWatch
cloudwatch_sns_arns = []

# VPC Peering / VPN
vpc_connection_type  = "vpc_peering"
peer_vpc_id          = ""
peer_route_table_ids = []
peer_cidr_block      = ""

# Pacemaker HA
ssh_cidr_blocks      = ["10.0.0.0/16"]
pacemaker_user_data  = "${path.module}/scripts/pacemaker_setup.sh"

Example: envs/project-template/backend.tf

terraform {
  backend "s3" {
    bucket         = var.tfstate_bucket
    key            = "${var.env}/terraform.tfstate"   # <<< Automatically changes per environment
    region         = var.aws_region
    dynamodb_table = var.tfstate_lock_table
    encrypt        = true
  }
}

How to use:
Copy envs/project-template/ → envs/myproject.
Edit terraform.tfvars with your project-specific values (env name, VPC, AMIs, TGW, ALBs, Bastion, CloudWatch, etc.).
Optionally update variables defaults in variables.tf.
Deploy:
  • terraform init
  • terraform plan
  • terraform apply
Pacemaker script runs automatically on nodes for HA floating IPs.

========================================================================================================================
Example: global/accounts.tf – Hub-Spoke Multi-Account Mapping

# Example: Multi-account mapping for Hub-Spoke landing zone
# <<< CHANGE OR ADD ENVIRONMENTS HERE >>>
variable "accounts" {
  type = map(object({
    account_id   = string
    environment  = string
    role_name    = string
    region       = string
  }))
  default = {
    hub = {
      account_id  = "111111111111"                # <<< CHANGE HERE for Hub AWS account ID
      environment = "global"                     # <<< CHANGE HERE for Hub environment name
      role_name   = "OrganizationAccountAccessRole"  # <<< CHANGE HERE if role differs
      region      = "us-east-1"                  # <<< CHANGE HERE for Hub region
    }
    sendbox = {
      account_id  = "222222222222"               # <<< CHANGE HERE for Sandbox AWS account ID
      environment = "sendbox"                    # <<< CHANGE HERE for Sandbox environment name
      role_name   = "OrganizationAccountAccessRole"
      region      = "us-east-1"
    }
    qa = {
      account_id  = "333333333333"               # <<< CHANGE HERE for QA AWS account ID
      environment = "qa"                         # <<< CHANGE HERE for QA environment name
      role_name   = "OrganizationAccountAccessRole"
      region      = "us-east-1"
    }
    prod = {
      account_id  = "444444444444"               # <<< CHANGE HERE for Prod AWS account ID
      environment = "prod"                       # <<< CHANGE HERE for Prod environment name
      role_name   = "OrganizationAccountAccessRole"
      region      = "us-east-1"
    }
  }
}

# Output to share account mapping with other modules/envs
output "account_map" {
  value = var.accounts
}

Example: global/backend.tf – Shared Backend S3 + DynamoDB Lock

terraform {
  backend "s3" {
    bucket         = "my-terraform-states"      # <<< CHANGE HERE for S3 bucket
    key            = "global/terraform.tfstate" # <<< CHANGE HERE if needed per environment
    region         = "us-east-1"                # <<< CHANGE HERE for bucket region
    dynamodb_table = "terraform-locks"          # <<< CHANGE HERE for DynamoDB table
    encrypt        = true
  }
}

# Tip: To parameterize by environment
# key = "${var.environment}/terraform.tfstate"

Example: global/providers.tf – Shared AWS Provider

# AWS region for global resources
variable "aws_region" {
  type    = string
  default = "us-east-1"       # <<< CHANGE HERE for AWS region
}

# Optional role ARN for cross-account assume role
variable "assume_role_arn" {
  type    = string
  default = ""                # <<< CHANGE HERE if using cross-account deployment
}

provider "aws" {
  region = var.aws_region

  # Optional multi-account assume role
  assume_role {
    role_arn     = var.assume_role_arn
    session_name = "TerraformGlobal"
  }
}

Where to change per environment/account:
account_id: AWS account ID for that environment (e.g., Sandbox, QA, Prod).
environment: Name of the environment, used in resource naming and state paths.
role_name: IAM role you will assume in that account.
region: AWS region for that environment/account.
bucket/key: In backend.tf, change S3 bucket, key, region, or DynamoDB lock table if needed.

SaltStack

Salt is an open-source configuration management, remote execution, and infrastructure automation platform. It enables centralized management of servers and cloud systems by allowing administrators to:
  • Execute commands across multiple systems simultaneously
  • Enforce desired configurations (Infrastructure as Code)
  • Automate deployments and patching
  • Orchestrate complex workflows
  • Manage hybrid and multi-cloud environments
Salt is designed for speed, scalability, security, and event-driven automation.

Salt primarily operates using a Master–Minion architecture, with optional agentless execution using salt-ssh.

salt-master:
The central control node responsible for:
  • Sending commands to minions
  • Managing authentication keys
  • Storing and serving state files
  • Publishing jobs on port 4505
  • Receiving job returns on port 4506
salt-minion:
The agent installed on managed systems:
  • Establishes encrypted connection to master
  • Executes commands and states
  • Returns execution results
  • Maintains persistent communication
salt-ssh:
Agentless execution method:
  • Uses SSH instead of minion service
  • No agent installation required
  • Suitable for restricted environments
salt-syndic
Hierarchical scaling component:
  • Connects multiple lower-level masters to a higher-level master
  • Used in large enterprise environments
  • Improves scalability and segmentation
salt-cloud:
Cloud provisioning tool:
  • Creates and manages cloud instances
  • Integrates with public cloud providers
  • Can automatically bootstrap minions
salt-api:
RESTful API interface:
Enables external systems to interact with Salt
Used in CI/CD, dashboards, and automation platforms
Allows programmatic control of infrastructure

Communication Model:
Salt uses a high-speed publish/subscribe (pub/sub) messaging system.
  • Port 4505 → Master publishes commands
  • Port 4506 → Minions return execution results
Communication is encrypted and asynchronous, allowing parallel execution across thousands of nodes.

Authentication Process:
  • Minion starts and connects to master
  • Minion sends its public key
  • Master administrator accepts the key
  • Secure communication begins
    Key management is handled using:
    salt-key

1. Installing Salt Components
1.1 Add Salt Repository
# curl -fsSL https://github.com/saltstack/salt-install-guide/releases/latest/download/salt.repo | sudo tee /etc/yum.repos.d/salt.repo
1.2 Install Packages
# dnf install salt-master
# dnf install salt-minion
# dnf install salt-ssh
# dnf install salt-syndic
# dnf install salt-cloud
# dnf install salt-api

2. Service Management
Enable and start services:
# systemctl enable salt-master && sudo systemctl start salt-master
# systemctl enable salt-minion && sudo systemctl start salt-minion
# systemctl enable salt-syndic && sudo systemctl start salt-syndic
# systemctl enable salt-api && sudo systemctl start salt-api
Check status:
# systemctl status salt-master
# systemctl status salt-minion

3. Salt Master Configuration
3.1 Network Settings
Default:
Binds to all interfaces
Ports 4505 and 4506
Custom configuration:
# vi /etc/salt/master.d/network.conf
interface: 192.168.10.20
ret_port: 4506
publish_port: 4505

3.2 Worker Threads
# vi /etc/salt/master.d/thread_options.conf
worker_threads: 5

4. Salt Minion Configuration
4.1 Master Address
# vi /etc/salt/minion.d/master.conf
master: 192.168.10.20
4.2 Minion ID
# vi /etc/salt/minion.d/id.conf
id: rebel_1

5. Remote Deployment via Jump Server:
Deployment can be automated using shell scripts executed from a centralized jump server.
5.1 master.sh
Usage
./master.sh <master-server>
Script:
#!/bin/bash
if [ -z "$1" ]; then
  echo "Usage: ./master.sh <master-server>"
  exit 1
fi
MASTER_HOST=$1
ssh $MASTER_HOST <<'EOF'
sudo curl -fsSL https://github.com/saltstack/salt-install-guide/releases/latest/download/salt.repo -o /etc/yum.repos.d/salt.repo
sudo dnf install -y salt-master salt-ssh salt-syndic salt-cloud salt-api
sudo mkdir -p /etc/salt/master.d
sudo tee /etc/salt/master.d/network.conf > /dev/null <<EOC
interface: 0.0.0.0
ret_port: 4506
publish_port: 4505
EOC
sudo tee /etc/salt/master.d/thread_options.conf > /dev/null <<EOC
worker_threads: 5
EOC
sudo systemctl enable salt-master
sudo systemctl restart salt-master
sudo systemctl enable salt-api
sudo systemctl restart salt-api
EOF

5.2 minion.sh
Usage
./minion.sh <client-server> <master-ip>
Script:
#!/bin/bash
if [ -z "$1" ] || [ -z "$2" ]; then
  echo "Usage: ./minion.sh <client-server> <master-ip>"
  exit 1
fi
CLIENT_HOST=$1
MASTER_IP=$2
ssh $CLIENT_HOST <<EOF
sudo curl -fsSL https://github.com/saltstack/salt-install-guide/releases/latest/download/salt.repo -o /etc/yum.repos.d/salt.repo
sudo dnf install -y salt-minion
sudo mkdir -p /etc/salt/minion.d
sudo tee /etc/salt/minion.d/master.conf > /dev/null <<EOC
master: $MASTER_IP
EOC
sudo tee /etc/salt/minion.d/id.conf > /dev/null <<EOC
id: \$(hostname -s)
EOC
sudo systemctl enable salt-minion
sudo systemctl restart salt-minion
EOF

6. Key Management
View keys:
salt-key
Accept specific key:
# salt-key -a <minion-id>
Accept all:
# salt-key -A
Delete key:
# salt-key -d <minion-id>
Automatically accepting keys is not recommended in production environments.

7. Testing Connectivity
# salt '*' test.ping
Expected output:
minion-id:
    True
8. Security Best Practices
  • Do not expose Salt Master to the public internet
  • Restrict ports 4505 and 4506
  • Avoid auto_accept: True in production
  • Bind master to private interface
  • Regularly audit and rotate keys
  • Monitor /var/log/salt/
9. Salt Master Reference — Execution Modules + State Keys + Orchestration
This reference provides a comprehensive overview of SaltStack for system administrators and DevOps engineers. It includes:
  • Execution Modules – Commands to manage packages, services, files, users, networking, cloud resources, and Windows systems.
  • State / SLS Keys – All keys and arguments used in Salt states (name, enable, require, watch, onlyif, onchanges, etc.) for declarative configuration management.
  • Orchestration & Targeting Keys – Keys for multi-minion orchestration (tgt, tgt_type, batch, queue, failhard, pillar) to coordinate tasks across complex environments.
This master cheat sheet enables efficient, consistent, and secure automation across Linux, Windows, VMware, Azure, GCP, and cloud-native infrastructure, providing a one-stop reference for both beginners and experienced Salt users.

Targeting Keys (Orchestration / Remote Execution)
tgt ---> Target expression for minions (e.g., '*', 'web*', 'G@os:Ubuntu')
tgt_type ---> Type of target expression (glob, list, grain, pillar, regex, etc.)
batch ---> Run state in batches (percentage or count)
queue ---> Queue states to run sequentially
expr_form ---> Alias for tgt_type
failhard ---> Stop orchestration if a minion fails (True/False)

State / SLS Keys (All States)
name ---> Name of the resource (package, service, file, user)
sls ---> Reference SLS file(s) to apply
include ---> Include other SLS files
exclude ---> Exclude specific SLS files
extend ---> Extend another state declaration
require ---> Require another state before running
require_in ---> Make other state depend on this state
watch ---> Trigger state if watched state changes
watch_in ---> Watch another state to trigger this state
onchanges ---> Run only if another state changes
onfail ---> Run only if another state fails
listen ---> Listen for events
listen_in ---> Trigger state in response to events
order ---> Force execution order (numeric)
force ---> Force execution even if already correct
test ---> Dry-run / check mode (True/False)

File & Directory Keys
source ---> Source file (salt:// or URL)
contents ---> Direct content for file
template ---> Template engine (jinja/mako)
user ---> File owner user
group ---> File owner group
mode ---> File permissions
backup ---> Keep backup of modified file
replace ---> Replace content in file (True/False)
makedirs ---> Create parent directories if missing
saltenv ---> Environment to get source from

Package / Service Keys
version ---> Specific version of package
refresh ---> Refresh repo cache
skip_verify ---> Skip GPG verification
allow_downgrade ---> Allow downgrades
enable ---> Enable service at boot (True/False)
start ---> Start service (True/False)
stop ---> Stop service (True/False)
reload ---> Reload service configuration
masked ---> Mask service
start_type ---> Windows service start type (auto/manual/disabled)

Command / Execution Keys
cwd ---> Current working directory
shell ---> Shell to run command (bash, sh, powershell)
env ---> Environment variables dictionary
timeout ---> Max time to run command (seconds)
runas ---> Run command as a specific user
runas_group ---> Run command as a specific group
onlyif ---> Run state only if command returns True
unless ---> Skip state if command returns True

Cloud / VM Keys
vm_name ---> Name of the VM
host ---> VMware host
datacenter ---> VMware datacenter
resource_group ---> Azure resource group
project ---> GCP project
zone ---> GCP VM zone
template ---> Template to deploy VM
snapshot_name ---> Name of VM snapshot
state ---> Desired state (running, stopped, present, absent)

Pillars & Variables
pillar ---> Pass pillar dictionary or reference
saltenv ---> Specify Salt environment (base, dev, prod)
grains ---> Grain expressions to target minions
context ---> Pass context variables

Scheduling Keys
function ---> Function to execute
minutes ---> Minute interval
hours ---> Hour interval
when ---> Specific time to run
enabled ---> Enable/disable schedule
return_job ---> Return job ID for scheduled state

Orchestration / Event Keys
listen ---> Listen for events
listen_in ---> Trigger state based on another state’s event
onchanges ---> Run if watched state changes
onfail ---> Run if watched state fails
require_in ---> Make other states depend on this state
failhard ---> Stop orchestration on failure
concurrent ---> Run state concurrently
batch ---> Run states in batches
queue ---> Queue execution for sequential order

System & Package Modules
pkg.upgrade ---> Upgrade all packages on a minion
pkg.install ---> Install specified packages
pkg.remove ---> Remove specified packages
pkg.version ---> Check version of a package
pkg.available ---> List available packages from repositories
system.reboot ---> Reboot the minion system
system.shutdown ---> Shutdown the minion system
system.halt ---> Halt the minion system
system.get_hw_info ---> Retrieve hardware info
system.reboot_required ---> Check if reboot required
sysctl.get ---> Get sysctl parameters
sysctl.set ---> Set sysctl parameters

Service & Process Modules
service.start ---> Start a service on a minion
service.stop ---> Stop a service
service.restart ---> Restart a service
service.status ---> Get status of a service
service.enable ---> Enable service at boot
service.disable ---> Disable service at boot
ps.ps ---> List processes
ps.kill ---> Kill process by PID

File & Command Modules
file.manage ---> Manage file attributes and content
file.remove ---> Remove a file or directory
file.copy ---> Copy files on a minion
file.makedirs ---> Create directories recursively
file.symlink ---> Manage symbolic links
file.append ---> Append text to a file
file.replace ---> Replace content in a file
cmd.run ---> Execute arbitrary shell command
cmd.shell ---> Run command in shell interpreter
cmd.exec_code ---> Execute code string
archive.extracted ---> Extract archive file

Users & Groups Modules
user.add ---> Create system user
user.delete ---> Delete system user
user.chpasswd ---> Change user password
group.add ---> Create group
group.delete ---> Delete group
group.adduser ---> Add user to a group

Grains & System Info
grains.items ---> Show all grains
grains.item ---> Show specific grain
grains.setval ---> Set custom grain
grains.delval ---> Delete grain
status.diskusage ---> Show disk usage
status.cpuinfo ---> Show CPU info
status.meminfo ---> Show memory usage
status.uptime ---> Show system uptime
system.version ---> Show Salt version

Windows Modules
win_wua.list ---> List Windows updates
win_wua.update ---> Apply Windows updates
win_service.start ---> Start Windows service
win_service.stop ---> Stop Windows service
win_service.restart ---> Restart Windows service
win_pkg.install ---> Install Windows package (Chocolatey)
win_pkg.remove ---> Remove Windows package
win_user.add ---> Create Windows user
win_user.delete ---> Delete Windows user
reg.read_key ---> Read registry key
reg.write_key ---> Write registry key
reg.delete_key ---> Delete registry key
win_timezone.get ---> Get Windows timezone
win_timezone.set ---> Set Windows timezone

Cloud / Virtualization Modules
vsphere.list_vms ---> List VMs in VMware vCenter
vsphere.power_on ---> Power on VMware VM
vsphere.power_off ---> Power off VMware VM
vsphere.snapshot_create ---> Create snapshot of VM
vsphere.snapshot_revert ---> Revert VM to snapshot
azurearm.vm_start ---> Start Azure VM
azurearm.vm_stop ---> Stop Azure VM
azurearm.vm_restart ---> Restart Azure VM
gcpcompute.list_instances ---> List GCP VMs
gcpcompute.start ---> Start GCP VM
gcpcompute.stop ---> Stop GCP VM
saltcloudmod.create ---> Create cloud instance via Salt Cloud
saltcloudmod.destroy ---> Destroy cloud instance via Salt Cloud

Networking & Misc Modules
network.interfaces ---> List network interfaces
network.ip_addrs ---> List IP addresses
network.routes ---> Show routing table
hosts.manage ---> Manage /etc/hosts
dnsmasq.configure ---> Configure dnsmasq
iptables.append_rule ---> Append iptables rule
iptables.delete_rule ---> Delete iptables rule
firewalld.add_port ---> Add port to firewalld
firewalld.remove_port ---> Remove port from firewalld
docker.running ---> Ensure Docker container running
docker.stopped ---> Ensure Docker container stopped

Scheduling & Utility Modules
schedule.list ---> List scheduled jobs
schedule.modify ---> Modify scheduled job
schedule.delete ---> Delete scheduled job
saltutil.refresh_modules ---> Refresh modules on minion
saltutil.sync_all ---> Sync modules, states, grains, etc.
mine.send ---> Publish data to Salt Mine
mine.get ---> Retrieve data from Salt Mine
mine.update ---> Update mine functions
test.ping ---> Ping minion
test.version ---> Show minion version
test.echo ---> Echo message
random.get_bytes ---> Generate random bytes
sysmod.list_functions ---> List loaded functions
sysmod.doc ---> Show module documentation

Common State / SLS Keys
name ---> Name of resource (package, service, file, user)
sls ---> Reference SLS file(s)
include ---> Include other SLS files
exclude ---> Exclude SLS files
extend ---> Extend another state declaration
require ---> Require another state before running
require_in ---> Make other state depend on this state
watch ---> Trigger state if watched state changes
watch_in ---> Watch another state to trigger this state
onchanges ---> Run only if another state changes
onfail ---> Run only if another state fails
listen ---> Listen for events
listen_in ---> Trigger state based on event in another state
order ---> Force execution order (numeric)
force ---> Force execution even if already correct
test ---> Dry-run / check mode (True/False)
onlyif ---> Run state only if command returns True
unless ---> Skip state if command returns True
backup ---> Keep backup of modified file
template ---> Template engine (jinja/mako)
contents ---> Direct content for file
source ---> File source (salt:// or URL)
user ---> Owner user
group ---> Owner group
mode ---> File permissions
makedirs ---> Create parent directories if missing
start_type ---> Windows service start type
timeout ---> Max time to run command
env ---> Environment variables dictionary
cwd ---> Current working directory
shell ---> Shell for command execution
runas ---> Run as specific user
runas_group ---> Run as specific group

Orchestration / Remote Execution Keys
tgt ---> Target minions (*, web*, G@os:Ubuntu)
tgt_type ---> Target expression type (glob, list, grain, pillar, regex)
expr_form ---> Alias for tgt_type
batch ---> Run state in batches (percentage or number)
queue ---> Queue execution sequentially
failhard ---> Stop orchestration on failure (True/False)
pillar ---> Pass pillar data dictionary
saltenv ---> Salt environment (base/dev/prod)
grains ---> Grain expression for targeting
context ---> Pass context variables
concurrent ---> Run state concurrently
return_job ---> Return job ID

10. Salt Command CLI Reference:
salt (Remote Execution)
salt '*' test.ping ---> Test connectivity to all minions
salt 'web*' cmd.run 'uptime' ---> Run shell command on minions matching web*
salt -G 'os:Rocky' test.version ---> Run function on minions with grain os:Rocky
salt -L 'web1,db1' service.restart nginx ---> Restart nginx on specific minions
salt -E 'web[0-9]' pkg.install vim ---> Install vim on minions matching regex
salt -C 'G@os:Rocky and web*' cmd.run 'uptime' ---> Compound targeting based on grains and names
salt '*' state.apply nginx ---> Apply nginx state to all minions
salt '*' state.highstate ---> Apply top file states to all minions
salt '*' state.apply test=True ---> Dry-run state application (no changes)
salt '*' grains.items ---> Show all grains
salt '*' grains.item os ---> Show specific grain
salt '*' grains.setval role web ---> Set grain 'role' to 'web'
salt '*' grains.delval role ---> Delete grain 'role'
salt '*' pillar.items ---> Show all pillar data
salt '*' pillar.item database ---> Show specific pillar item
salt '*' saltutil.refresh_pillar ---> Refresh pillar data on minions
salt '*' network.interfaces ---> Show network interfaces
salt '*' saltutil.sync_all ---> Sync all modules from master to minions
salt '*' saltutil.refresh_modules ---> Refresh modules on minions

salt-call (Local Execution)
salt-call test.ping ---> Test connectivity locally on minion
salt-call state.apply ---> Apply all states locally
salt-call grains.items ---> Show all grains on minion
salt-call pillar.items ---> Show all pillar data on minion
salt-call --local test.ping ---> Run command locally without master

salt-key (Key Management)
salt-key -L ---> List all minion keys (Accepted, Unaccepted, Rejected)
salt-key -a <minion-id> ---> Accept a specific minion key
salt-key -A ---> Accept all pending minion keys
salt-key -r <minion-id> ---> Reject a specific minion key
salt-key -R ---> Reject all pending keys
salt-key -d <minion-id> ---> Delete a specific minion key
salt-key -D ---> Delete all minion keys
salt-key -f <minion-id> ---> Show fingerprint of a specific key
salt-key --list-accepted ---> List only accepted keys
salt-key --list-unaccepted ---> List only unaccepted keys
salt-key --list-rejected ---> List only rejected keys
salt-key --gen-keys=<name> ---> Generate new key pair
salt-key --gen-signature ---> Generate signing key

salt-run (Runner Modules / Orchestration)
salt-run jobs.list_jobs ---> List all jobs executed by Salt
salt-run jobs.lookup_jid <job_id> ---> Lookup result of a specific job
salt-run jobs.active ---> Show currently running jobs
salt-run manage.up ---> Show minions currently connected
salt-run manage.down ---> Show minions currently disconnected
salt-run state.orchestrate <orchestrate_file> ---> Run orchestration file
salt-run state.orchestrate test=True ---> Dry-run orchestration
salt-run state.event ---> Listen to Salt event bus
salt-run state.event pretty=True ---> Pretty-print events for debugging

salt-cp (File Copy)
salt-cp '*' /etc/hosts /tmp/hosts ---> Copy /etc/hosts from master to all minions
salt-cp 'web1' /tmp/nginx.conf /etc/nginx/nginx.conf ---> Copy file to specific minion

salt-ssh (Agentless Execution)
salt-ssh '*' test.ping ---> Test connectivity over SSH without minion
salt-ssh 'web1' cmd.run 'uptime' ---> Run shell command via SSH on minion
salt-ssh 'db*' state.apply ---> Apply states on agentless minions

salt-cloud (Cloud Management)
salt-cloud --list-providers ---> List configured cloud providers
salt-cloud --list-instances ---> List current cloud instances
salt-cloud -p <profile> <vm_name> ---> Create new VM using profile
salt-cloud -d <vm_name> ---> Destroy a cloud instance
salt-cloud --destroy-all ---> Destroy all cloud instances
salt-cloud -f <provider> ---> Destroy all instances for a specific provider
salt-cloud --profile-list ---> List all profiles

salt-master / salt-minion (Daemon Management)
salt-master ---> Start Salt master in foreground
salt-minion ---> Start Salt minion in foreground
systemctl start salt-master ---> Start Salt master service
systemctl stop salt-master ---> Stop Salt master service
systemctl restart salt-minion ---> Restart Salt minion service
systemctl status salt-minion ---> Check status of minion service
systemctl start salt-syndic ---> Start syndic service
systemctl status salt-syndic ---> Check syndic status
systemctl start salt-api ---> Start Salt API service
systemctl stop salt-api ---> Stop Salt API service

Testing & Debug
salt '*' test.ping ---> Test connectivity
salt '*' test.version ---> Show Salt version
salt '*' test.echo "hello" ---> Echo a message
salt '*' saltutil.sync_all ---> Sync all modules from master to minions
salt '*' saltutil.refresh_modules ---> Refresh modules on minions

Example Salt Playbook — example.sls
# example.sls
# Description: Demonstrates pkg, service, file, user, grains, pillar, and orchestration keys

# 1. Upgrade all packages
upgrade_system:
  pkg.upgrade:
    - name: '*'
    - refresh: True
    - test: False   # Set True for dry-run

# 2. Install nginx package
install_nginx:
  pkg.installed:
    - name: nginx
    - require:
      - pkg: upgrade_system

# 3. Ensure nginx service is running and enabled
nginx_service:
  service.running:
    - name: nginx
    - enable: True
    - require:
      - pkg: install_nginx

# 4. Deploy nginx configuration file from master
nginx_config:
  file.managed:
    - name: /etc/nginx/nginx.conf
    - source: salt://nginx/files/nginx.conf
    - user: root
    - group: root
    - mode: '0644'
    - require:
      - pkg: install_nginx
      - service: nginx_service
    - watch_in:
      - service: nginx_service

# 5. Add a deploy user
deploy_user:
  user.present:
    - name: deploy
    - uid: 1500
    - shell: /bin/bash
    - groups:
      - sudo

# 6. Set a custom grain value
set_role_grain:
  grains.present:
    - name: role
    - value: web

# 7. Conditional execution example
reload_nginx_if_config_changed:
  service.running:
    - name: nginx
    - watch:
      - file: nginx_config

# 8. Orchestration key example (batch + failhard)
upgrade_and_restart_web:
  module.run:
    - name: state.apply
    - tgt: 'web*'
    - batch: 50%
    - failhard: True

Example Execution Script — run_playbook.sh
#!/bin/bash
# Script: run_playbook.sh
# Description: Execute Salt states on a remote minion or group

# Usage: ./run_playbook.sh <target-minion>

TARGET=$1

if [ -z "$TARGET" ]; then
  echo "Usage: $0 <target-minion>"
  exit 1
fi

# Test connectivity
echo "Testing connectivity to minion $TARGET..."
salt "$TARGET" test.ping

# Apply top file highstate
echo "Applying highstate on $TARGET..."
salt "$TARGET" state.highstate

# Apply specific SLS file
echo "Applying example.sls on $TARGET..."
salt "$TARGET" state.apply example

# Restart nginx if configuration changed
echo "Ensuring nginx service is running and config is applied..."
salt "$TARGET" state.apply example test=False

Logical Migration Using Clone rootvg and Splitvg datavg – AIX LPAR Migration Procedure

This procedure describes a method for migrating an AIX LPAR to a new target LPAR by leveraging rootvg clone and splitvg for datavg.

The approach involves creating clone copies of the root volume group (rootvg) and splitting the data volume group (datavg) to generate an independent clone that can be imported on the target LPAR.

This method is particularly useful when source and target LPARs share the same storage subsystem, allowing for a fast, minimal-downtime migration with an exact replica of the source environment. It ensures the target system can boot independently and applications can resume quickly after cutover.

Scope:
  • Applicable when source and target LPARs share the same storage subsystem.
  • Suitable for quick cloning or migration with minimal OS and data reconstruction.
  • Performed in a controlled maintenance window to maintain data consistency.
Pre-Requisites:
  • Access to HMC and both source and target LPARs.
  • SAN connectivity between source and target LPARs.
  • Sufficient free LUNs to mirror rootvg and split datavg.
  • Scheduled downtime for applications and databases during split operations.
  • Coordination with storage team for LUN mapping/unmapping.
Step-by-Step Migration Procedure:

1. Create Target LPAR

From HMC, create a new LPAR profile:
Assign CPU, memory, and network configuration.
Add virtual FC adapters (NPIV) or vSCSI as required.

2. Provide WWNs to Storage Team
Share target LPAR WWPNs with storage team.
Verify WWPN login:
# chnportlogin -m <manage_system> -o login -p <LPAR>
# lsnportlogin -m <manage_system> --filter "lpar_names=<LPAR>"

3. Storage Team: Prepare and Map LUNs
Allocate LUNs equal in size to source rootvg and datavg.
Map these LUNs to both source and target LPARs for shared access.

4. Clone rootvg on Source LPAR
Identify new disks:
# lspv
Take the backup of /etc/filesystems file:
# cp -p /etc/filesystems /etc/filesystems_Backup_<DATE> 
Clone rootvg:
# alt_disk_copy -B -d <Disk Name> 
Note the disk serial no. of cloned rootvg:
lscfg -vl <disk name> |grep "Serial Number"
Release the LUNs for rootvg Cloned one, PURE Storage disk):
# rmdev -Rdl <disk name> 
Mirror datavg:
# extendvg datavg <Disk Name> 
# mirrorvg -S datavg 
<Disk Name> 
Check the VG sync (mirror) status:
# lsvg <VG Name> --> See the value of "STALE PPs: should be 0"

5. Split datavg Using splitvg
During the maintenance window:
# splitvg -y datavg -c datavg_clone
Note the cloned VG disks (e.g., hdiskY, hdiskZ).

6. varyoffvg and export Volume Groups
After verification:
# varyoffvg <vg name>
# exportvg <vg name> 

7. Unmap Cloned Disks from Source
Coordinate with storage team to unmap cloned rootvg and datavg disks from source LPAR.
Map these disks to the target LPAR.

8. Boot Target LPAR & Scan rootvg LUNs
Boot target LPAR via SMS menu or HMC:
SMS → Select Device → Boot from hdiskX (cloned rootvg)
Verify system:
# lsvg -o
# oslevel -s
Ask storage team to map additional LUNs for datavg.
Scan for new disks:
# cfgmgr -v
# lspv

9. Import datavg on Target LPAR
Import cloned data volume group:
# readvgda -v3 <Disk Name> 
# importvg -y datavg <Disk Name> 
Rename logical volumes or mount points if needed:
# chlv -n lv_old lv_new
# chfs -m /data/app /data_new/app
# mount -a
# df -g

10. Final Configuration and Validation
Update /etc/hosts with target IP and hostname.
Adjust network settings:
# chdev -l inet0 -a netaddr=<new_ip> -a netmask=<mask> -a gateway=<gateway>
Validate filesystem structure and data integrity.

11. Start Application and Database
  • Start application and database services on the target LPAR.
  • Verify operational status, data consistency, and application connectivity.

Fallback / Rollback Plan — Logical Migration (AIX LPAR):

Purpose:
To restore operations to the original source LPAR in case of a failure during migration or if post-migration validation on the target LPAR identifies critical issues. This ensures rapid restoration of services with minimal downtime and data loss.

Fallback Procedure:
1. Shutdown Target LPAR
Stop all applications and database services gracefully on the target LPAR.
Shutdown the LPAR:
# shutdown -F
Confirm the LPAR is powered off via HMC or CLI:
# lssyscfg -r lpar -m <managed_system> -F name,state
2. Revert DNS Configuration
Update DNS records to point back to the source LPAR IP address.
Confirm that clients and network services will resolve to the original source.
3. Start Source LPAR
Power on the source LPAR from HMC or CLI:
# chsysstate -r lpar -m <managed_system> -o on -n <source_LPAR>
Verify the system boots successfully and rootvg disks are accessible.
4. Mount Filesystems
Ensure all volume groups and filesystems are mounted:
# mount -a
# df -g
5. Start Database and Applications
  • Start database and application services on the source LPAR.
  • Validate application and database accessibility to confirm full service restoration.
Notes:
  • Keep the target LPAR powered off until the issue is resolved to avoid conflicts with IPs, DNS, or shared storage.
  • Document any errors observed on the target LPAR for post-mortem analysis.
  • After successful rollback, the migration plan can be revisited and corrected before re-attempt.

Logical Migration Using mksysb Backup and rsync – AIX LPAR Migration Procedure

This procedure describes how to perform a logical migration of an existing AIX LPAR to a new target LPAR using a combination of mksysb for restoring the OS and system configuration (rootvg) and rsync for transferring and synchronizing application/data filesystems (datavg).

This approach is suitable when the source and target reside on different storage subsystems or when SAN-level cloning is not possible. It allows the target LPAR to be built from the source system image, while application data is synchronized over the network, ensuring minimal downtime during the final cutover.

The process is fully controlled, reproducible, and allows incremental data synchronization to maintain consistency until the last moment.

Objective:
  • Rebuild the target LPAR using mksysb for the OS and system configuration.
  • Migrate application and database data using rsync, preserving file ownership, permissions, and timestamps.
  • Enable a controlled cutover with minimal downtime.
  • Ensure the target system is functionally equivalent to the source LPAR.
Scope:
  • Migration of AIX operating system (rootvg) and application/data (datavg) from source LPAR to target LPAR.
  • Target system restored from a mksysb image.
  • Application/data synchronized using rsync.
  • Suitable for environments with different storage subsystems or network-based replication.
Pre-Requisites:
  • NIM server configured and reachable by the target LPAR.
  • Target LUNs for rootvg and datavg allocated and mapped to the target LPAR.
  • Network connectivity between source and target LPARs for rsync.
  • Sufficient disk space on the target LPAR for the source data.
  • Downtime window scheduled for final rsync and cutover.
  • Application and database owners available for validation and controlled stop/start.
Step-by-Step Migration Procedure:

1. Create Target LPAR Profile

From HMC, create a new LPAR profile:
Configure CPU, memory, and adapter settings (match source or new specifications).
Add virtual FC adapters for SAN zoning.
Add virtual Ethernet adapters for network access.

2. Provide WWNs to Storage Team
Share target LPAR WWPNs with storage team.
Verify WWPN login:
# chnportlogin -m <manage_system> -o login -p <LPAR>
# lsnportlogin -m <manage_system> --filter "lpar_names=<LPAR>"

3. Storage Team: Allocate and Map LUNs
Allocate LUNs matching source rootvg and datavg sizes.
Map LUNs to target LPAR via virtual FC adapters.

4. Create mksysb Backup on Source LPAR
# mksysb -ieX /backup/$(hostname)_$(date +%Y%m%d)
# lsmksysb -lf /backup/$(hostname)_$(date +%Y%m%d)
Captures full system image including OS, rootvg, and configuration.

5. Transfer mksysb to NIM Server
# scp /backup/<hostname>_<date>.mksysb target_nimserver:/export/mksysb/

6. target NIM Server define NIM Resources
On NIM master, define backup and client:
# nim -o define -t mksysb -a server=master -a location=/export/mksysb/<file> mksysb_<hostname>
# nim -o define -t spot -a source=mksysb_<hostname> spot_<hostname>
# nim -o define -t client -a platform=chrp -a if1="net_<network> <target_ip> <target_hostname>" target_lpar

7. Install Target LPAR Using mksysb
Boot target LPAR via network (SMS menu).
Start NIM installation:
# nim -o bos_inst -a source=mksysb -a spot=spot_<hostname> -a mksysb=mksysb_<hostname> -a accept_licenses=yes target_lpar
Target system will have the same AIX configuration as source.
Reference: Building AIX LPAR using mksysb

8. Post-Installation Configuration
# hostname <new_hostname>
# chdev -l inet0 -a hostname=<new_hostname>
# chdev -l en0 -a netaddr=<new_ip> -a netmask=<mask> -a gateway=<gateway>
Update /etc/hosts and /etc/resolv.conf.
Verify connectivity:
# ping <gateway>

9. Create datavg on Target LPAR
# mkvg -y datavg hdiskX hdiskY
# mklv -t jfs2 -y lvname datavg <size>
# crfs -v jfs2 -d lvname -m /data/app -A yes -p rw
# mount -a
Recreate logical volumes and filesystems to match source layout.

10. rsync Data from Source to Target
Initial sync while source is online:
Login to the target LPAR and run:
# rsync -avz --progress root@source:/data/ /data/
Final sync during cutover window after stopping apps:
# rsync -avz --delete root@source:/data/ /data/
Preserves file ownership, permissions, and timestamps.

11. Application Cutover
Stop database and applications on source LPAR.
Perform final rsync.
Shutdown source LPAR:
# shutdown -F
Update DNS to target LPAR IP.
Start applications and database on target.

12. Post-Migration Validation
# oslevel -s
# lsvg -o
# df -g
# lsdev -Cc disk
# bootlist -om normal
# errpt -a | more
Verify all filesystems are mounted correctly.
Confirm applications and databases start and function as expected.

Fallback / Rollback Plan — Logical Migration (AIX LPAR):

Purpose:
To restore operations to the original source LPAR in case of a failure during migration or if post-migration validation on the target LPAR identifies critical issues. This ensures rapid restoration of services with minimal downtime and data loss.

Fallback Procedure:
1. Shutdown Target LPAR
Stop all applications and database services gracefully on the target LPAR.
Shutdown the LPAR:
# shutdown -F
Confirm the LPAR is powered off via HMC or CLI:
# lssyscfg -r lpar -m <managed_system> -F name,state
2. Revert DNS Configuration
Update DNS records to point back to the source LPAR IP address.
Confirm that clients and network services will resolve to the original source.
3. Start Source LPAR
Power on the source LPAR from HMC or CLI:
# chsysstate -r lpar -m <managed_system> -o on -n <source_LPAR>
Verify the system boots successfully and rootvg disks are accessible.
4. Mount Filesystems
Ensure all volume groups and filesystems are mounted:
# mount -a
# df -g
5. Start Database and Applications
Start database and application services on the source LPAR.
Validate application and database accessibility to confirm full service restoration.
Notes:
  • Keep the target LPAR powered off until the issue is resolved to avoid conflicts with IPs, DNS, or shared storage.
  • Document any errors observed on the target LPAR for post-mortem analysis.
  • After successful rollback, the migration plan can be revisited and corrected before re-attempt.

Logical Migration (mksysb/Savevg) – AIX LPAR Migration Procedure

This document describes the procedure to perform a logical migration of an existing AIX LPAR to a new target LPAR when direct storage mapping or SAN-based cloning is not possible.

In this method, the target LPAR is rebuilt using mksysb (system backup) for the root volume group (rootvg) and savevg for application/data volume groups (datavg). The approach ensures that the target system is functionally equivalent to the source environment, while allowing migration across different storage subsystems.

The migration leverages NIM (Network Installation Manager) to deploy the system image and preserves network and system configurations, ensuring controlled cutover with minimal downtime.

Objective:
  • Rebuild the target LPAR using mksysb and savevg backups from the source LPAR.
  • Restore AIX OS and application/data volumes (datavg) on a different storage subsystem.
  • Enable the target LPAR to be functionally identical to the source system.
  • Ensure a controlled cutover with minimal downtime.
Scope:
  • Migration of AIX OS and application/data from source to target LPAR.
  • Source and target reside on different storage subsystems.
  • Utilizes NIM for network-based system restoration.
  • Final validation and cutover ensure production readiness.
Pre-Requisites:
  • Access to HMC for creating target LPAR profile.
  • NIM master server configured and reachable from both source and target.
  • Network connectivity between source, target, and NIM server.
  • Sufficient storage allocated on the target LPAR for rootvg and datavg.
  • Valid mksysb and savevg backups from the source system.
  • Application and database owners available for stop/start coordination.
Step-by-Step Migration Procedure:

1. Create Target LPAR
From HMC, create a new LPAR profile with:
Same CPU and memory as source (or per requirements).
Virtual FC adapters for SAN connectivity (NPIV if applicable).
Virtual Ethernet adapters for network access.

2. Provide WWNs to Storage Team
Capture and share target LPAR WWPNs with storage team.
Verify WWPN login:
# chnportlogin -m <manage_system> -o login -p <LPAR>
# lsnportlogin -m <manage_system> --filter "lpar_names=<LPAR>"

3. Storage Team: Allocate New LUNs
Allocate LUNs for:
rootvg – OS/system files
datavg – application/data volume group
LUNs must be equal to or larger than source disks.

4. Create mksysb Backup on Source LPAR
# mksysb -ieX /backup/$(hostname)_$(date +%Y%m%d)
# lsmksysb -lf /backup/$(hostname)_$(date +%Y%m%d)
Creates full system backup including OS and rootvg.

5. Transfer mksysb to NIM Server
# scp /backup/<hostname>_<date>.mksysb nimserver:/export/mksysb/

6. Define NIM Resources
On NIM master, define:
# nim -o define -t mksysb -a server=master -a location=/export/mksysb/<file> mksysb_<hostname>
# nim -o define -t spot -a source=mksysb_<hostname> spot_<hostname>
# nim -o define -t lpp_source -a server=master -a location=/export/lpp_source lpp_7300-03-01
# nim -o define -t client -a platform=chrp -a if1="net_<network> <target_ip> <target_hostname>" -a netboot_kernel=mp target_lpar

7. Restore mksysb to Target LPAR
Boot target LPAR via SMS menu (network boot).
Start installation from NIM:
# nim -o bos_inst -a source=mksysb -a spot=spot_<hostname> -a mksysb=mksysb_<hostname> -a lpp_source=lpp_7300-03-01 -a accept_licenses=yes -a preserve_res=yes target_lpar
After completion, the target system has the same AIX OS configuration as the source.
Reference: Building AIX LPAR using mksysb

8. Update Host and Network Configuration
# hostname <new_hostname>
# chdev -l inet0 -a hostname=<new_hostname>
# chdev -l en0 -a netaddr=<new_ip> -a netmask=<mask> -a gateway=<gateway>
Edit /etc/hosts and /etc/resolv.conf as needed.
Verify connectivity:
# ping <gateway>

9. Restore datavg Using savevg Backup
On source LPAR:
# savevg -i -f /backup/datavg_$(date +%Y%m%d).backup datavg
Transfer or NFS-mount backup on target:
# mount nimserver:/export/backup /mnt
# restvg -f /mnt/datavg_<date>.backup hdiskY hdiskZ
Verify restoration:
# lsvg
# lsvg -p datavg
# df -g

10. Application and Database Cutover
Stop database and applications on source LPAR.
Shutdown source cleanly:
# shutdown -F
Update DNS to point to target LPAR IP.
Start applications and database on target.

11. Post-Migration Validation
Verify system and application readiness:
# lsvg -o
# df -g
# errpt -a | more
# oslevel -s
# bootlist -om normal
# lsdev -Cc disk
Ensure all file systems are mounted and applications/databases are running correctly.

Fallback / Rollback Plan — Logical Migration (AIX LPAR):

Purpose:
To restore operations to the original source LPAR in case of a failure during migration or if post-migration validation on the target LPAR identifies critical issues. This ensures rapid restoration of services with minimal downtime and data loss.

Fallback Procedure:
1. Shutdown Target LPAR
Stop all applications and database services gracefully on the target LPAR.
Shutdown the LPAR:
# shutdown -F
Confirm the LPAR is powered off via HMC or CLI:
# lssyscfg -r lpar -m <managed_system> -F name,state
2. Revert DNS Configuration
Update DNS records to point back to the source LPAR IP address.
Confirm that clients and network services will resolve to the original source.
3. Start Source LPAR
Power on the source LPAR from HMC or CLI:
# chsysstate -r lpar -m <managed_system> -o on -n <source_LPAR>
Verify the system boots successfully and rootvg disks are accessible.
4. Mount Filesystems
Ensure all volume groups and filesystems are mounted:
# mount -a
# df -g
5. Start Database and Applications
  • Start database and application services on the source LPAR.
  • Validate application and database accessibility to confirm full service restoration.
Notes:
  • Keep the target LPAR powered off until the issue is resolved to avoid conflicts with IPs, DNS, or shared storage.
  • Document any errors observed on the target LPAR for post-mortem analysis.
  • After successful rollback, the migration plan can be revisited and corrected before re-attempt.