Terraform for Cloud Networking: Complete Infrastructure Guide

Infrastructure as Code transforms network management from click-ops to repeatable, version-controlled, and reviewable configurations. This guide covers Terraform patterns for cloud networking across AWS, Azure, and GCP.

Why Terraform for Networking?

Network infrastructure benefits enormously from IaC:

Terraform Networking Fundamentals

Resource Dependencies

Network resources have complex dependencies. Terraform handles this, but understanding helps:

# Dependency chain
VPC → Subnets → Route Tables → Routes
VPC → Internet Gateway → NAT Gateway (public subnet)
VPC → Security Groups → References to other SGs
Subnets → Load Balancers → Target Groups

State Management

Networking state requires special attention:

AWS Networking with Terraform

VPC Module Structure

# modules/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "${var.environment}-vpc"
    Environment = var.environment
  }
}

resource "aws_subnet" "public" {
  count                   = length(var.availability_zones)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone       = var.availability_zones[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.environment}-public-${var.availability_zones[count.index]}"
    Type = "public"
  }
}

resource "aws_subnet" "private" {
  count             = length(var.availability_zones)
  vpc_id            = aws_vpc.main.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name = "${var.environment}-private-${var.availability_zones[count.index]}"
    Type = "private"
  }
}

Internet and NAT Gateways

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
}

resource "aws_eip" "nat" {
  count  = var.single_nat ? 1 : length(var.availability_zones)
  domain = "vpc"
}

resource "aws_nat_gateway" "main" {
  count         = var.single_nat ? 1 : length(var.availability_zones)
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id

  depends_on = [aws_internet_gateway.main]
}

Route Tables

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
}

resource "aws_route_table" "private" {
  count  = var.single_nat ? 1 : length(var.availability_zones)
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main[var.single_nat ? 0 : count.index].id
  }
}

Security Groups Best Practices

Modular Security Groups

# Security group with dynamic rules
resource "aws_security_group" "web" {
  name_prefix = "${var.environment}-web-"
  vpc_id      = aws_vpc.main.id

  dynamic "ingress" {
    for_each = var.web_ingress_rules
    content {
      from_port       = ingress.value.from_port
      to_port         = ingress.value.to_port
      protocol        = ingress.value.protocol
      cidr_blocks     = ingress.value.cidr_blocks
      security_groups = ingress.value.security_groups
    }
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  lifecycle {
    create_before_destroy = true
  }
}

# Reference other security groups
resource "aws_security_group" "app" {
  name_prefix = "${var.environment}-app-"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.web.id]
  }
}

Load Balancer Configuration

resource "aws_lb" "main" {
  name               = "${var.environment}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = aws_subnet.public[*].id

  enable_deletion_protection = var.environment == "production"
}

resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.main.arn
  port              = 443
  protocol          = "HTTPS"
  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
  certificate_arn   = var.certificate_arn

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app.arn
  }
}

resource "aws_lb_target_group" "app" {
  name        = "${var.environment}-app-tg"
  port        = 8080
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "ip"

  health_check {
    path                = "/health"
    healthy_threshold   = 2
    unhealthy_threshold = 3
    interval            = 30
  }
}

Multi-Cloud Patterns

Provider Configuration

# providers.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    google = {
      source  = "hashicorp/google"
      version = "~> 5.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

provider "google" {
  project = var.gcp_project
  region  = var.gcp_region
}

provider "azurerm" {
  features {}
  subscription_id = var.azure_subscription_id
}

Abstraction Layer

Create abstractions for multi-cloud consistency:

# modules/network/main.tf
# Uses count or for_each to create provider-specific resources

locals {
  is_aws   = var.provider == "aws"
  is_gcp   = var.provider == "gcp"
  is_azure = var.provider == "azure"
}

module "aws_vpc" {
  source = "./aws"
  count  = local.is_aws ? 1 : 0
  # ...
}

module "gcp_vpc" {
  source = "./gcp"
  count  = local.is_gcp ? 1 : 0
  # ...
}

Terraform Patterns for Networking

CIDR Calculation

# Use cidrsubnet for consistent subnet allocation
locals {
  vpc_cidr = "10.0.0.0/16"
  
  # Public: 10.0.0.0/24, 10.0.1.0/24, 10.0.2.0/24
  public_subnets = [for i in range(3) : cidrsubnet(local.vpc_cidr, 8, i)]
  
  # Private: 10.0.10.0/24, 10.0.11.0/24, 10.0.12.0/24
  private_subnets = [for i in range(3) : cidrsubnet(local.vpc_cidr, 8, i + 10)]
  
  # Database: 10.0.20.0/24, 10.0.21.0/24, 10.0.22.0/24
  database_subnets = [for i in range(3) : cidrsubnet(local.vpc_cidr, 8, i + 20)]
}

Environment-Specific Configuration

# Use locals to define environment differences
locals {
  env_config = {
    development = {
      single_nat     = true
      instance_type  = "t3.small"
      multi_az       = false
    }
    staging = {
      single_nat     = true
      instance_type  = "t3.medium"
      multi_az       = true
    }
    production = {
      single_nat     = false
      instance_type  = "t3.large"
      multi_az       = true
    }
  }
  
  config = local.env_config[var.environment]
}

Common Pitfalls

Testing Network Infrastructure

# Use Terratest for infrastructure testing
func TestVpcCreation(t *testing.T) {
    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../examples/vpc",
    })
    
    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)
    
    vpcId := terraform.Output(t, terraformOptions, "vpc_id")
    assert.NotEmpty(t, vpcId)
}

Key Takeaways

Need Help with Network Infrastructure as Code?

We implement Terraform-based networking solutions. Contact us for a consultation.