Terraform Best Practices: Using cidrsubnet and Loops for Scalable Subnet Management


Managing scalable and maintainable subnets is a cornerstone of efficient infrastructure as code (IaC) with Terraform. Rather than hardcoding subnet CIDRs manually, using cidrsubnet in conjunction with Terraform loops (like for_each or count) enables you to create subnet blocks dynamically and programmatically. This approach reduces error, enhances clarity, and promotes reusability.

This post will explore using cidrsubnet and loops in Terraform to create scalable, clean, and DRY (Don't Repeat Yourself) subnet configurations for your AWS VPCs.


Why Not Hardcode Subnets?

Hardcoding subnets:

  • Leads to cluttered and redundant code.

  • Makes updates harder and error-prone.

  • Doesn’t scale well for large environments.

Dynamic CIDR generation avoids manual calculations and supports large and automated deployments.


Understanding cidrsubnet

Terraform’s cidrsubnet function allows you to create subnet CIDRs from a base CIDR block by incrementing subnet bits. Syntax:


cidrsubnet(prefix, newbits, netnum)


  • prefix: Base network CIDR (e.g., 10.0.0.0/16)

  • newbits: Number of additional bits to extend the mask (e.g., 8 to go from /16 to /24)

  • netnum: Index of the subnet

Example:


cidrsubnet("10.0.0.0/16", 8, 1) # Output: 10.0.1.0/24



Using Loops for Multiple Subnets

Example with for_each


variable "subnet_names" {

  default = ["public-a", "public-b", "public-c"]

}


resource "aws_subnet" "public" {

  for_each = toset(var.subnet_names)


  vpc_id     = aws_vpc.main.id

  cidr_block = cidrsubnet("10.0.0.0/16", 8, index(var.subnet_names, each.key))

  availability_zone = each.key == "public-a" ? "us-east-1a" :

                      each.key == "public-b" ? "us-east-1b" : "us-east-1c"

}


This snippet creates three subnets (10.0.0.0/24, 10.0.1.0/24, 10.0.2.0/24) across different availability zones.


More Advanced: Using count and Maps


variable "azs" {

  default = ["us-east-1a", "us-east-1b", "us-east-1c"]

}


resource "aws_subnet" "private" {

  count = length(var.azs)


  vpc_id            = aws_vpc.main.id

  cidr_block        = cidrsubnet("10.1.0.0/16", 8, count.index)

  availability_zone = var.azs[count.index]

}


This technique scales beautifully in multi-AZ environments and abstracts subnet creation into reusable modules.


Putting It All Together in a Module

Create a reusable subnets module:

modules/subnets/main.tf


resource "aws_subnet" "this" {

  for_each = var.subnet_map


  vpc_id            = var.vpc_id

  cidr_block        = cidrsubnet(var.base_cidr, var.newbits, each.value.netnum)

  availability_zone = each.value.az

}


Root module:


module "subnets" {

  source     = "./modules/subnets"

  vpc_id     = aws_vpc.main.id

  base_cidr  = "10.0.0.0/16"

  newbits    = 8

  subnet_map = {

    public-a = { az = "us-east-1a", netnum = 0 }

    public-b = { az = "us-east-1b", netnum = 1 }

    public-c = { az = "us-east-1c", netnum = 2 }

  }

}



Best Practices Summary

  • Use cidrsubnet to avoid manual CIDR calculation.

  • Avoid hardcoded subnet blocks – use loops (for_each or count) with indices.

  • Build reusable modules for subnet logic.

  • Tag your resources and use variables for flexibility.

  • Use maps or objects to handle AZ and subnet relationships cleanly.


Conclusion

Using cidrsubnet with Terraform loops enables scalable and manageable subnet provisioning, reducing human error and improving modularity. As your infrastructure grows, these best practices will keep your codebase clean, dynamic, and easy to maintain.

Comments

Popular posts from this blog

Podcast - How to Obfuscate Code and Protect Your Intellectual Property (IP) Across PHP, JavaScript, Node.js, React, Java, .NET, Android, and iOS Apps

AWS Console Not Loading? Here’s How to Fix It Fast

YouTube Channel

Follow us on X