Create and Configure AWS VPCs

The aws:ec2/vpc:Vpc resource, part of the Pulumi AWS provider, provisions a VPC (Virtual Private Cloud) as the foundational network container for AWS resources. This guide focuses on two capabilities: CIDR block allocation (manual and IPAM-based), and tagging with instance tenancy control.

A VPC doesn’t create a complete network on its own. Real workloads require subnets, route tables, internet gateways, and NAT gateways configured as companion resources. The examples are intentionally small and demonstrate VPC-level configuration only. You’ll work with CIDR block sizing, instance tenancy (default vs. dedicated hardware), and AWS IPAM for organizations managing IP addresses at scale.

Create a VPC with a CIDR block

Most deployments start by defining the private IP address space for cloud resources. The VPC is a logical container; subnets and routing come next.

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 defines the IPv4 address range for the VPC in CIDR notation. Common choices are /16 (65,536 addresses) or /20 (4,096 addresses). The VPC doesn’t contain instances or subnets by itself; those are added via separate resources.

Add tags and instance tenancy options

Teams label VPCs for cost tracking or enforce dedicated hardware for compliance requirements.

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 tags property adds key-value metadata visible in the AWS console and cost reports. The instanceTenancy property controls whether EC2 instances launched in this VPC run on shared hardware (default) or dedicated hardware (dedicated). Dedicated tenancy incurs a $2/hour regional fee plus per-instance charges.

Allocate CIDR blocks from AWS IPAM pools

Organizations managing IP addresses across multiple VPCs use AWS IPAM to prevent overlaps and centralize allocation.

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 hardcoding a cidrBlock, you reference an ipv4IpamPoolId and specify ipv4NetmaskLength to request a block of that size. IPAM assigns a non-overlapping CIDR from the pool. The dependsOn ensures the VPC waits for the pool’s CIDR allocation to complete before requesting its own block.

Beyond These Examples

These snippets focus on specific VPC-level features: basic VPC creation with CIDR blocks, tagging and instance tenancy control, and AWS IPAM integration for centralized IP management. They’re intentionally minimal rather than full network deployments.

The examples may reference pre-existing infrastructure such as AWS region configuration in the provider, and IPAM pools for automated CIDR allocation (Example 3 only). They focus on VPC configuration rather than provisioning companion resources like subnets or route tables.

To keep things focused, common VPC patterns are omitted, including:

  • DNS resolution configuration (enableDnsSupport, enableDnsHostnames)
  • IPv6 CIDR blocks and dual-stack networking
  • 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.

Frequently Asked Questions

CIDR & IP Address Management
What VPC properties can't be changed after creation?
The cidrBlock, ipv4IpamPoolId, and ipv4NetmaskLength properties are immutable and require VPC replacement if changed.
What are my IPv6 CIDR options and their conflicts?
You can either use assignGeneratedIpv6CidrBlock for an Amazon-provided /56 block, or ipv6IpamPoolId for IPAM-managed allocation; these two options conflict. Additionally, ipv6NetmaskLength conflicts with explicitly setting ipv6CidrBlock.
How do I allocate a VPC CIDR from AWS IPAM?
Set ipv4IpamPoolId to your IPAM pool ID and specify ipv4NetmaskLength for the desired prefix length. Add dependsOn referencing the IPAM pool CIDR allocation to ensure proper creation order.
What's the valid range for IPv6 netmask length?
Valid values are 44 to 60 in increments of 4 (44, 48, 52, 56, 60).
DNS Configuration
What's the difference between the DNS settings?
enableDnsSupport controls DNS resolution in the VPC (defaults to true), while enableDnsHostnames controls whether instances receive public DNS hostnames (defaults to false).
Instance Tenancy & Costs
What does dedicated instance tenancy cost?
Setting instanceTenancy to dedicated incurs a $2 per hour per-region fee, plus an hourly per-instance usage fee. The default default tenancy allows you to control tenancy at the instance level.
Default Resources
What default resources are created with a VPC?
AWS automatically creates a default network ACL, route table, and security group. Their IDs are available as defaultNetworkAclId, defaultRouteTableId, and defaultSecurityGroupId outputs.

Ready to get started?

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Create free account