Advanced Terraform: Modules and State Management
· Pavan Y N · terraform, advanced, modules, state, enterprise
Published: October 30, 2025
Reading Time: 22 minutes
Tags: Terraform, Advanced, Modules, State, Enterprise
# Introduction
Advanced Terraform techniques enable teams to build scalable, maintainable infrastructure as code. This guide covers reusable modules, advanced state management, testing strategies, and enterprise patterns for Terraform at scale.
# Reusable Modules
## Module Structure Best Practices
terraform-modules/
├── vpc/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ ├── versions.tf
│ ├── README.md
│ └── examples/
│ └── complete/
│ ├── main.tf
│ └── variables.tf
├── eks/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── README.md
└── rds/
├── main.tf
├── variables.tf
├── outputs.tf
└── README.md
## Advanced VPC Module
modules/vpc/main.tf:
locals {
azs = slice(data.aws_availability_zones.available.names, 0, var.az_count)
public_subnet_cidrs = [
for i in range(var.az_count) :
cidrsubnet(var.vpc_cidr, 8, i)
]
private_subnet_cidrs = [
for i in range(var.az_count) :
cidrsubnet(var.vpc_cidr, 8, i + 10)
]
database_subnet_cidrs = [
for i in range(var.az_count) :
cidrsubnet(var.vpc_cidr, 8, i + 20)
]
}
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
tags = merge(
var.tags,
{
Name = var.name
}
)
}
resource "aws_subnet" "public" {
count = var.az_count
vpc_id = aws_vpc.this.id
cidr_block = local.public_subnet_cidrs[count.index]
availability_zone = local.azs[count.index]
map_public_ip_on_launch = true
tags = merge(
var.tags,
{
Name = "${var.name}-public-${local.azs[count.index]}"
Type = "public"
"kubernetes.io/role/elb" = "1"
}
)
}
resource "aws_subnet" "private" {
count = var.az_count
vpc_id = aws_vpc.this.id
cidr_block = local.private_subnet_cidrs[count.index]
availability_zone = local.azs[count.index]
tags = merge(
var.tags,
{
Name = "${var.name}-private-${local.azs[count.index]}"
Type = "private"
"kubernetes.io/role/internal-elb" = "1"
}
)
}
resource "aws_subnet" "database" {
count = var.create_database_subnets ? var.az_count : 0
vpc_id = aws_vpc.this.id
cidr_block = local.database_subnet_cidrs[count.index]
availability_zone = local.azs[count.index]
tags = merge(
var.tags,
{
Name = "${var.name}-database-${local.azs[count.index]}"
Type = "database"
}
)
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = merge(
var.tags,
{
Name = "${var.name}-igw"
}
)
}
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : var.az_count) : 0
domain = "vpc"
tags = merge(
var.tags,
{
Name = "${var.name}-nat-eip-${count.index + 1}"
}
)
depends_on = [aws_internet_gateway.this]
}
resource "aws_nat_gateway" "this" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : var.az_count) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(
var.tags,
{
Name = "${var.name}-nat-${count.index + 1}"
}
)
depends_on = [aws_internet_gateway.this]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
tags = merge(
var.tags,
{
Name = "${var.name}-public-rt"
}
)
}
resource "aws_route_table" "private" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : var.az_count) : 0
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.this[count.index].id
}
tags = merge(
var.tags,
{
Name = "${var.name}-private-rt-${count.index + 1}"
}
)
}
resource "aws_route_table_association" "public" {
count = var.az_count
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = var.az_count
subnet_id = aws_subnet.private[count.index].id
route_table_id = var.single_nat_gateway ? aws_route_table.private[0].id : aws_route_table.private[count.index].id
}
# VPC Flow Logs
resource "aws_flow_log" "this" {
count = var.enable_flow_logs ? 1 : 0
iam_role_arn = aws_iam_role.flow_logs[0].arn
log_destination = aws_cloudwatch_log_group.flow_logs[0].arn
traffic_type = "ALL"
vpc_id = aws_vpc.this.id
max_aggregation_interval = 60
tags = merge(
var.tags,
{
Name = "${var.name}-flow-logs"
}
)
}
resource "aws_cloudwatch_log_group" "flow_logs" {
count = var.enable_flow_logs ? 1 : 0
name = "/aws/vpc/${var.name}"
retention_in_days = var.flow_logs_retention_days
tags = var.tags
}
modules/vpc/variables.tf:
variable "name" {
description = "Name to be used on all resources"
type = string
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "Must be a valid IPv4 CIDR block."
}
}
variable "az_count" {
description = "Number of availability zones"
type = number
default = 3
validation {
condition = var.az_count >= 2 && var.az_count <= 6
error_message = "AZ count must be between 2 and 6."
}
}
variable "enable_nat_gateway" {
description = "Enable NAT Gateway"
type = bool
default = true
}
variable "single_nat_gateway" {
description = "Use single NAT Gateway for all AZs"
type = bool
default = false
}
variable "create_database_subnets" {
description = "Create database subnets"
type = bool
default = false
}
variable "enable_dns_hostnames" {
description = "Enable DNS hostnames"
type = bool
default = true
}
variable "enable_dns_support" {
description = "Enable DNS support"
type = bool
default = true
}
variable "enable_flow_logs" {
description = "Enable VPC Flow Logs"
type = bool
default = false
}
variable "flow_logs_retention_days" {
description = "Flow logs retention in days"
type = number
default = 7
}
variable "tags" {
description = "Tags to apply to resources"
type = map(string)
default = {}
}
modules/vpc/outputs.tf:
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.this.id
}
output "vpc_cidr" {
description = "CIDR block of the VPC"
value = aws_vpc.this.cidr_block
}
output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs of private subnets"
value = aws_subnet.private[*].id
}
output "database_subnet_ids" {
description = "IDs of database subnets"
value = aws_subnet.database[*].id
}
output "nat_gateway_ids" {
description = "IDs of NAT Gateways"
value = aws_nat_gateway.this[*].id
}
output "internet_gateway_id" {
description = "ID of Internet Gateway"
value = aws_internet_gateway.this.id
}
## Using the Module
module "vpc" {
source = "./modules/vpc"
name = "production-vpc"
vpc_cidr = "10.0.0.0/16"
az_count = 3
enable_nat_gateway = true
single_nat_gateway = false
create_database_subnets = true
enable_flow_logs = true
tags = {
Environment = "production"
ManagedBy = "Terraform"
Project = "MyApp"
}
}
# Use module outputs
resource "aws_security_group" "app" {
vpc_id = module.vpc.vpc_id
# ... configuration
}
# Advanced State Management
## Remote State with S3 and DynamoDB
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
kms_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
# Workspace-specific state
workspace_key_prefix = "workspaces"
}
}
## State Locking and Consistency
# Create backend resources
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state"
lifecycle {
prevent_destroy = true
}
tags = {
Name = "Terraform State"
Environment = "Production"
}
}
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
kms_master_key_id = aws_kms_key.terraform_state.arn
}
bucket_key_enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_lifecycle_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
id = "delete-old-versions"
status = "Enabled"
noncurrent_version_expiration {
noncurrent_days = 90
}
}
}
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
point_in_time_recovery {
enabled = true
}
server_side_encryption {
enabled = true
kms_key_arn = aws_kms_key.terraform_state.arn
}
tags = {
Name = "Terraform State Locks"
Environment = "Production"
}
}
resource "aws_kms_key" "terraform_state" {
description = "KMS key for Terraform state encryption"
deletion_window_in_days = 10
enable_key_rotation = true
tags = {
Name = "Terraform State Encryption"
}
}
# Testing Terraform Code
## Terratest Example
// test/vpc_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVPCModule(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"name": "test-vpc",
"vpc_cidr": "10.0.0.0/16",
"az_count": 2,
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
// Test outputs
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcID)
publicSubnetIDs := terraform.OutputList(t, terraformOptions, "public_subnet_ids")
assert.Equal(t, 2, len(publicSubnetIDs))
privateSubnetIDs := terraform.OutputList(t, terraformOptions, "private_subnet_ids")
assert.Equal(t, 2, len(privateSubnetIDs))
}
# STAR Approach: Enterprise Terraform
Situation: Company with 100+ AWS accounts, inconsistent infrastructure, and manual provisioning taking weeks.
Task: Implement enterprise-scale Terraform with reusable modules, automated testing, and self-service provisioning.
Action:
- •
Module Library Development:
- →Created 30+ reusable modules
- →Implemented comprehensive testing
- →Published to private registry
- →Versioned all modules
- •
State Management:
- →Centralized state in S3 with encryption
- →Implemented state locking
- →Separate state per environment
- →Automated state backups
- •
CI/CD Integration:
- →Automated terraform plan on PRs
- →Implemented policy-as-code with Sentinel
- →Added security scanning
- →Automated apply on merge
- •
Self-Service Platform:
- →Built web portal for infrastructure requests
- →Automated account provisioning
- →Implemented approval workflows
- →Created comprehensive documentation
- •
Governance:
- →Implemented tagging standards
- →Cost allocation by team
- →Compliance scanning
- →Regular audits
Result:
- →Provisioning time reduced from weeks to hours
- →Infrastructure consistency improved to 100%
- →Cost visibility increased by 90%
- →Self-service enabled for 500+ developers
- →Compliance violations reduced by 95%
- →Infrastructure costs optimized by 40%
# Best Practices
## 1. Module Design
- →Keep modules focused and reusable
- →Use semantic versioning
- →Provide comprehensive examples
- →Document all variables and outputs
## 2. State Management
- →Always use remote state
- →Enable state locking
- →Encrypt state files
- →Regular backups
## 3. Security
- →Scan for security issues
- →Use least privilege
- →Encrypt sensitive data
- →Regular audits
## 4. Testing
- →Unit test modules
- →Integration testing
- →Policy validation
- →Regular testing
## 5. Documentation
- →Comprehensive README files
- →Example usage
- →Variable descriptions
- →Architecture diagrams
# Conclusion
Advanced Terraform patterns enable enterprise-scale infrastructure management with consistency, security, and efficiency. By leveraging reusable modules, robust state management, and comprehensive testing, teams can build maintainable infrastructure as code that scales with organizational growth.
# Additional Resources
Ready to scale Terraform? Start with reusable modules and gradually implement enterprise patterns!