Skip to content

Instantly share code, notes, and snippets.

@sqlxpert
Last active March 6, 2026 02:07
Show Gist options
  • Select an option

  • Save sqlxpert/2b1dc6724f081bdccef268cbae6283cb to your computer and use it in GitHub Desktop.

Select an option

Save sqlxpert/2b1dc6724f081bdccef268cbae6283cb to your computer and use it in GitHub Desktop.
Allocate VPC subnet private IP address ranges from an IPAM resource planning pool (new in Terraform AWS Provider v6.29)
# AWS IPAM resource planning pool for VPC subnets, in Terraform
# Copyright Paul Marcelin. Licensed under GPLv3.
# https://gist.github.com/sqlxpert/2b1dc6724f081bdccef268cbae6283cb
#
# Related: https://docs.aws.amazon.com/vpc/latest/ipam/tutorials-subnet-planning.html
#
# Try it in CloudShell!
# https://docs.aws.amazon.com/cloudshell/latest/userguide/welcome.html
#
#
# sudo dnf install --assumeyes 'dnf-command(config-manager)'
# sudo dnf config-manager --add-repo 'https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo'
# sudo dnf install --assumeyes terraform
#
# terraform init
# terraform apply -target='aws_vpc_ipam_pool_cidr_allocation.sample_vpc_subnets'
# terraform apply
#
# # terraform apply -destroy # Takes loooong!
#
#
# Pre-allocate to work around an error in cloudposse/dynamic-subnets/aws
# count = local.need_vpc_data ? 1 : 0
# The "count" value depends on resource attributes that cannot be determined
terraform {
backend "local" {}
required_version = ">= 1.10.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 6.29.0"
# https://github.com/hashicorp/terraform-provider-aws/issues/34615
# https://github.com/hashicorp/terraform-provider-aws/pull/44705
}
}
}
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}
resource "aws_vpc_ipam" "sample_vpc" {
description = "Sample VPC"
operating_regions {
region_name = data.aws_region.current.region
}
}
resource "aws_vpc_ipam_pool" "sample_vpc" {
description = "Sample VPC private addresses"
locale = data.aws_region.current.region
ipam_scope_id = aws_vpc_ipam.sample_vpc.private_default_scope_id
address_family = "ipv4"
allocation_default_netmask_length = 21
auto_import = false
}
resource "aws_vpc_ipam_pool_cidr" "sample_vpc" {
ipam_pool_id = aws_vpc_ipam_pool.sample_vpc.id
lifecycle {
ignore_changes = [
cidr,
]
}
cidr = "10.11.0.0/21"
}
module "sample_vpc" {
source = "cloudposse/vpc/aws"
version = "3.0.0"
name = "Sample"
depends_on = [
aws_vpc_ipam_pool_cidr.sample_vpc,
]
ipv4_primary_cidr_block_association = {
ipv4_ipam_pool_id = aws_vpc_ipam_pool.sample_vpc.id
ipv4_netmask_length = aws_vpc_ipam_pool.sample_vpc.allocation_default_netmask_length
}
assign_generated_ipv6_cidr_block = false
default_security_group_deny_all = true
}
resource "aws_vpc_ipam_pool" "sample_vpc_subnets" {
description = "Sample VPC subnet private addresses"
locale = data.aws_region.current.region
source_ipam_pool_id = aws_vpc_ipam_pool.sample_vpc.id
ipam_scope_id = aws_vpc_ipam.sample_vpc.private_default_scope_id
address_family = "ipv4"
allocation_default_netmask_length = 24
auto_import = false
source_resource {
resource_type = "vpc"
resource_region = data.aws_region.current.region
resource_owner = provider::aws::arn_parse(data.aws_caller_identity.current.arn)["account_id"]
resource_id = module.sample_vpc.vpc_id
}
}
resource "aws_vpc_ipam_pool_cidr" "sample_vpc_subnets" {
ipam_pool_id = aws_vpc_ipam_pool.sample_vpc_subnets.id
lifecycle {
ignore_changes = [
cidr,
]
}
cidr = module.sample_vpc.vpc_cidr_block
}
locals {
subnet_scope_to_keys = {
for subnet_scope in ["private", "public"] :
subnet_scope => [
for subnet_index in range(3) :
join(":", [subnet_scope, subnet_index])
]
}
subnet_keys_set = toset(flatten(values(local.subnet_scope_to_keys)))
}
resource "aws_vpc_ipam_pool_cidr_allocation" "sample_vpc_subnets" {
for_each = local.subnet_keys_set
description = "Sample VPC ${each.key} subnet private addresses"
depends_on = [
aws_vpc_ipam_pool_cidr.sample_vpc_subnets,
]
lifecycle {
ignore_changes = [
description,
cidr,
]
}
ipam_pool_id = aws_vpc_ipam_pool.sample_vpc_subnets.id
}
module "sample_vpc_subnets" {
source = "cloudposse/dynamic-subnets/aws"
version = "3.1.1"
name = "Sample"
vpc_id = module.sample_vpc.vpc_id
igw_id = [module.sample_vpc.igw_id]
ipv4_cidrs = [{
for subnet_scope, subnet_keys in local.subnet_scope_to_keys :
subnet_scope => sort([
for subnet_key in subnet_keys :
aws_vpc_ipam_pool_cidr_allocation.sample_vpc_subnets[subnet_key].cidr
])
}]
# Maximum subnets per type (public or private), not overall!
max_subnet_count = 3
private_subnets_per_az_count = 1
public_subnets_per_az_count = 1
public_route_table_enabled = true
public_route_table_per_subnet_enabled = false
private_route_table_enabled = false
nat_gateway_enabled = false
max_nats = 0
private_open_network_acl_enabled = false
}
@sqlxpert
Copy link
Author

sqlxpert commented Mar 6, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment