Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Terraform module for public and private subnets provisioning in existing VPC

License

NotificationsYou must be signed in to change notification settings

cloudposse/terraform-aws-dynamic-subnets

Project Banner

Latest ReleaseLast UpdatedSlack CommunityGet Support

Terraform module to provision public and privatesubnets in an existingVPC

Note: This module is intended for use with an existing VPC and existing Internet Gateway.To create a new VPC, useterraform-aws-vpc module.

Note: Due to Terraformlimitations,many optional inputs to this module are specified as alist(string) that can have zero or one element, rather thanas astring that could be empty ornull. The designation of an input as alist type does not necessarilymean that you can supply more than one value in the list, so check the input's description before supplying more than one value.

The core function of this module is to create 2 sets of subnets, a "public" set with bidirectional access to thepublic internet, and a "private" set behind a firewall with egress-only access to the public internet. Thisincludes dividing up a given CIDR range so that a each subnet gets its owndistinct CIDR range within that range, and then creating those subnets in the appropriate availability zones.The intention is to keep this module relatively simple and easy to use for the most popular use cases.In its default configuration, this module creates 1 public subnet and 1 private subnet in eachof the specified availability zones. The public subnets are configured for bi-directional traffic to thepublic internet, while the private subnets are configured for egress-only traffic to the public internet.

The module supports creating different numbers of public and private subnets per availability zone. This is usefulfor common architectures where you need a single public subnet for load balancers but multiple private subnetsfor different application tiers (web, app, data). You can specify the number and names of public and privatesubnets independently usingpublic_subnets_per_az_count/public_subnets_per_az_names andprivate_subnets_per_az_count/private_subnets_per_az_names variables.

Rather than provide a wealth of configuration options allowing for numerous special cases, this moduleprovides some common options and further provides the ability to suppress the creation of resources, allowingyou to create and configure them as you like from outside this module. For example, rather than give you theoption to customize the Network ACL, the module gives you the option to create a completely open one (and controlaccess via Security Groups and other means) or not create one at all, allowing you to create and configure one yourself.

Public subnets

This module defines a public subnet as one that has direct access to an internet gateway and can accept incoming connection requests.In the simplest configuration, the module creates a single route table with a default route targeted to theVPC's internet gateway, and associates all the public subnets with that single route table.

Likewise it creates a single Network ACL with associated rules allowing all ingress and all egress,and associates that ACL with all the public subnets.

Private subnets

A private subnet may be able to initiate traffic to the public internet through a NAT gateway,a NAT instance, or an egress-only internet gateway, or it might only have direct access to otherprivate subnets. In the simple configuration, for IPv4 and/or IPv6 with NAT64 enabled viapublic_dns64_enabledorprivate_dns64_enabled, the module creates 1 NAT Gateway or NAT Instance for eachprivate subnet (in the public subnet in the same availability zone), creates 1 route table for each private subnet,and adds to that route table a default route from the subnet to its NAT Gateway or Instance. For IPv6,the module adds a route to the Egress-Only Internet Gateway configured via input.

As with the Public subnets, the module creates a single Network ACL with associated rules allowing all ingress andall egress, and associates that ACL with all the private subnets.

Customization for special use cases

Various features are controlled bybool inputs with names ending in_enabled. By changing the defaultvalues, you can enable or disable creation of public subnets, private subnets, route tables,NAT gateways, NAT instances, or Network ACLs. So for example, you could use this module to create onlyprivate subnets and the open Network ACL, and then add your own route table associations to the subnetsand route all non-local traffic to a Transit Gateway or VPN.

CIDR allocation

For IPv4, you provide a CIDR and the module divides the address space into the largest CIDRs possible that are stillsmall enough to accommodatemax_subnet_count subnets of each enabled type (public or private). Whenmax_subnet_countis left at the default0, it is set to the total number of availability zones in the region. Private subnetsare allocated out of the first half of the reserved range, and public subnets are allocated out of the second half.

For IPv6, you provide a/56 CIDR and the module assigns/64 subnets of that CIDR in consecutive order startingat zero. (You have the option of specifying a list of CIDRs instead.) As with IPv4, enough CIDRs are allocated tocovermax_subnet_count private and public subnets (when both are enabled, which is the default), with the privatesubnets being allocated out of the lower half of the reservation and the public subnets allocated out of the upper half.

Deployment Modes and Configuration

This module supports various deployment modes through flexible configuration variables. Understanding these optionsallows you to tailor the subnet architecture to your specific use case.

Availability Zone Selection

availability_zones - Explicitly specify which AZs to use:

  • Provide a list of AZ names (e.g.,["us-east-1a", "us-east-1b", "us-east-1c"])
  • The list ordermust be stable - do not reorder or Terraform will recreate subnets
  • If empty, the module uses all available AZs in the region (sorted alphabetically)
  • Can be truncated bymax_subnet_count if you specify more AZs than the limit

availability_zone_ids - Use AZ IDs instead of names:

  • Provide a list of AZ IDs (e.g.,["use1-az1", "use1-az2"])
  • Overridesavailability_zones when set
  • Useful for multi-account consistency (AZ names like "us-east-1a" map to different physical locations across accounts, but AZ IDs are consistent)
  • The module automatically translates IDs to names for resource creation

Subnet Count and CIDR Reservation

max_subnet_count - Controls CIDR reservation for future growth:

  • Default:0 (reserves CIDRs for all AZs in the region)
  • Recommended: Set to3 or the maximum number of AZs you anticipate using
  • The module reserves CIDR space for this many subnets ofeach type (public and private)
  • Example: If a region has 4 AZs but you setmax_subnet_count = 3, only 3 subnets will be created, but you can later expand to the 4th without changing existing subnet CIDRs
  • Important: This must be a constant value, not computed, due to Terraform limitations

