# Increment subnet ID's when building out new environments

**URL:** <https://discuss.hashicorp.com/t/increment-subnet-ids-when-building-out-new-environments/19201>\
**Category:** Terraform\
**Created:** [December 23, 2020, 11:23pm UTC](https://discuss.hashicorp.com/t/increment-subnet-ids-when-building-out-new-environments/19201 "2020-12-23T23:23:51Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![tsposato](https://sea2.discourse-cdn.com/hashicorp/user_avatar/discuss.hashicorp.com/tsposato/32/16060_2.png) [@tsposato](https://discuss.hashicorp.com/u/tsposato)\
**Post date:** [December 23, 2020, 11:23pm UTC](https://discuss.hashicorp.com/t/increment-subnet-ids-when-building-out-new-environments/19201/1 "2020-12-23T23:23:51Z")

</div>

Hi,

I have some current code which builds out an environment within an aws vpc with multiple different subnets for public/private etc. and this all works fine. However if I build a new environment it also assigns the same public/private subnet ranges, which is fine internally however I will be linking up these environments to my office via VPNs for each environment so I want to have separate subnet ranges for each environment terraform builds & manages.

Something like:  
workspace: TEST1

resource “aws\_subnet” “ApplicationSubnet1” {  
cidr\_block = “10.0.0.0/24”  
vpc\_id = aws\_vpc.application.id  
availability\_zone = “${var.aws-region}a”  
}

workspace: TEST2

resource “aws\_subnet” “ApplicationSubnet1” {  
cidr\_block = “10.0.1.0/24”  
vpc\_id = aws\_vpc.application.id  
availability\_zone = “${var.aws-region}a”  
}

I’m not sure the best way to do this? Do I store a config entry in S3 that I read and then increment subnet based on that? Is there any examples of doing this? Is it even supported?

Thanks,  
Terry

---

<div class="post-metadata">

**Author:** ![stuart-c](https://sea2.discourse-cdn.com/hashicorp/user_avatar/discuss.hashicorp.com/stuart-c/32/10789_2.png) [@stuart-c](https://discuss.hashicorp.com/u/stuart-c)\
**Post date:** [December 24, 2020, 9:20am UTC](https://discuss.hashicorp.com/t/increment-subnet-ids-when-building-out-new-environments/19201/2 "2020-12-24T09:20:19Z")

</div>

It is totally up to you where you want to store the IP address information. If you have an existing IPAM system you could try to get the information from there (either HTTP calls or extracted by a wrapper script).

We don’t, so we store the information in .tfvar files which we then choose using -var-file depending on the environment.

---

<div class="post-metadata">

**Author:** ![tsposato](https://sea2.discourse-cdn.com/hashicorp/user_avatar/discuss.hashicorp.com/tsposato/32/16060_2.png) [@tsposato](https://discuss.hashicorp.com/u/tsposato)\
**Post date:** [December 24, 2020, 9:37am UTC](https://discuss.hashicorp.com/t/increment-subnet-ids-when-building-out-new-environments/19201/3 "2020-12-24T09:37:07Z")

</div>

Ahh interesting, that might be the way for me too. I’ve never used external .tfvar files before so I guess I will need to read up on it.

---

<div class="post-metadata">

**Author:** ![apparentlymart](https://sea2.discourse-cdn.com/hashicorp/user_avatar/discuss.hashicorp.com/apparentlymart/32/702_2.png) [@apparentlymart](https://discuss.hashicorp.com/u/apparentlymart)\
**Post date:** [January 5, 2021, 11:52pm UTC](https://discuss.hashicorp.com/t/increment-subnet-ids-when-building-out-new-environments/19201/4 "2021-01-05T23:52:56Z")

</div>

As @stuart-c noted, there are lots of ways to get this done depending on how you’d like your system to be structured.

To add another idea to the pile, at a previous job we had a shared module called `ip-ranges` whose entire purpose was to represent our global network addressing scheme, and then we called that module from each configuration that would declare network objects and retrieved the appropriate CIDR prefixes out of that data structure1.

In that module we had some lookup tables to systematically assign different numbers to different discriminators that we were allocating the addresses to. For example, we first had a lookup table giving a number per cloud vendor, and then one for a number per region for each vendor, and finally one for availability zones. We then combined all that together with `cidrsubnet` to turn the individual numbers into full address prefixes.

I no longer have access to the source code for that, and it was written for Terraform 0.6 anyway so I expect I wrote it pretty differently to how I’d write it today, but here’s a little snippet showing the general idea in case you’d like to extrapolate it into a full solution. For simplicity I’m going to leave out the “vendor” table and just focus on the two-level heirarchy of region and AZ, because I think that’ll be enough to show what I mean:

```auto
locals {
  # If we allocate two addressing bits to regions and
  # three addressing bits to zones then we can have
  # up to four regions and up to eight zones, with
  # each zone then requiring five subnet addressing
  # bits in total.
  regions = {
    us-west-2 = 0
    us-east-1 = 1
    eu-west-1 = 2
    # number 3 available for future expansion
  }
  zones = {
    a = 0
    b = 1
    c = 2
    d = 3
    e = 4
    f = 5
    g = 6
    h = 7
  }

  base_cidr_block = "10.0.0.0/12"
  region_blocks = {
    for name, num in local.regions : name => {
      cidr_block = cidrsubnet(local.base_cidr_block, 2, num)
    }
  }
  zone_blocks = {
    for name, region_num in local.regions : name => {
      for letter, num in local.zones : "${name}${letter}" => {
        cidr_block = cidrsubnet(local.region_blocks[name].cidr_block, 3, num)
      }
    }
  }
}

output "networks" {
  value = {
    aws = {
      cidr_block = local.base_cidr_block
      regions = local.region_blocks
      zones = local.zone_blocks
    }
  }
}

```

The idea here is that the output of this module doesn’t define which networks to create but rather provides the answer to a question like "If I’m creating a network for `us-west-2a`, what should its `cidr_block` be?

```auto
module "ip_ranges" {
  source = "../modules/ip-ranges"
}

resource “aws_subnet” “application_subnet_1” {
  cidr_block = module.ip_ranges.aws.zones["${var.aws-region}a"]
  vpc_id = aws_vpc.application.id
  availability_zone = "${var.aws-region}a"
}

```

Notice how it just ignores the rest of the data structure, because those address assignments are for some other resource to use, possibly in an entirely different configuration. Other configurations might end up referring to the `cidr_block` of this subnet too, such as if they’re configuring cross-VPC routing tables, and so having the full address tree available to all callers can make that sort of thing easier to represent. IP addressing scheme is a cross-cutting concern, after all.

Region and availability zone might not be the only discriminators that make sense in your particular addressing scheme – you might want to assign another addressing bit to distinguish private vs. public subnets, for example – but the general idea here is to assign some addressing bits to each of the items that will contribute to your addressing hierarchy and then centralize that addressing scheme in a shared module so that everything in your infrastructure will agree on what the addresses for each network ought to be.

This is certainly not the only way to do it. It might be overkill for simple systems, and equally might not be flexible _enough_ for more complex systems that have many components that all have their own networks, but I’m just sharing it in case it’s useful inspiration.

* * *

1 (An interesting historical note is that the module I’m describing is what prompted me to contribute the [`cidrsubnet`](https://www.terraform.io/docs/configuration/functions/cidrsubnet.html) and [`cidrhost`](https://www.terraform.io/docs/configuration/functions/cidrhost.html) to Terraform, before I was a HashiCorp employee working on Terraform full-time.)
