Terraform AWS VPC IPAM Tutorial
Create an AWS VPC IPAM pool with Terraform, provision address space, and allocate a VPC CIDR from the pool instead of hard coding each VPC range.

On this page
Hard coding a different VPC range into each Terraform workspace makes it easy for two accounts or regions to claim overlapping address space. AWS VPC IP Address Manager can own a pool of ranges and allocate a CIDR when Terraform creates a VPC.
This tutorial creates a regional AWS IPAM, provisions a private /16 pool, allocates a /20 VPC from it, and derives two smaller subnets from the assigned VPC range. The example uses one AWS account and one region so you can understand the allocation flow before sharing pools across an organization.
Check prerequisites and configure Terraform
Choose one AWS region and confirm that the account can create VPC IPAM resources and VPCs. The identity used by Terraform needs permission to create and inspect IPAMs, scopes, pools, pool CIDRs, VPCs, and subnets. Enable billing and avoid using production address ranges in this tutorial.
Create a directory named aws-ipam-lab and save the current AWS provider selection in .terraform.lock.hcl after initializing it. The example uses the AWS provider 6.x resource model. Pin the provider range your team has tested and commit the lock file.
Create main.tf with the provider and a region input. The IPAM configuration also needs to know which regions it may manage.
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
variable "region" {
type = string
default = "us-east-1"
}
provider "aws" {
region = var.region
}
Authenticate with an AWS profile or a role. Do not put access keys in Terraform files or variable values committed to version control.
Create an IPAM and regional pool
Add an IPAM that operates in the selected region, then create a pool in its private default scope. Configure an allocation length so a VPC can request a consistent /20 block.
resource "aws_vpc_ipam" "main" {
operating_regions {
region_name = var.region
}
}
resource "aws_vpc_ipam_pool" "regional" {
address_family = "ipv4"
ipam_scope_id = aws_vpc_ipam.main.private_default_scope_id
locale = var.region
allocation_default_netmask_length = 20
allocation_min_netmask_length = 20
allocation_max_netmask_length = 20
}
The locale determines where resources can use the pool. A regional pool is a useful boundary for this one region example. Larger designs often use a hierarchy with an organization level pool, regional child pools, and account specific allocations.
The scope separates private and public address management, while the pool defines which ranges can be allocated and which resource types may use them. In a production hierarchy, keep the parent pool under the network team’s control and delegate only child pools to workload accounts. That boundary prevents each application team from provisioning arbitrary parent ranges or changing the address policy used by other regions.
Allocation lengths are a policy choice. This example forces every VPC to request a /20, which makes capacity planning simple but may waste space for small environments. If development and production need different sizes, set the permitted minimum and maximum lengths deliberately and require each workload to request the intended prefix. A permissive pool can still create unwanted allocation sizes even when every Terraform configuration is syntactically valid.
Provision address space into the pool
An empty pool cannot allocate a VPC range. Add a CIDR resource to provision a parent block that your organization has reserved for this AWS scope:
resource "aws_vpc_ipam_pool_cidr" "regional" {
ipam_pool_id = aws_vpc_ipam_pool.regional.id
cidr = "10.60.0.0/16"
}
Replace 10.60.0.0/16 with a range approved for this network. Make sure it does not overlap with other clouds, on premises routes, VPNs, or partner connections. Provisioning adds space to the pool. It does not create a VPC.
Treat the provisioned CIDR as a reservation with an owner and a purpose. Do not copy a corporate supernet into several independent regional pools unless the pool hierarchy and assignment rules prevent duplicate use. If the organization already has a central address authority, decide whether AWS IPAM owns allocation or merely mirrors those approvals. Two systems must not both believe they can allocate the same range.
AWS can take time to move a newly added pool CIDR into the provisioned state. Wait for that transition before creating a VPC. A failed apply during this period does not necessarily mean that the pool definition is wrong. Check the pool CIDR lifecycle status, the pool locale, the VPC region, and the requested netmask length before retrying.
Allocate a VPC from the pool
Create a VPC that asks the IPAM pool for a /20 allocation. AWS chooses an available range from the provisioned pool and records the allocation against the VPC.
resource "aws_vpc" "main" {
ipv4_ipam_pool_id = aws_vpc_ipam_pool.regional.id
ipv4_netmask_length = 20
enable_dns_support = true
enable_dns_hostnames = true
depends_on = [aws_vpc_ipam_pool_cidr.regional]
tags = {
Name = "ipam-lab"
Environment = "sandbox"
}
}
output "allocated_vpc_cidr" {
value = aws_vpc.main.cidr_block
}
Because the VPC references the pool resource, Terraform creates the IPAM and pool first. The VPC CIDR is assigned by AWS during apply, so Terraform can use the resulting aws_vpc.main.cidr_block in dependent subnet resources.
The explicit dependency on the provisioned CIDR ensures Terraform waits until that range is part of the pool. It also gives destroy ordering a clear dependency: the VPC is removed before the pool CIDR that supplied its allocation. Without the dependency, the pool and VPC can be created in parallel after the pool exists, which creates a race on a newly provisioned pool.
An IPAM allocation is not an AWS subnet. The VPC receives one prefix from the pool, and you still need to divide that prefix into subnets that match your availability zones and application tiers. Keep the VPC allocation large enough for those subnet ranges and for future expansion. Changing the requested VPC prefix later can require replacing or migrating the network, so choose it before workloads depend on the VPC.
Add subnets inside the allocated VPC
Use cidrsubnet to divide the allocated /20 into smaller ranges. A /20 parent can contain sixteen /24 blocks:
resource "aws_subnet" "private" {
for_each = {
app_a = 0
app_b = 1
}
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, each.value)
availability_zone = "${var.region}${each.key == "app_a" ? "a" : "b"}"
tags = {
Name = each.key
}
}
For production, pass real availability zone identifiers as explicit inputs rather than assuming that letter suffixes map identically across accounts. Add route tables, NAT or egress paths, and security controls based on the application design.
Initialize and review the allocation
Run Terraform from the project directory and inspect the plan before creating resources:
terraform init
terraform fmt -recursive
terraform validate
terraform plan
The plan should show the IPAM, pool, provisioned pool CIDR, VPC, and subnets. AWS may take several minutes to make a newly provisioned CIDR available to IPAM. If allocation fails immediately after provisioning, wait for the pool CIDR state to become provisioned and run a fresh plan.
Apply the lab only after verifying the CIDR reservation and expected region:
terraform apply
terraform output allocated_vpc_cidr
Inspect the VPC in IPAM and compare its allocation CIDR with the Terraform output. Also inspect the Terraform state to confirm the VPC is associated with the intended pool. If the VPC exists in AWS but is missing from IPAM, investigate resource discovery, pool association, and account permissions before creating another VPC. Reapplying blindly can allocate a second block instead of reconciling the first.
When the allocation fails, check the following in order: the pool has a provisioned CIDR, the pool locale matches the VPC region, the netmask length falls within the pool’s allowed lengths, and the account has permission to allocate. Then inspect any allocation rules or resource tags required by your organization’s pool policy. A useful failure message from IPAM is more specific than a generic Terraform apply error, so read the provider response before changing the plan.
Open the IPAM console or use AWS APIs to confirm that the pool shows the VPC allocation. If the VPC is destroyed, IPAM returns the allocation to the pool according to its lifecycle. Keep the pool itself and its parent CIDR under deliberate ownership.
Extend the design across accounts
For a multi account network, place IPAM administration in a delegated network account, create regional pools, and share child pools through AWS Resource Access Manager. Give participant accounts permission to allocate from the shared pool without granting them permission to change the parent allocation hierarchy.
Before sharing a pool, decide who can create allocations, who can release them, and which account owns the Terraform state for each resource. A participant role should not be able to alter the IPAM parent or remove another team’s pool CIDR. Tag VPCs with an owning team and environment so the allocation can be reconciled with an application inventory.
Test the sharing workflow with a small nonproduction participant account. Verify that the participant can allocate the allowed prefix and that it cannot create an allocation outside the shared pool. Then destroy the test VPC and confirm that the IPAM allocation returns to available capacity. This verifies both the permission boundary and the cleanup path before real workloads depend on the hierarchy.
Decide which Terraform state owns the IPAM, each pool, and each workload VPC. Avoid declaring the same pool in multiple states. The AWS IPAM allocation guide describes the distinction between provisioning a CIDR into a pool and allocating that CIDR to a resource. The Terraform provider documents the pool resource at aws_vpc_ipam_pool.
Clean up the lab
Run terraform destroy from the same initialized directory and review the destroy plan. Terraform removes the subnets and VPC before removing the pool CIDR, pool, and IPAM because of their resource dependencies. Do not destroy shared IPAM infrastructure when cleaning up a workload state.
Start with one regional pool whose parent CIDR has an approved owner, then allocate a test VPC and confirm that the resulting CIDR appears in IPAM. Expand the pool hierarchy only after the allocation and cleanup lifecycle works for your account boundaries.
Frequently Asked Questions
How do I assign a VPC CIDR from AWS IPAM with Terraform?
Does Terraform reserve the VPC CIDR in an AWS IPAM pool?
Can I use an AWS IPAM pool across accounts?
Stop reaching for a spreadsheet
Obelinf keeps every subnet, device, circuit, and rack in one live source of truth, with audit logs and a topology view. Free for personal use.
Related Articles

AWS VPC Design Best Practices: CIDR, Subnets, and Routing
Design an AWS VPC the right way: size the CIDR block, lay out public, private, and isolated subnets across availability zones, and wire route tables without common mistakes.
Read more
How to Use Terraform with IPAM for Network Subnets
Connect Terraform to IPAM with this hands on guide. Use Obelinf to find an approved subnet, read its CIDR safely, and plan an AWS network from it.
Read more
Terraform CIDR Planning with Subnet Examples
Use Terraform cidrsubnet and cidrsubnets to divide a VPC range into stable subnet blocks, validate the result, and avoid renumbering deployed networks.
Read more