subnets_per_az_count - Create multiple subnets of each type per AZ:

  • Default:1 (one public and one private subnet per AZ)
  • Set to2 or higher to create multiple subnets per AZ
  • Creates thesame number of public and private subnets
  • Useful for segmenting workloads within the same AZ (e.g., separate subnets for web tier, app tier, data tier)
  • Each subnet gets its own CIDR from the allocated range
  • Works withsubnets_per_az_names for organized outputs

subnets_per_az_names - Assign names to subnets for better organization:

  • Default:["common"]
  • Provide a list of names matchingsubnets_per_az_count (e.g.,["web", "app", "data"])
  • Names are used as keys in thenamed_private_subnets_map andnamed_public_subnets_map outputs
  • Makes it easy to reference specific subnet groups in other modules
  • Example:module.subnets.named_private_subnets_map["web"] returns all web-tier private subnet IDs

public_subnets_per_az_count - Set a different number of public subnets per AZ:

  • Default:null (usessubnets_per_az_count for backward compatibility)
  • Set this when you need a different number of public vs private subnets
  • Common pattern: Set to1 for a single public subnet (for load balancers) while having multiple private subnets
  • Must be greater than 0 if specified
  • Works independently fromprivate_subnets_per_az_count

public_subnets_per_az_names - Assign names specifically to public subnets:

  • Default:null (usessubnets_per_az_names for backward compatibility)
  • Provide a list of names matchingpublic_subnets_per_az_count
  • Names are used as keys in thenamed_public_subnets_map output
  • Example:["public-lb"] for a single load balancer subnet per AZ

private_subnets_per_az_count - Set a different number of private subnets per AZ:

  • Default:null (usessubnets_per_az_count for backward compatibility)
  • Set this when you need a different number of private vs public subnets
  • Common pattern: Set to3 for multi-tier architecture (web, app, data) while having only 1 public subnet
  • Must be greater than 0 if specified
  • Works independently frompublic_subnets_per_az_count

private_subnets_per_az_names - Assign names specifically to private subnets:

  • Default:null (usessubnets_per_az_names for backward compatibility)
  • Provide a list of names matchingprivate_subnets_per_az_count
  • Names are used as keys in thenamed_private_subnets_map output
  • Example:["web", "app", "data"] for a three-tier architecture

Subnet Type Selection

public_subnets_enabled - Enable/disable public subnet creation:

  • Default:true
  • Set tofalse to create only private subnets
  • When disabled, NAT Gateways/Instances are also disabled (since they require public subnets)
  • Use case: Internal-only VPCs that route through Transit Gateway or VPN

