The aws:ec2/vpc:Vpc resource, part of the Pulumi AWS provider, provisions the VPC network container itself: its IPv4 address space, tenancy rules, and DNS behavior. This guide focuses on three capabilities: IPv4 CIDR allocation (explicit and IPAM-managed), instance tenancy configuration, and organizational tagging.
VPCs are the foundation for AWS networking. Subnets, route tables, security groups, and internet gateways are configured through companion resources. The examples are intentionally small. Combine them with your own subnet layout and routing configuration.
Create a VPC with a CIDR block
Most AWS deployments begin by defining a VPC with an IPv4 CIDR block that determines the private IP address range available for resources.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const main = new aws.ec2.Vpc("main", {cidrBlock: "10.0.0.0/16"});
import pulumi
import pulumi_aws as aws
main = aws.ec2.Vpc("main", cidr_block="10.0.0.0/16")
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ec2.NewVpc(ctx, "main", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.0.0.0/16"),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var main = new Aws.Ec2.Vpc("main", new()
{
CidrBlock = "10.0.0.0/16",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var main = new Vpc("main", VpcArgs.builder()
.cidrBlock("10.0.0.0/16")
.build());
}
}
resources:
main:
type: aws:ec2:Vpc
properties:
cidrBlock: 10.0.0.0/16
The cidrBlock property sets the IPv4 address range using CIDR notation. This example uses 10.0.0.0/16, which provides 65,536 private IP addresses. Choose a CIDR block that doesn’t overlap with other networks you’ll connect to via VPN or peering.
Add instance tenancy and organizational tags
Teams often need to control where EC2 instances run and apply tags for cost tracking and resource organization.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const main = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
instanceTenancy: "default",
tags: {
Name: "main",
},
});
import pulumi
import pulumi_aws as aws
main = aws.ec2.Vpc("main",
cidr_block="10.0.0.0/16",
instance_tenancy="default",
tags={
"Name": "main",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := ec2.NewVpc(ctx, "main", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.0.0.0/16"),
InstanceTenancy: pulumi.String("default"),
Tags: pulumi.StringMap{
"Name": pulumi.String("main"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var main = new Aws.Ec2.Vpc("main", new()
{
CidrBlock = "10.0.0.0/16",
InstanceTenancy = "default",
Tags =
{
{ "Name", "main" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var main = new Vpc("main", VpcArgs.builder()
.cidrBlock("10.0.0.0/16")
.instanceTenancy("default")
.tags(Map.of("Name", "main"))
.build());
}
}
resources:
main:
type: aws:ec2:Vpc
properties:
cidrBlock: 10.0.0.0/16
instanceTenancy: default
tags:
Name: main
The instanceTenancy property controls hardware isolation. Setting it to “default” allows instances to use their own tenancy attribute; “dedicated” forces all instances onto dedicated hardware (incurs additional per-hour fees). The tags property adds key-value metadata for organization and cost allocation.
Allocate CIDR from AWS IPAM pools
Organizations managing IP addresses across multiple accounts and regions use IPAM to centralize allocation and prevent overlapping ranges.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const current = aws.getRegion({});
const test = new aws.ec2.VpcIpam("test", {operatingRegions: [{
regionName: current.then(current => current.region),
}]});
const testVpcIpamPool = new aws.ec2.VpcIpamPool("test", {
addressFamily: "ipv4",
ipamScopeId: test.privateDefaultScopeId,
locale: current.then(current => current.region),
});
const testVpcIpamPoolCidr = new aws.ec2.VpcIpamPoolCidr("test", {
ipamPoolId: testVpcIpamPool.id,
cidr: "172.20.0.0/16",
});
const testVpc = new aws.ec2.Vpc("test", {
ipv4IpamPoolId: testVpcIpamPool.id,
ipv4NetmaskLength: 28,
}, {
dependsOn: [testVpcIpamPoolCidr],
});
import pulumi
import pulumi_aws as aws
current = aws.get_region()
test = aws.ec2.VpcIpam("test", operating_regions=[{
"region_name": current.region,
}])
test_vpc_ipam_pool = aws.ec2.VpcIpamPool("test",
address_family="ipv4",
ipam_scope_id=test.private_default_scope_id,
locale=current.region)
test_vpc_ipam_pool_cidr = aws.ec2.VpcIpamPoolCidr("test",
ipam_pool_id=test_vpc_ipam_pool.id,
cidr="172.20.0.0/16")
test_vpc = aws.ec2.Vpc("test",
ipv4_ipam_pool_id=test_vpc_ipam_pool.id,
ipv4_netmask_length=28,
opts = pulumi.ResourceOptions(depends_on=[test_vpc_ipam_pool_cidr]))
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
current, err := aws.GetRegion(ctx, &aws.GetRegionArgs{}, nil)
if err != nil {
return err
}
test, err := ec2.NewVpcIpam(ctx, "test", &ec2.VpcIpamArgs{
OperatingRegions: ec2.VpcIpamOperatingRegionArray{
&ec2.VpcIpamOperatingRegionArgs{
RegionName: pulumi.String(current.Region),
},
},
})
if err != nil {
return err
}
testVpcIpamPool, err := ec2.NewVpcIpamPool(ctx, "test", &ec2.VpcIpamPoolArgs{
AddressFamily: pulumi.String("ipv4"),
IpamScopeId: test.PrivateDefaultScopeId,
Locale: pulumi.String(current.Region),
})
if err != nil {
return err
}
testVpcIpamPoolCidr, err := ec2.NewVpcIpamPoolCidr(ctx, "test", &ec2.VpcIpamPoolCidrArgs{
IpamPoolId: testVpcIpamPool.ID(),
Cidr: pulumi.String("172.20.0.0/16"),
})
if err != nil {
return err
}
_, err = ec2.NewVpc(ctx, "test", &ec2.VpcArgs{
Ipv4IpamPoolId: testVpcIpamPool.ID(),
Ipv4NetmaskLength: pulumi.Int(28),
}, pulumi.DependsOn([]pulumi.Resource{
testVpcIpamPoolCidr,
}))
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var current = Aws.GetRegion.Invoke();
var test = new Aws.Ec2.VpcIpam("test", new()
{
OperatingRegions = new[]
{
new Aws.Ec2.Inputs.VpcIpamOperatingRegionArgs
{
RegionName = current.Apply(getRegionResult => getRegionResult.Region),
},
},
});
var testVpcIpamPool = new Aws.Ec2.VpcIpamPool("test", new()
{
AddressFamily = "ipv4",
IpamScopeId = test.PrivateDefaultScopeId,
Locale = current.Apply(getRegionResult => getRegionResult.Region),
});
var testVpcIpamPoolCidr = new Aws.Ec2.VpcIpamPoolCidr("test", new()
{
IpamPoolId = testVpcIpamPool.Id,
Cidr = "172.20.0.0/16",
});
var testVpc = new Aws.Ec2.Vpc("test", new()
{
Ipv4IpamPoolId = testVpcIpamPool.Id,
Ipv4NetmaskLength = 28,
}, new CustomResourceOptions
{
DependsOn =
{
testVpcIpamPoolCidr,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.AwsFunctions;
import com.pulumi.aws.inputs.GetRegionArgs;
import com.pulumi.aws.ec2.VpcIpam;
import com.pulumi.aws.ec2.VpcIpamArgs;
import com.pulumi.aws.ec2.inputs.VpcIpamOperatingRegionArgs;
import com.pulumi.aws.ec2.VpcIpamPool;
import com.pulumi.aws.ec2.VpcIpamPoolArgs;
import com.pulumi.aws.ec2.VpcIpamPoolCidr;
import com.pulumi.aws.ec2.VpcIpamPoolCidrArgs;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.resources.CustomResourceOptions;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
final var current = AwsFunctions.getRegion(GetRegionArgs.builder()
.build());
var test = new VpcIpam("test", VpcIpamArgs.builder()
.operatingRegions(VpcIpamOperatingRegionArgs.builder()
.regionName(current.region())
.build())
.build());
var testVpcIpamPool = new VpcIpamPool("testVpcIpamPool", VpcIpamPoolArgs.builder()
.addressFamily("ipv4")
.ipamScopeId(test.privateDefaultScopeId())
.locale(current.region())
.build());
var testVpcIpamPoolCidr = new VpcIpamPoolCidr("testVpcIpamPoolCidr", VpcIpamPoolCidrArgs.builder()
.ipamPoolId(testVpcIpamPool.id())
.cidr("172.20.0.0/16")
.build());
var testVpc = new Vpc("testVpc", VpcArgs.builder()
.ipv4IpamPoolId(testVpcIpamPool.id())
.ipv4NetmaskLength(28)
.build(), CustomResourceOptions.builder()
.dependsOn(testVpcIpamPoolCidr)
.build());
}
}
resources:
test:
type: aws:ec2:VpcIpam
properties:
operatingRegions:
- regionName: ${current.region}
testVpcIpamPool:
type: aws:ec2:VpcIpamPool
name: test
properties:
addressFamily: ipv4
ipamScopeId: ${test.privateDefaultScopeId}
locale: ${current.region}
testVpcIpamPoolCidr:
type: aws:ec2:VpcIpamPoolCidr
name: test
properties:
ipamPoolId: ${testVpcIpamPool.id}
cidr: 172.20.0.0/16
testVpc:
type: aws:ec2:Vpc
name: test
properties:
ipv4IpamPoolId: ${testVpcIpamPool.id}
ipv4NetmaskLength: 28
options:
dependsOn:
- ${testVpcIpamPoolCidr}
variables:
current:
fn::invoke:
function: aws:getRegion
arguments: {}
Instead of specifying cidrBlock directly, this configuration uses ipv4IpamPoolId to reference an IPAM pool and ipv4NetmaskLength to request a /28 subnet from that pool. IPAM automatically assigns a non-overlapping CIDR block. The dependsOn ensures the pool CIDR exists before VPC creation.
Beyond these examples
These snippets focus on specific VPC-level features: IPv4 CIDR allocation (explicit and IPAM-managed) and instance tenancy and tagging. They’re intentionally minimal rather than full network architectures.
The IPAM example requires pre-existing infrastructure such as AWS IPAM configuration with operating regions and pool CIDRs. The examples focus on VPC configuration rather than provisioning subnets, route tables, or gateways.
To keep things focused, common VPC patterns are omitted, including:
- DNS resolution settings (enableDnsSupport, enableDnsHostnames)
- IPv6 CIDR blocks and dual-stack configuration
- Network address usage metrics (enableNetworkAddressUsageMetrics)
These omissions are intentional: the goal is to illustrate how each VPC feature is wired, not provide drop-in network modules. See the VPC resource reference for all available configuration options.
Let's create and Configure AWS VPCs
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
CIDR & IP Address Management
cidrBlock, ipv4IpamPoolId, and ipv4NetmaskLength properties are immutable and require VPC replacement if changed.ipv4IpamPoolId to your IPAM pool ID and specify ipv4NetmaskLength for the desired CIDR size. Add dependsOn to ensure the IPAM pool CIDR is allocated before VPC creation.cidrBlock explicitly (e.g., “10.0.0.0/16”) or derive it from IPAM using ipv4IpamPoolId with ipv4NetmaskLength.IPv6 Configuration
assignGeneratedIpv6CidrBlock for an Amazon-provided /56 CIDR block, or use ipv6IpamPoolId for IPAM-managed allocation. These options conflict and cannot be used together.assignGeneratedIpv6CidrBlock conflicts with ipv6IpamPoolId, and ipv6NetmaskLength conflicts with ipv6CidrBlock. Choose one approach per conflict pair.ipv6NetmaskLength are 44 to 60 in increments of 4 (44, 48, 52, 56, 60).DNS & Instance Configuration
enableDnsHostnames to true to enable hostname resolution for instances in the VPC.enableDnsSupport (defaults to true) enables DNS resolution, while enableDnsHostnames (defaults to false) enables DNS hostnames for instances. Enable both for full DNS functionality.instanceTenancy to dedicated incurs a $2 per hour per region fee, plus an hourly per-instance usage fee. Use default tenancy unless dedicated instances are required.Default Resources & Management
defaultNetworkAclId), route table (defaultRouteTableId), and security group (defaultSecurityGroupId) when you create a VPC.aws.ec2.MainRouteTableAssociation to associate a different route table as the VPC’s main route table.