private_subnets_enabled - Enable/disable private subnet creation:

  • Default:true
  • Set tofalse to create only public subnets
  • When disabled, NAT Gateways/Instances are also disabled (since private subnets don't need them)
  • Use case: DMZ or edge VPCs with only internet-facing resources

NAT Configuration and Cost Optimization

max_nats - Limit the number of NAT devices for cost savings:

  • Default:999 (creates one NAT per AZ for high availability)
  • Set to1 for cost savings (single NAT, reduced availability)
  • Set to2 for balance between cost and availability (two NATs across AZs)
  • Cost impact: Each NAT Gateway costs ~$32/month plus data transfer fees
  • Availability impact: If the NAT fails (or its AZ fails), private subnets lose internet access
  • The module distributes NAT devices across the first N availability zones
  • Example: With 3 AZs andmax_nats = 1, only the first AZ gets a NAT Gateway

nat_gateway_public_subnet_indices - Control which public subnet gets the NAT Gateway (by index):

  • Default:[0] (place NAT in the first public subnet of each AZ)
  • When you have multiple public subnets per AZ, this determines which one hosts the NAT Gateway
  • NAT Gateways are shared - one NAT per AZ serves all private subnets in that AZ
  • Important: Each subnet index must be less thanpublic_subnets_per_az_count
  • Example: Withpublic_subnets_per_az_count = 2 andnat_gateway_public_subnet_indices = [0], the NAT goes in the first public subnet
  • Advanced: Set to[0, 1] to create redundant NATs within each AZ (rarely needed, increases cost)
  • Cannot be used withnat_gateway_public_subnet_names (choose indices OR names, not both)

nat_gateway_public_subnet_names - Control which public subnet gets the NAT Gateway (by name):

  • Default:null (usesnat_gateway_public_subnet_indices instead)
  • More intuitive alternative to using indices - specify subnets by name
  • References the names frompublic_subnets_per_az_names
  • Example:["loadbalancer"] places NAT in the "loadbalancer" subnet
  • Example:["loadbalancer", "web"] creates 2 NATs per AZ in both named subnets (expensive)
  • Cannot be used withnat_gateway_public_subnet_indices (choose indices OR names, not both)
  • Recommended approach for clarity and maintainability

Common Deployment Patterns

Standard HA deployment (default):

# 1 public + 1 private subnet per AZ, with NAT Gateway per AZpublic_subnets_enabled=trueprivate_subnets_enabled=truemax_subnet_count=3# Reserve space for 3 AZsmax_nats=999# One NAT per AZ

Cost-optimized deployment:

# Single NAT Gateway shared across all private subnetsmax_nats=1# Everything else default

Private-only with Transit Gateway:

# Private subnets only, routing through TGWpublic_subnets_enabled=falseprivate_subnets_enabled=truenat_gateway_enabled=false# Add custom routes to TGW externally

Public-only (DMZ) deployment:

# Public subnets only for internet-facing resourcespublic_subnets_enabled=trueprivate_subnets_enabled=false

Multi-tier architecture per AZ (legacy approach - same number of public and private):

# 3 private AND 3 public subnets per AZ (web, app, data)subnets_per_az_count=3subnets_per_az_names=["web","app","data"]# Access subnets via: named_private_subnets_map["web"]

Multi-tier with separate public/private counts (recommended):

# 1 public subnet per AZ for load balancers# 3 private subnets per AZ for web, app, and data tierspublic_subnets_per_az_count=1public_subnets_per_az_names=["public-lb"]private_subnets_per_az_count=3private_subnets_per_az_names=["web","app","data"]# NAT Gateway automatically goes in the first (and only) public subnet# Can omit nat_gateway config since there's only one public subnet# Access subnets via:#   named_public_subnets_map["public-lb"]#   named_private_subnets_map["web"]#   named_private_subnets_map["app"]#   named_private_subnets_map["data"]

Multiple public subnets with controlled NAT placement:

# 2 public subnets per AZ: one for ALB, one for bastion hosts# Place NAT Gateway in the ALB subnetpublic_subnets_per_az_count=2public_subnets_per_az_names=["alb","bastion"]# OPTION 1: Use subnet name (recommended - more readable)nat_gateway_public_subnet_names=["alb"]# OPTION 2: Use subnet index (alternative)# nat_gateway_public_subnet_indices = [0]  # 0 = first subnet = "alb"# Result: 1 NAT per AZ in the "alb" subnet, shared by all private subnets

Multiple private + multiple public with one NAT in specific subnet:

# Real-world example: Database, app tiers + load balancer and web frontends# 3 private subnets per AZ (database, app1, app2)# 2 public subnets per AZ (loadbalancer, web)# 1 NAT Gateway per AZ in the "loadbalancer" subnetavailability_zones=["us-east-1a","us-east-1b","us-east-1c"]private_subnets_per_az_count=3private_subnets_per_az_names=["database","app1","app2"]public_subnets_per_az_count=2public_subnets_per_az_names=["loadbalancer","web"]# Place NAT Gateway in the "loadbalancer" subnet (by name)nat_gateway_public_subnet_names=["loadbalancer"]# Result per AZ:# - 3 private subnets: database, app1, app2# - 2 public subnets: loadbalancer, web# - 1 NAT Gateway in "loadbalancer" subnet# - All 3 private subnets route to the same NAT# Total: 3 NAT Gateways (one per AZ) = ~$96/month

Multiple private + multiple public with NAT in EACH public subnet (high availability):

# Advanced: Redundant NAT Gateways within each AZ for maximum availability# 3 private subnets per AZ (database, app1, app2)# 2 public subnets per AZ (loadbalancer, web)# 2 NAT Gateways per AZ (one in each public subnet)availability_zones=["us-east-1a","us-east-1b","us-east-1c"]private_subnets_per_az_count=3private_subnets_per_az_names=["database","app1","app2"]public_subnets_per_az_count=2public_subnets_per_az_names=["loadbalancer","web"]# Place NAT Gateways in BOTH public subnets (by name)nat_gateway_public_subnet_names=["loadbalancer","web"]# Alternative using indices:# nat_gateway_public_subnet_indices = [0, 1]# Result per AZ:# - 3 private subnets: database, app1, app2# - 2 public subnets: loadbalancer, web# - 2 NAT Gateways: one in "loadbalancer", one in "web"# - Private subnets distributed across NATs:#   - "database" → NAT in "loadbalancer"#   - "app1" → NAT in "web"#   - "app2" → NAT in "loadbalancer"# Total: 6 NAT Gateways (2 per AZ × 3 AZs) = ~$192/month# WARNING: This is expensive. Use only if you need intra-AZ NAT redundancy.

NAT Gateway ID References in Outputs

The module exposes NAT Gateway IDs in the subnet stats outputs, enabling downstream components like networkfirewalls to reference the NAT Gateways associated with each subnet.

named_private_subnets_stats_map - Each private subnet includes the NAT Gateway ID it routes to:

# Output structure (4 fields per subnet):named_private_subnets_stats_map={"database"= [    {      az="us-east-2a"      subnet_id="subnet-abc123"      route_table_id="rtb-def456"      nat_gateway_id="nat-xyz789"# NAT Gateway this subnet routes to for egress    },# ... one entry per AZ  ]"app1"= [... ]"app2"= [... ]}

named_public_subnets_stats_map - Each public subnet includes the NAT Gateway ID if one exists in that subnet:

# Output structure (4 fields per subnet):named_public_subnets_stats_map={"loadbalancer"= [    {      az="us-east-2a"      subnet_id="subnet-ghi789"      route_table_id="rtb-jkl012"      nat_gateway_id="nat-xyz789"# NAT Gateway in this public subnet (if any)    },# ... one entry per AZ  ]"web"= [... ]}

Use case example - Network firewall routing:

# Reference NAT Gateway IDs from subnet statslocals {database_nat_gateways=[forstatsinmodule.subnets.named_private_subnets_stats_map["database"]:stats.nat_gateway_idifstats.nat_gateway_id!=""  ]}# Use in network firewall route configurationresource"aws_networkfirewall_firewall_policy""example" {# ... configuration that needs NAT Gateway IDs}

Multi-account with consistent AZs:

# Use AZ IDs for consistency across accountsavailability_zone_ids=["use1-az1","use1-az2","use1-az4"]# These map to the same physical locations across all accounts

Tip

👽 Use Atmos with Terraform

Cloud Posse usesatmos to easily orchestrate multiple environments using Terraform.
Works withGithub Actions,Atlantis, orSpacelift.

Watch demo of using Atmos with Terraform
Example of runningatmos to manage infrastructure from ourQuick Start tutorial.

Usage

module"subnets" {source="cloudposse/dynamic-subnets/aws"# Cloud Posse recommends pinning every module to a specific version# version = "x.x.x"namespace="eg"stage="prod"name="app"vpc_id="vpc-XXXXXXXX"igw_id=["igw-XXXXXXXX"]ipv4_cidr_block=["10.0.0.0/16"]availability_zones=["us-east-1a","us-east-1b"]}

Create only private subnets, route to transit gateway:

module"private_tgw_subnets" {source="cloudposse/dynamic-subnets/aws"# Cloud Posse recommends pinning every module to a specific version# version = "x.x.x"namespace="eg"stage="prod"name="app"vpc_id="vpc-XXXXXXXX"igw_id=["igw-XXXXXXXX"]ipv4_cidr_block=["10.0.0.0/16"]availability_zones=["us-east-1a","us-east-1b"]nat_gateway_enabled=falsepublic_subnets_enabled=false}resource"aws_route""private" {count=length(module.private_tgw_subnets.private_route_table_ids)route_table_id=module.private_tgw_subnets.private_route_table_ids[count.index]destination_cidr_block="0.0.0.0/0"transit_gateway_id="tgw-XXXXXXXXX"}

Seeexamples for working examples. In particular, seeexamples/naclsfor an example of how to create custom Network Access Control Lists (NACLs) outside ofbut in conjunction with this module.

Important

In Cloud Posse's examples, we avoid pinning modules to specific versions to prevent discrepancies between the documentationand the latest released versions. However, for your own projects, we strongly advise pinning each module to the exact versionyou're using. This practice ensures the stability of your infrastructure. Additionally, we recommend implementing a systematicapproach for updating versions to avoid unexpected changes.

Requirements

NameVersion
terraform>= 1.1.0
aws>= 5.0

Providers

NameVersion
aws>= 5.0

Modules

NameSourceVersion
nat_instance_labelcloudposse/label/null0.25.0
nat_labelcloudposse/label/null0.25.0
private_labelcloudposse/label/null0.25.0
public_labelcloudposse/label/null0.25.0
thiscloudposse/label/null0.25.0
utilscloudposse/utils/aws1.4.0

Resources

NameType
aws_eip.defaultresource
aws_eip_association.nat_instanceresource
aws_instance.nat_instanceresource
aws_nat_gateway.defaultresource
aws_network_acl.privateresource
aws_network_acl.publicresource
aws_network_acl_rule.private4_egressresource
aws_network_acl_rule.private4_ingressresource
aws_network_acl_rule.private6_egressresource
aws_network_acl_rule.private6_ingressresource
aws_network_acl_rule.public4_egressresource
aws_network_acl_rule.public4_ingressresource
aws_network_acl_rule.public6_egressresource
aws_network_acl_rule.public6_ingressresource
aws_route.nat4resource
aws_route.nat_instanceresource
aws_route.private6resource
aws_route.private_nat64resource
aws_route.publicresource
aws_route.public6resource
aws_route.public_nat64resource
aws_route_table.privateresource
aws_route_table.publicresource
aws_route_table_association.privateresource
aws_route_table_association.publicresource
aws_security_group.nat_instanceresource
aws_security_group_rule.nat_instance_egressresource
aws_security_group_rule.nat_instance_ingressresource
aws_subnet.privateresource
aws_subnet.publicresource
aws_ami.nat_instancedata source
aws_availability_zones.defaultdata source
aws_eip.natdata source
aws_vpc.defaultdata source

Inputs

NameDescriptionTypeDefaultRequired
additional_tag_mapAdditional key-value pairs to add to each map intags_as_list_of_maps. Not added totags orid.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration.
map(string){}no
attributesID element. Additional attributes (e.g.workers orcluster) to add toid,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by thedelimiter
and treated as a single ID element.
list(string)[]no
availability_zone_attribute_styleThe style of Availability Zone code to use in tags and names. One offull,short, orfixed.
When usingavailability_zone_ids, IDs will first be translated into AZ names.
string"short"no
availability_zone_idsList of Availability Zones IDs where subnets will be created. Overridesavailability_zones.
Useful in some regions when using only some AZs and you want to use the same ones across multiple accounts.
list(string)[]no
availability_zonesList of Availability Zones (AZs) where subnets will be created. Ignored whenavailability_zone_ids is set.
The order of zones in the listmust be stable or else Terraform will continually make changes.
If no AZs are specified, thenmax_subnet_count AZs will be selected in alphabetical order.
Ifmax_subnet_count > 0 andlength(var.availability_zones) > max_subnet_count, the list
will be truncated. We recommend settingavailability_zones andmax_subnet_count explicitly as constant
(not computed) values for predictability, consistency, and stability.
list(string)[]no
aws_route_create_timeoutDEPRECATED: Useroute_create_timeout instead.
Time to wait for AWS route creation, specified as a Go Duration, e.g.2m
stringnullno
aws_route_delete_timeoutDEPRECATED: Useroute_delete_timeout instead.
Time to wait for AWS route deletion, specified as a Go Duration, e.g.2m
stringnullno
contextSingle object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables asnull to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional_tag_map, which are merged.
any
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
no
delimiterDelimiter to be used between ID elements.
Defaults to- (hyphen). Set to"" to use no delimiter at all.
stringnullno
descriptor_formatsDescribe additional descriptors to be output in thedescriptors output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
{<br/> format = string<br/> labels = list(string)<br/>}
(Type isany so the map values can later be enhanced to provide additional options.)
format is a Terraform format string to be passed to theformat() function.
labels is a list of labels, in order, to pass toformat() function.
Label values will be normalized before being passed toformat() so they will be
identical to how they appear inid.
Default is{} (descriptors output will be empty).
any{}no
enabledSet to false to prevent the module from creating any resourcesboolnullno
environmentID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'stringnullno
id_length_limitLimitid to this many characters (minimum 6).
Set to0 for unlimited length.
Set tonull for keep the existing setting, which defaults to0.
Does not affectid_full.
numbernullno
igw_idThe Internet Gateway ID that the public subnets will route traffic to.
Used ifpublic_route_table_enabled istrue, ignored otherwise.
list(string)[]no
ipv4_cidr_blockBase IPv4 CIDR block which will be divided into subnet CIDR blocks (e.g.10.0.0.0/16). Ignored ifipv4_cidrs is set.
If no CIDR block is provided, the VPC's default IPv4 CIDR block will be used.
list(string)[]no
ipv4_cidrsLists of CIDRs to assign to subnets. Order of CIDRs in the lists must not change over time.
Lists may contain more CIDRs than needed.
list(object({
private = list(string)
public = list(string)
}))
[]no
ipv4_enabledSettrue to enable IPv4 addresses in the subnetsbooltrueno
ipv4_private_instance_hostname_typeHow to generate the DNS name for the instances in the private subnets.
Eitherip-name to generate it from the IPv4 address, or
resource-name to generate it from the instance ID.
string"ip-name"no
ipv4_private_instance_hostnames_enabledIftrue, DNS queries for instance hostnames in the private subnets will be answered with A (IPv4) records.boolfalseno
ipv4_public_instance_hostname_typeHow to generate the DNS name for the instances in the public subnets.
Eitherip-name to generate it from the IPv4 address, or
resource-name to generate it from the instance ID.
string"ip-name"no
ipv4_public_instance_hostnames_enabledIftrue, DNS queries for instance hostnames in the public subnets will be answered with A (IPv4) records.boolfalseno
ipv6_cidr_blockBase IPv6 CIDR block from which/64 subnet CIDRs will be assigned. Must be/56. (e.g.2600:1f16:c52:ab00::/56).
Ignored ifipv6_cidrs is set. If no CIDR block is provided, the VPC's default IPv6 CIDR block will be used.
list(string)[]no
ipv6_cidrsLists of CIDRs to assign to subnets. Order of CIDRs in the lists must not change over time.
Lists may contain more CIDRs than needed.
list(object({
private = list(string)
public = list(string)
}))
[]no
ipv6_egress_only_igw_idThe Egress Only Internet Gateway ID the private IPv6 subnets will route traffic to.
Used ifprivate_route_table_enabled istrue andipv6_enabled istrue, ignored otherwise.
list(string)[]no
ipv6_enabledSettrue to enable IPv6 addresses in the subnetsboolfalseno
ipv6_private_instance_hostnames_enabledIftrue (or ifipv4_enabled isfalse), DNS queries for instance hostnames in the private subnets will be answered with AAAA (IPv6) records.boolfalseno
ipv6_public_instance_hostnames_enabledIftrue (or ifipv4_enabled is false), DNS queries for instance hostnames in the public subnets will be answered with AAAA (IPv6) records.boolfalseno
label_key_caseControls the letter case of thetags keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via thetags input.
Possible values:lower,title,upper.
Default value:title.
stringnullno
label_orderThe order in which the labels (ID elements) appear in theid.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present.
list(string)nullno
label_value_caseControls the letter case of ID elements (labels) as included inid,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via thetags input.
Possible values:lower,title,upper andnone (no transformation).
Set this totitle and setdelimiter to"" to yield Pascal Case IDs.
Default value:lower.
stringnullno
labels_as_tagsSet of labels (ID elements) to include as tags in thetags output.
Default is to include all labels.
Tags with empty values will not be included in thetags output.
Set to[] to suppress all generated tags.
Notes:
The value of thename tag, if included, will be theid, not thename.
Unlike othernull-label inputs, the initial setting oflabels_as_tags cannot be
changed in later chained modules. Attempts to change it will be silently ignored.
set(string)
[
"default"
]
no
map_public_ip_on_launchIftrue, instances launched into a public subnet will be assigned a public IPv4 addressbooltrueno
max_natsUpper limit on number of NAT Gateways/Instances to create.
Set to 1 or 2 for cost savings at the expense of availability.
number999no
max_subnet_countSets the maximum number of each type (public or private) of subnet to deploy.
0 will reserve a CIDR for every Availability Zone (excluding Local Zones) in the region, and
deploy a subnet in each availability zone specified inavailability_zones oravailability_zone_ids,
or every zone if none are specified. We recommend setting this equal to the maximum number of AZs you anticipate using,
to avoid causing subnets to be destroyed and recreated with smaller IPv4 CIDRs when AWS adds an availability zone.
Due to Terraform limitations, you can not setmax_subnet_count from a computed value, you have to set it
from an explicit constant. For most cases,3 is a good choice.
number0no
metadata_http_endpoint_enabledWhether the metadata service is available on the created NAT instancesbooltrueno
metadata_http_put_response_hop_limitThe desired HTTP PUT response hop limit (between 1 and 64) for instance metadata requests on the created NAT instancesnumber1no
metadata_http_tokens_requiredWhether or not the metadata service requires session tokens, also referred to as Instance Metadata Service Version 2, on the created NAT instancesbooltrueno
nameID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as atag.
The "name" tag is set to the fullid string. There is no tag with the value of thename input.
stringnullno
namespaceID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally uniquestringnullno
nat_elastic_ipsExisting Elastic IPs (not EIP IDs) to attach to the NAT Gateway(s) or Instance(s) instead of creating new ones.list(string)[]no
nat_gateway_enabledSettrue to create NAT Gateways to perform IPv4 NAT and NAT64 as needed.
Defaults totrue unlessnat_instance_enabled istrue.
boolnullno
nat_gateway_public_subnet_indicesThe index (starting from 0) of the public subnet in each AZ to place the NAT Gateway.
If you have multiple public subnets per AZ (viapublic_subnets_per_az_count), this determines which one gets the NAT Gateway.
Default:[0] (use the first public subnet in each AZ).
You can specify multiple indices if you want redundant NATs within an AZ, but this is rarely needed and increases cost.
Cannot be used together withnat_gateway_public_subnet_names.
Example:[0] creates 1 NAT per AZ in the first public subnet.
Example:[0, 1] creates 2 NATs per AZ in the first and second public subnets (expensive).
list(number)
[
0
]
no
nat_gateway_public_subnet_namesThe names of the public subnets in each AZ where NAT Gateways should be placed.
Uses the names frompublic_subnets_per_az_names to determine placement.
This is more intuitive than using indices - specify the subnet by name instead of position.
Cannot be used together withnat_gateway_public_subnet_indices (only use indices OR names, not both).
If not specified, defaults to usingnat_gateway_public_subnet_indices.
Example:["loadbalancer"] creates 1 NAT per AZ in the "loadbalancer" subnet.
Example:["loadbalancer", "web"] creates 2 NATs per AZ in "loadbalancer" and "web" subnets (expensive).
list(string)nullno
nat_instance_ami_idA list optionally containing the ID of the AMI to use for the NAT instance.
If the list is empty (the default), the latest official AWS NAT instance AMI
will be used. NOTE: The Official NAT instance AMI is being phased out and
does not support NAT64. Use of a NAT gateway is recommended instead.
list(string)[]no
nat_instance_cpu_credits_overrideNAT Instance credit option for CPU usage. Valid values are "standard" or "unlimited".
T3 and later instances are launched as unlimited by default. T2 instances are launched as standard by default.
string""no
nat_instance_enabledSettrue to create NAT Instances to perform IPv4 NAT.
Defaults tofalse.
boolnullno
nat_instance_root_block_device_encryptedWhether to encrypt the root block device on the created NAT instancesbooltrueno
nat_instance_typeNAT Instance typestring"t3.micro"no
open_network_acl_ipv4_rule_numberTherule_no assigned to the network ACL rules for IPv4 traffic generated by this modulenumber100no
open_network_acl_ipv6_rule_numberTherule_no assigned to the network ACL rules for IPv6 traffic generated by this modulenumber111no
private_assign_ipv6_address_on_creationIftrue, network interfaces created in a private subnet will be assigned an IPv6 addressbooltrueno
private_dns64_nat64_enabledIftrue and IPv6 is enabled, DNS queries made to the Amazon-provided DNS Resolver in private subnets will return synthetic
IPv6 addresses for IPv4-only destinations, and these addresses will be routed to the NAT Gateway.
Requirespublic_subnets_enabled,nat_gateway_enabled, andprivate_route_table_enabled to betrue to be fully operational.
Defaults totrue unless there is no public IPv4 subnet for egress, in which case it defaults tofalse.
boolnullno
private_labelThe string to use in IDs and elsewhere to identify resources for the private subnets and distinguish them from resources for the public subnetsstring"private"no
private_open_network_acl_enabledIftrue, a single network ACL be created and it will be associated with every private subnet, and a rule (number 100)
will be created allowing all ingress and all egress. You can add additional rules to this network ACL
using theaws_network_acl_rule resource.
Iffalse, you will need to manage the network ACL outside of this module.
booltrueno
private_route_table_enabledIftrue, a network route table and default route to the NAT gateway, NAT instance, or egress-only gateway
will be created for each private subnet (1:1). If false, you will need to create your own route table(s) and route(s).
booltrueno
private_subnets_additional_tagsAdditional tags to be added to private subnetsmap(string){}no
private_subnets_enabledIf false, do not create private subnets (or NAT gateways or instances)booltrueno
private_subnets_per_az_countThe number of private subnets to provision per Availability Zone.
If not provided, defaults to the value ofsubnets_per_az_count for backward compatibility.
Set this to create a different number of private subnets than public subnets.
numbernullno
private_subnets_per_az_namesThe names to assign to the private subnets per Availability Zone.
If not provided, defaults to the value ofsubnets_per_az_names for backward compatibility.
If provided, the length must matchprivate_subnets_per_az_count.
The names will be used as keys in the outputsnamed_private_subnets_map andnamed_private_route_table_ids_map.
list(string)nullno
public_assign_ipv6_address_on_creationIftrue, network interfaces created in a public subnet will be assigned an IPv6 addressbooltrueno
public_dns64_nat64_enabledIftrue and IPv6 is enabled, DNS queries made to the Amazon-provided DNS Resolver in public subnets will return synthetic
IPv6 addresses for IPv4-only destinations, and these addresses will be routed to the NAT Gateway.
Requiresnat_gateway_enabled andpublic_route_table_enabled to betrue to be fully operational.
boolfalseno
public_labelThe string to use in IDs and elsewhere to identify resources for the public subnets and distinguish them from resources for the private subnetsstring"public"no
public_open_network_acl_enabledIftrue, a single network ACL be created and it will be associated with every public subnet, and a rule
will be created allowing all ingress and all egress. You can add additional rules to this network ACL
using theaws_network_acl_rule resource.
Iffalse, you will need to manage the network ACL outside of this module.
booltrueno
public_route_table_enabledIftrue, network route table(s) will be created as determined bypublic_route_table_per_subnet_enabled and
appropriate routes will be added to destinations this module knows about.
Iffalse, you will need to create your own route table(s) and route(s).
Ignored ifpublic_route_table_ids is non-empty.
booltrueno
public_route_table_idsList optionally containing the ID of a single route table shared by all public subnets
or exactly one route table ID for each public subnet.
If provided, it overridespublic_route_table_per_subnet_enabled.
If omitted andpublic_route_table_enabled istrue,
one or more network route tables will be created for the public subnets,
according to the setting ofpublic_route_table_per_subnet_enabled.
list(string)[]no
public_route_table_per_subnet_enabledIftrue (andpublic_route_table_enabled istrue), a separate network route table will be created for and associated with each public subnet.
Iffalse (andpublic_route_table_enabled istrue), a single network route table will be created and it will be associated with every public subnet.
If not set, it will be set to the value ofpublic_dns64_nat64_enabled.
boolnullno
public_subnets_additional_tagsAdditional tags to be added to public subnetsmap(string){}no
public_subnets_enabledIf false, do not create public subnets.
Since NAT gateways and instances must be created in public subnets, these will also not be created whenfalse.
booltrueno
public_subnets_per_az_countThe number of public subnets to provision per Availability Zone.
If not provided, defaults to the value ofsubnets_per_az_count for backward compatibility.
Set this to create a different number of public subnets than private subnets.
numbernullno
public_subnets_per_az_namesThe names to assign to the public subnets per Availability Zone.
If not provided, defaults to the value ofsubnets_per_az_names for backward compatibility.
If provided, the length must matchpublic_subnets_per_az_count.
The names will be used as keys in the outputsnamed_public_subnets_map andnamed_public_route_table_ids_map.
list(string)nullno
regex_replace_charsTerraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set,"/[^a-zA-Z0-9-]/" is used to remove all characters other than hyphens, letters and digits.
stringnullno
root_block_device_encryptedDEPRECATED: usenat_instance_root_block_device_encrypted instead.
Whether to encrypt the root block device on the created NAT instances
boolnullno
route_create_timeoutTime to wait for a network routing table entry to be created, specified as a Go Duration, e.g.2m. Usenull for proivder default.stringnullno
route_delete_timeoutTime to wait for a network routing table entry to be deleted, specified as a Go Duration, e.g.2m. Usenull for proivder default.stringnullno
stageID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'stringnullno
subnet_create_timeoutTime to wait for a subnet to be created, specified as a Go Duration, e.g.2m. Usenull for proivder default.stringnullno
subnet_delete_timeoutTime to wait for a subnet to be deleted, specified as a Go Duration, e.g.5m. Usenull for proivder default.stringnullno
subnet_type_tag_keyDEPRECATED: Usepublic_subnets_additional_tags andprivate_subnets_additional_tags instead
Key for subnet type tag to provide information about the type of subnets, e.g.cpco.io/subnet/type: private orcpco.io/subnet/type: public
stringnullno
subnet_type_tag_value_formatDEPRECATED: Usepublic_subnets_additional_tags andprivate_subnets_additional_tags instead.
The value of thesubnet_type_tag_key will be set toformat(var.subnet_type_tag_value_format, <type>)
where<type> is eitherpublic orprivate.
string"%s"no
subnets_per_az_countThe number of subnet of each type (public or private) to provision per Availability Zone.number1no
subnets_per_az_namesThe subnet names of each type (public or private) to provision per Availability Zone.
This variable is optional.
If a list of names is provided, the list items will be used as keys in the outputsnamed_private_subnets_map,named_public_subnets_map,
named_private_route_table_ids_map andnamed_public_route_table_ids_map
list(string)
[
"common"
]
no
tagsAdditional tags (e.g.{'BusinessUnit': 'XYZ'}).
Neither the tag keys nor the tag values will be modified by this module.
map(string){}no
tenantID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is forstringnullno
vpc_idVPC ID where subnets will be created (e.g.vpc-aceb2723)stringn/ayes

Outputs

NameDescription
availability_zone_idsList of Availability Zones IDs where subnets were created, when available
availability_zonesList of Availability Zones where subnets were created
az_private_route_table_ids_mapMap of AZ names to list of private route table IDs in the AZs
az_private_subnets_mapMap of AZ names to list of private subnet IDs in the AZs
az_public_route_table_ids_mapMap of AZ names to list of public route table IDs in the AZs
az_public_subnets_mapMap of AZ names to list of public subnet IDs in the AZs
named_private_route_table_ids_mapMap of subnet names (specified inprivate_subnets_per_az_names orsubnets_per_az_names variable) to lists of private route table IDs
named_private_subnets_mapMap of subnet names (specified inprivate_subnets_per_az_names orsubnets_per_az_names variable) to lists of private subnet IDs
named_private_subnets_stats_mapMap of subnet names (specified inprivate_subnets_per_az_names orsubnets_per_az_names variable) to lists of objects with each object having four items: AZ, private subnet ID, private route table ID, NAT Gateway ID (the NAT Gateway that this private subnet routes to for egress)
named_public_route_table_ids_mapMap of subnet names (specified inpublic_subnets_per_az_names orsubnets_per_az_names variable) to lists of public route table IDs
named_public_subnets_mapMap of subnet names (specified inpublic_subnets_per_az_names orsubnets_per_az_names variable) to lists of public subnet IDs
named_public_subnets_stats_mapMap of subnet names (specified inpublic_subnets_per_az_names orsubnets_per_az_names variable) to lists of objects with each object having four items: AZ, public subnet ID, public route table ID, NAT Gateway ID (the NAT Gateway in this public subnet, if any)
nat_eip_allocation_idsElastic IP allocations in use by NAT
nat_gateway_idsIDs of the NAT Gateways created
nat_gateway_public_ipsDEPRECATED: usenat_ips instead. Public IPv4 IP addresses in use by NAT.
nat_instance_ami_idID of AMI used by NAT instance
nat_instance_idsIDs of the NAT Instances created
nat_ipsElastic IP Addresses in use by NAT
private_network_acl_idID of the Network ACL created for private subnets
private_route_table_idsIDs of the created private route tables
private_subnet_arnsARNs of the created private subnets
private_subnet_cidrsIPv4 CIDR blocks of the created private subnets
private_subnet_idsIDs of the created private subnets
private_subnet_ipv6_cidrsIPv6 CIDR blocks of the created private subnets
public_network_acl_idID of the Network ACL created for public subnets
public_route_table_idsIDs of the created public route tables
public_subnet_arnsARNs of the created public subnets
public_subnet_cidrsIPv4 CIDR blocks of the created public subnets
public_subnet_idsIDs of the created public subnets
public_subnet_ipv6_cidrsIPv6 CIDR blocks of the created public subnets

Subnet calculation logic

terraform-aws-dynamic-subnets creates a set of subnets based on various CIDR inputs andthe maximum possible number of subnets, which ismax_subnet_count when specified orthe number of Availability Zones in the region whenmax_subnet_count is left atits default value of zero.

You can explicitly provide CIDRs for subnets viaipv4_cidrs andipv6_cidrs inputs if you want,but the usual use case is to provide a single CIDR which this module will subdivide into a setof CIDRs as follows:

  1. Get number of available AZ in the region:
existing_az_count = length(data.aws_availability_zones.available.names)
  1. Determine how many sets of subnets are being created. (Usually it is2:public andprivate):subnet_type_count.
  2. Multiply the results of (1) and (2) to determine how many CIDRs to reserve:
cidr_count = existing_az_count * subnet_type_count
  1. Calculate the number of bits needed to enumerate all the CIDRs:
subnet_bits = ceil(log(cidr_count, 2))
  1. Reserve CIDRs for private subnets usingcidrsubnet:
private_subnet_cidrs = [ for netnumber in range(0, existing_az_count): cidrsubnet(cidr_block, subnet_bits, netnumber) ]
  1. Reserve CIDRs for public subnets in the second half of the CIDR block:
public_subnet_cidrs = [ for netnumber in range(existing_az_count, existing_az_count * 2): cidrsubnet(cidr_block, subnet_bits, netnumber) ]

Note that this means that, for example, in a region with 4 availability zones, if you specify only 3 availability zonesinvar.availability_zones, this module will still reserve CIDRs for the 4th zone. This is so that if you laterwant to expand into that zone, the existing subnet CIDR assignments will not be disturbed. If you do not wantto reserve these CIDRs, setmax_subnet_count to the number of zones you are actually using.

Related Projects

Check out these related projects.

Tip

Use Terraform Reference Architectures for AWS

Use Cloud Posse's ready-to-goterraform architecture blueprints for AWS to get up and running quickly.

✅ We build it together with your team.
✅ Your team owns everything.
✅ 100% Open Source and backed by fanatical support.

Request Quote

📚Learn More

Cloud Posse is the leadingDevOps Accelerator for funded startups and enterprises.

Your team can operate like a pro today.

Ensure that your team succeeds by using Cloud Posse's proven process and turnkey blueprints. Plus, we stick around until you succeed.

Day-0: Your Foundation for Success

  • Reference Architecture. You'll get everything you need from the ground up built using 100% infrastructure as code.
  • Deployment Strategy. Adopt a proven deployment strategy with GitHub Actions, enabling automated, repeatable, and reliable software releases.
  • Site Reliability Engineering. Gain total visibility into your applications and services with Datadog, ensuring high availability and performance.
  • Security Baseline. Establish a secure environment from the start, with built-in governance, accountability, and comprehensive audit logs, safeguarding your operations.
  • GitOps. Empower your team to manage infrastructure changes confidently and efficiently through Pull Requests, leveraging the full power of GitHub Actions.

Request Quote

Day-2: Your Operational Mastery

  • Training. Equip your team with the knowledge and skills to confidently manage the infrastructure, ensuring long-term success and self-sufficiency.
  • Support. Benefit from a seamless communication over Slack with our experts, ensuring you have the support you need, whenever you need it.
  • Troubleshooting. Access expert assistance to quickly resolve any operational challenges, minimizing downtime and maintaining business continuity.
  • Code Reviews. Enhance your team’s code quality with our expert feedback, fostering continuous improvement and collaboration.
  • Bug Fixes. Rely on our team to troubleshoot and resolve any issues, ensuring your systems run smoothly.
  • Migration Assistance. Accelerate your migration process with our dedicated support, minimizing disruption and speeding up time-to-value.
  • Customer Workshops. Engage with our team in weekly workshops, gaining insights and strategies to continuously improve and innovate.

Request Quote

✨ Contributing

This project is under active development, and we encourage contributions from our community.

Many thanks to our outstanding contributors:

For 🐛 bug reports & feature requests, please use theissue tracker.

In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow.

  1. Review ourCode of Conduct andContributor Guidelines.
  2. Fork the repo on GitHub
  3. Clone the project to your own machine
  4. Commit changes to your own branch
  5. Push your work back up to your fork
  6. Submit aPull Request so that we can review your changes

NOTE: Be sure to merge the latest changes from "upstream" before making a pull request!

Running Terraform Tests

We useAtmos to streamline how Terraform tests are run. It centralizes configuration and wraps common test workflows with easy-to-use commands.

All tests are located in thetest/ folder.

Under the hood, tests are powered by Terratest together with our internalTest Helpers library, providing robust infrastructure validation.

Setup dependencies:

To run tests:

  • Run all tests:
    atmostest run
  • Clean up test artifacts:
    atmostest clean
  • Explore additional test options:
    atmostest --help

The configuration for test commands is centrally managed. To review what's being imported, see theatmos.yaml file.

Learn more about ourautomated testing in our documentation or implementingcustom commands with atmos.

🌎 Slack Community

Join ourOpen Source Community on Slack. It'sFREE for everyone! Our "SweetOps" community is where you get to talk with others who share a similar vision for how to rollout and manage infrastructure. This is the best place to talk shop, ask questions, solicit feedback, and work together as a community to build totallysweet infrastructure.

📰 Newsletter

Sign up forour newsletter and join 3,000+ DevOps engineers, CTOs, and founders who get insider access to the latest DevOps trends, so you can always stay in the know.Dropped straight into your Inbox every week — and usually a 5-minute read.

📆 Office Hours

Join us every Wednesday via Zoom for your weekly dose of insider DevOps trends, AWS news and Terraform insights, all sourced from our SweetOps community, plus alive Q&A that you can’t find anywhere else.It'sFREE for everyone!

License

License

Preamble to the Apache License, Version 2.0

Complete license is available in theLICENSE file.

Licensed to the Apache Software Foundation (ASF) under oneor more contributor license agreements.  See the NOTICE filedistributed with this work for additional informationregarding copyright ownership.  The ASF licenses this fileto you under the Apache License, Version 2.0 (the"License"); you may not use this file except in compliancewith the License.  You may obtain a copy of the License at  https://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing,software distributed under the License is distributed on an"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANYKIND, either express or implied.  See the License for thespecific language governing permissions and limitationsunder the License.

Trademarks

All other trademarks referenced herein are the property of their respective owners.


Copyright © 2017-2025Cloud Posse, LLC

README footer

Beacon

About

Terraform module for public and private subnets provisioning in existing VPC

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Contributors45


[8]ページ先頭

©2009-2025 Movatter.jp