Configure GCP VPC Subnetworks

The gcp:compute/subnetwork:Subnetwork resource, part of the Pulumi GCP provider, defines regional IP address ranges within a VPC network, controlling where VM instances and other resources draw their internal addresses. This guide focuses on four capabilities: primary and secondary IP range allocation, VPC flow logging, IPv6 configurations, and proxy-only subnets for load balancers.

Subnets belong to VPC networks that must have custom subnet mode enabled (autoCreateSubnetworks set to false). Some configurations reference InternalRange resources for coordinated IP allocation. The examples are intentionally small. Combine them with your own VPC networks and routing configuration.

Create a subnet with secondary IP ranges

Most VPC deployments subdivide the network into regional subnets with primary CIDR blocks for VM instances. Secondary ranges provide additional IP space for container pods or alias IPs.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const custom_test = new gcp.compute.Network("custom-test", {
    name: "test-network",
    autoCreateSubnetworks: false,
});
const network_with_private_secondary_ip_ranges = new gcp.compute.Subnetwork("network-with-private-secondary-ip-ranges", {
    name: "test-subnetwork",
    ipCidrRange: "10.2.0.0/16",
    region: "us-central1",
    network: custom_test.id,
    secondaryIpRanges: [{
        rangeName: "tf-test-secondary-range-update1",
        ipCidrRange: "192.168.10.0/24",
    }],
});
import pulumi
import pulumi_gcp as gcp

custom_test = gcp.compute.Network("custom-test",
    name="test-network",
    auto_create_subnetworks=False)
network_with_private_secondary_ip_ranges = gcp.compute.Subnetwork("network-with-private-secondary-ip-ranges",
    name="test-subnetwork",
    ip_cidr_range="10.2.0.0/16",
    region="us-central1",
    network=custom_test.id,
    secondary_ip_ranges=[{
        "range_name": "tf-test-secondary-range-update1",
        "ip_cidr_range": "192.168.10.0/24",
    }])
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		custom_test, err := compute.NewNetwork(ctx, "custom-test", &compute.NetworkArgs{
			Name:                  pulumi.String("test-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSubnetwork(ctx, "network-with-private-secondary-ip-ranges", &compute.SubnetworkArgs{
			Name:        pulumi.String("test-subnetwork"),
			IpCidrRange: pulumi.String("10.2.0.0/16"),
			Region:      pulumi.String("us-central1"),
			Network:     custom_test.ID(),
			SecondaryIpRanges: compute.SubnetworkSecondaryIpRangeArray{
				&compute.SubnetworkSecondaryIpRangeArgs{
					RangeName:   pulumi.String("tf-test-secondary-range-update1"),
					IpCidrRange: pulumi.String("192.168.10.0/24"),
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var custom_test = new Gcp.Compute.Network("custom-test", new()
    {
        Name = "test-network",
        AutoCreateSubnetworks = false,
    });

    var network_with_private_secondary_ip_ranges = new Gcp.Compute.Subnetwork("network-with-private-secondary-ip-ranges", new()
    {
        Name = "test-subnetwork",
        IpCidrRange = "10.2.0.0/16",
        Region = "us-central1",
        Network = custom_test.Id,
        SecondaryIpRanges = new[]
        {
            new Gcp.Compute.Inputs.SubnetworkSecondaryIpRangeArgs
            {
                RangeName = "tf-test-secondary-range-update1",
                IpCidrRange = "192.168.10.0/24",
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.compute.inputs.SubnetworkSecondaryIpRangeArgs;
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 custom_test = new Network("custom-test", NetworkArgs.builder()
            .name("test-network")
            .autoCreateSubnetworks(false)
            .build());

        var network_with_private_secondary_ip_ranges = new Subnetwork("network-with-private-secondary-ip-ranges", SubnetworkArgs.builder()
            .name("test-subnetwork")
            .ipCidrRange("10.2.0.0/16")
            .region("us-central1")
            .network(custom_test.id())
            .secondaryIpRanges(SubnetworkSecondaryIpRangeArgs.builder()
                .rangeName("tf-test-secondary-range-update1")
                .ipCidrRange("192.168.10.0/24")
                .build())
            .build());

    }
}
resources:
  network-with-private-secondary-ip-ranges:
    type: gcp:compute:Subnetwork
    properties:
      name: test-subnetwork
      ipCidrRange: 10.2.0.0/16
      region: us-central1
      network: ${["custom-test"].id}
      secondaryIpRanges:
        - rangeName: tf-test-secondary-range-update1
          ipCidrRange: 192.168.10.0/24
  custom-test:
    type: gcp:compute:Network
    properties:
      name: test-network
      autoCreateSubnetworks: false

The ipCidrRange defines the primary address space for VM instances. The secondaryIpRanges array adds named ranges for containers or alias IPs. Each secondary range needs a rangeName and its own ipCidrRange that doesn’t overlap with the primary range.

Enable VPC flow logs for traffic analysis

Network troubleshooting and security analysis require visibility into traffic patterns. VPC flow logs capture metadata about IP traffic flowing through the subnet.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const custom_test = new gcp.compute.Network("custom-test", {
    name: "log-test-network",
    autoCreateSubnetworks: false,
});
const subnet_with_logging = new gcp.compute.Subnetwork("subnet-with-logging", {
    name: "log-test-subnetwork",
    ipCidrRange: "10.2.0.0/16",
    region: "us-central1",
    network: custom_test.id,
    logConfig: {
        aggregationInterval: "INTERVAL_10_MIN",
        flowSampling: 0.5,
        metadata: "INCLUDE_ALL_METADATA",
    },
});
import pulumi
import pulumi_gcp as gcp

custom_test = gcp.compute.Network("custom-test",
    name="log-test-network",
    auto_create_subnetworks=False)
subnet_with_logging = gcp.compute.Subnetwork("subnet-with-logging",
    name="log-test-subnetwork",
    ip_cidr_range="10.2.0.0/16",
    region="us-central1",
    network=custom_test.id,
    log_config={
        "aggregation_interval": "INTERVAL_10_MIN",
        "flow_sampling": 0.5,
        "metadata": "INCLUDE_ALL_METADATA",
    })
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		custom_test, err := compute.NewNetwork(ctx, "custom-test", &compute.NetworkArgs{
			Name:                  pulumi.String("log-test-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSubnetwork(ctx, "subnet-with-logging", &compute.SubnetworkArgs{
			Name:        pulumi.String("log-test-subnetwork"),
			IpCidrRange: pulumi.String("10.2.0.0/16"),
			Region:      pulumi.String("us-central1"),
			Network:     custom_test.ID(),
			LogConfig: &compute.SubnetworkLogConfigArgs{
				AggregationInterval: pulumi.String("INTERVAL_10_MIN"),
				FlowSampling:        pulumi.Float64(0.5),
				Metadata:            pulumi.String("INCLUDE_ALL_METADATA"),
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var custom_test = new Gcp.Compute.Network("custom-test", new()
    {
        Name = "log-test-network",
        AutoCreateSubnetworks = false,
    });

    var subnet_with_logging = new Gcp.Compute.Subnetwork("subnet-with-logging", new()
    {
        Name = "log-test-subnetwork",
        IpCidrRange = "10.2.0.0/16",
        Region = "us-central1",
        Network = custom_test.Id,
        LogConfig = new Gcp.Compute.Inputs.SubnetworkLogConfigArgs
        {
            AggregationInterval = "INTERVAL_10_MIN",
            FlowSampling = 0.5,
            Metadata = "INCLUDE_ALL_METADATA",
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
import com.pulumi.gcp.compute.inputs.SubnetworkLogConfigArgs;
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 custom_test = new Network("custom-test", NetworkArgs.builder()
            .name("log-test-network")
            .autoCreateSubnetworks(false)
            .build());

        var subnet_with_logging = new Subnetwork("subnet-with-logging", SubnetworkArgs.builder()
            .name("log-test-subnetwork")
            .ipCidrRange("10.2.0.0/16")
            .region("us-central1")
            .network(custom_test.id())
            .logConfig(SubnetworkLogConfigArgs.builder()
                .aggregationInterval("INTERVAL_10_MIN")
                .flowSampling(0.5)
                .metadata("INCLUDE_ALL_METADATA")
                .build())
            .build());

    }
}
resources:
  subnet-with-logging:
    type: gcp:compute:Subnetwork
    properties:
      name: log-test-subnetwork
      ipCidrRange: 10.2.0.0/16
      region: us-central1
      network: ${["custom-test"].id}
      logConfig:
        aggregationInterval: INTERVAL_10_MIN
        flowSampling: 0.5
        metadata: INCLUDE_ALL_METADATA
  custom-test:
    type: gcp:compute:Network
    properties:
      name: log-test-network
      autoCreateSubnetworks: false

The logConfig block enables flow logging with three key settings: aggregationInterval controls how often logs are written (here, every 10 minutes), flowSampling determines what fraction of traffic to capture (0.5 means 50%), and metadata specifies whether to include all available fields. Logs export to Cloud Logging automatically.

Reserve a proxy-only subnet for load balancers

Regional internal Application Load Balancers require dedicated proxy-only subnets that don’t host VM instances. These subnets provide IP addresses for the load balancer’s Envoy proxies.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const custom_test = new gcp.compute.Network("custom-test", {
    name: "l7lb-test-network",
    autoCreateSubnetworks: false,
});
const network_for_l7lb = new gcp.compute.Subnetwork("network-for-l7lb", {
    name: "l7lb-test-subnetwork",
    ipCidrRange: "10.0.0.0/22",
    region: "us-central1",
    purpose: "REGIONAL_MANAGED_PROXY",
    role: "ACTIVE",
    network: custom_test.id,
});
import pulumi
import pulumi_gcp as gcp

custom_test = gcp.compute.Network("custom-test",
    name="l7lb-test-network",
    auto_create_subnetworks=False)
network_for_l7lb = gcp.compute.Subnetwork("network-for-l7lb",
    name="l7lb-test-subnetwork",
    ip_cidr_range="10.0.0.0/22",
    region="us-central1",
    purpose="REGIONAL_MANAGED_PROXY",
    role="ACTIVE",
    network=custom_test.id)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		custom_test, err := compute.NewNetwork(ctx, "custom-test", &compute.NetworkArgs{
			Name:                  pulumi.String("l7lb-test-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSubnetwork(ctx, "network-for-l7lb", &compute.SubnetworkArgs{
			Name:        pulumi.String("l7lb-test-subnetwork"),
			IpCidrRange: pulumi.String("10.0.0.0/22"),
			Region:      pulumi.String("us-central1"),
			Purpose:     pulumi.String("REGIONAL_MANAGED_PROXY"),
			Role:        pulumi.String("ACTIVE"),
			Network:     custom_test.ID(),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var custom_test = new Gcp.Compute.Network("custom-test", new()
    {
        Name = "l7lb-test-network",
        AutoCreateSubnetworks = false,
    });

    var network_for_l7lb = new Gcp.Compute.Subnetwork("network-for-l7lb", new()
    {
        Name = "l7lb-test-subnetwork",
        IpCidrRange = "10.0.0.0/22",
        Region = "us-central1",
        Purpose = "REGIONAL_MANAGED_PROXY",
        Role = "ACTIVE",
        Network = custom_test.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
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 custom_test = new Network("custom-test", NetworkArgs.builder()
            .name("l7lb-test-network")
            .autoCreateSubnetworks(false)
            .build());

        var network_for_l7lb = new Subnetwork("network-for-l7lb", SubnetworkArgs.builder()
            .name("l7lb-test-subnetwork")
            .ipCidrRange("10.0.0.0/22")
            .region("us-central1")
            .purpose("REGIONAL_MANAGED_PROXY")
            .role("ACTIVE")
            .network(custom_test.id())
            .build());

    }
}
resources:
  network-for-l7lb:
    type: gcp:compute:Subnetwork
    properties:
      name: l7lb-test-subnetwork
      ipCidrRange: 10.0.0.0/22
      region: us-central1
      purpose: REGIONAL_MANAGED_PROXY
      role: ACTIVE
      network: ${["custom-test"].id}
  custom-test:
    type: gcp:compute:Network
    properties:
      name: l7lb-test-network
      autoCreateSubnetworks: false

Setting purpose to REGIONAL_MANAGED_PROXY marks this subnet as reserved for load balancer infrastructure. The role property (ACTIVE or BACKUP) controls whether the subnet is actively serving traffic or ready for failover. These subnets cannot host VM instances.

Enable external IPv6 connectivity

Applications that need to communicate with IPv6 clients on the internet require dual-stack subnets with globally routable addresses.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const custom_test = new gcp.compute.Network("custom-test", {
    name: "ipv6-test-network",
    autoCreateSubnetworks: false,
});
const subnetwork_ipv6 = new gcp.compute.Subnetwork("subnetwork-ipv6", {
    name: "ipv6-test-subnetwork",
    ipCidrRange: "10.0.0.0/22",
    region: "us-west2",
    stackType: "IPV4_IPV6",
    ipv6AccessType: "EXTERNAL",
    network: custom_test.id,
});
import pulumi
import pulumi_gcp as gcp

custom_test = gcp.compute.Network("custom-test",
    name="ipv6-test-network",
    auto_create_subnetworks=False)
subnetwork_ipv6 = gcp.compute.Subnetwork("subnetwork-ipv6",
    name="ipv6-test-subnetwork",
    ip_cidr_range="10.0.0.0/22",
    region="us-west2",
    stack_type="IPV4_IPV6",
    ipv6_access_type="EXTERNAL",
    network=custom_test.id)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		custom_test, err := compute.NewNetwork(ctx, "custom-test", &compute.NetworkArgs{
			Name:                  pulumi.String("ipv6-test-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSubnetwork(ctx, "subnetwork-ipv6", &compute.SubnetworkArgs{
			Name:           pulumi.String("ipv6-test-subnetwork"),
			IpCidrRange:    pulumi.String("10.0.0.0/22"),
			Region:         pulumi.String("us-west2"),
			StackType:      pulumi.String("IPV4_IPV6"),
			Ipv6AccessType: pulumi.String("EXTERNAL"),
			Network:        custom_test.ID(),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var custom_test = new Gcp.Compute.Network("custom-test", new()
    {
        Name = "ipv6-test-network",
        AutoCreateSubnetworks = false,
    });

    var subnetwork_ipv6 = new Gcp.Compute.Subnetwork("subnetwork-ipv6", new()
    {
        Name = "ipv6-test-subnetwork",
        IpCidrRange = "10.0.0.0/22",
        Region = "us-west2",
        StackType = "IPV4_IPV6",
        Ipv6AccessType = "EXTERNAL",
        Network = custom_test.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
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 custom_test = new Network("custom-test", NetworkArgs.builder()
            .name("ipv6-test-network")
            .autoCreateSubnetworks(false)
            .build());

        var subnetwork_ipv6 = new Subnetwork("subnetwork-ipv6", SubnetworkArgs.builder()
            .name("ipv6-test-subnetwork")
            .ipCidrRange("10.0.0.0/22")
            .region("us-west2")
            .stackType("IPV4_IPV6")
            .ipv6AccessType("EXTERNAL")
            .network(custom_test.id())
            .build());

    }
}
resources:
  subnetwork-ipv6:
    type: gcp:compute:Subnetwork
    properties:
      name: ipv6-test-subnetwork
      ipCidrRange: 10.0.0.0/22
      region: us-west2
      stackType: IPV4_IPV6
      ipv6AccessType: EXTERNAL
      network: ${["custom-test"].id}
  custom-test:
    type: gcp:compute:Network
    properties:
      name: ipv6-test-network
      autoCreateSubnetworks: false

The stackType property set to IPV4_IPV6 enables dual-stack networking. The ipv6AccessType property controls whether IPv6 addresses are EXTERNAL (internet-routable) or INTERNAL (VPC-only). External IPv6 addresses allow direct communication with IPv6 clients on the internet.

Configure internal IPv6 for VPC communication

Some workloads need IPv6 for internal VPC communication without exposing addresses to the internet. Internal IPv6 uses Unique Local Addresses (ULA) that remain private.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const custom_test = new gcp.compute.Network("custom-test", {
    name: "internal-ipv6-test-network",
    autoCreateSubnetworks: false,
    enableUlaInternalIpv6: true,
});
const subnetwork_internal_ipv6 = new gcp.compute.Subnetwork("subnetwork-internal-ipv6", {
    name: "internal-ipv6-test-subnetwork",
    ipCidrRange: "10.0.0.0/22",
    region: "us-west2",
    stackType: "IPV4_IPV6",
    ipv6AccessType: "INTERNAL",
    network: custom_test.id,
});
import pulumi
import pulumi_gcp as gcp

custom_test = gcp.compute.Network("custom-test",
    name="internal-ipv6-test-network",
    auto_create_subnetworks=False,
    enable_ula_internal_ipv6=True)
subnetwork_internal_ipv6 = gcp.compute.Subnetwork("subnetwork-internal-ipv6",
    name="internal-ipv6-test-subnetwork",
    ip_cidr_range="10.0.0.0/22",
    region="us-west2",
    stack_type="IPV4_IPV6",
    ipv6_access_type="INTERNAL",
    network=custom_test.id)
package main

import (
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		custom_test, err := compute.NewNetwork(ctx, "custom-test", &compute.NetworkArgs{
			Name:                  pulumi.String("internal-ipv6-test-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
			EnableUlaInternalIpv6: pulumi.Bool(true),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSubnetwork(ctx, "subnetwork-internal-ipv6", &compute.SubnetworkArgs{
			Name:           pulumi.String("internal-ipv6-test-subnetwork"),
			IpCidrRange:    pulumi.String("10.0.0.0/22"),
			Region:         pulumi.String("us-west2"),
			StackType:      pulumi.String("IPV4_IPV6"),
			Ipv6AccessType: pulumi.String("INTERNAL"),
			Network:        custom_test.ID(),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var custom_test = new Gcp.Compute.Network("custom-test", new()
    {
        Name = "internal-ipv6-test-network",
        AutoCreateSubnetworks = false,
        EnableUlaInternalIpv6 = true,
    });

    var subnetwork_internal_ipv6 = new Gcp.Compute.Subnetwork("subnetwork-internal-ipv6", new()
    {
        Name = "internal-ipv6-test-subnetwork",
        IpCidrRange = "10.0.0.0/22",
        Region = "us-west2",
        StackType = "IPV4_IPV6",
        Ipv6AccessType = "INTERNAL",
        Network = custom_test.Id,
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
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 custom_test = new Network("custom-test", NetworkArgs.builder()
            .name("internal-ipv6-test-network")
            .autoCreateSubnetworks(false)
            .enableUlaInternalIpv6(true)
            .build());

        var subnetwork_internal_ipv6 = new Subnetwork("subnetwork-internal-ipv6", SubnetworkArgs.builder()
            .name("internal-ipv6-test-subnetwork")
            .ipCidrRange("10.0.0.0/22")
            .region("us-west2")
            .stackType("IPV4_IPV6")
            .ipv6AccessType("INTERNAL")
            .network(custom_test.id())
            .build());

    }
}
resources:
  subnetwork-internal-ipv6:
    type: gcp:compute:Subnetwork
    properties:
      name: internal-ipv6-test-subnetwork
      ipCidrRange: 10.0.0.0/22
      region: us-west2
      stackType: IPV4_IPV6
      ipv6AccessType: INTERNAL
      network: ${["custom-test"].id}
  custom-test:
    type: gcp:compute:Network
    properties:
      name: internal-ipv6-test-network
      autoCreateSubnetworks: false
      enableUlaInternalIpv6: true

Internal IPv6 requires the parent network to have enableUlaInternalIpv6 set to true at creation time. Setting ipv6AccessType to INTERNAL allocates ULA addresses that work within the VPC but aren’t routable on the internet. This configuration is useful for IPv6-native applications that don’t need external connectivity.

Allocate IP ranges from reserved address pools

Organizations managing multiple VPCs or planning network migrations often pre-reserve IP ranges to prevent conflicts. Reserved internal ranges let you allocate subnet addresses from a centrally managed pool.

import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";

const _default = new gcp.compute.Network("default", {
    name: "network-reserved-internal-range",
    autoCreateSubnetworks: false,
});
const reserved = new gcp.networkconnectivity.InternalRange("reserved", {
    name: "reserved",
    network: _default.id,
    usage: "FOR_VPC",
    peering: "FOR_SELF",
    prefixLength: 24,
    targetCidrRanges: ["10.0.0.0/8"],
});
const subnetwork_reserved_internal_range = new gcp.compute.Subnetwork("subnetwork-reserved-internal-range", {
    name: "subnetwork-reserved-internal-range",
    region: "us-central1",
    network: _default.id,
    reservedInternalRange: pulumi.interpolate`networkconnectivity.googleapis.com/${reserved.id}`,
});
import pulumi
import pulumi_gcp as gcp

default = gcp.compute.Network("default",
    name="network-reserved-internal-range",
    auto_create_subnetworks=False)
reserved = gcp.networkconnectivity.InternalRange("reserved",
    name="reserved",
    network=default.id,
    usage="FOR_VPC",
    peering="FOR_SELF",
    prefix_length=24,
    target_cidr_ranges=["10.0.0.0/8"])
subnetwork_reserved_internal_range = gcp.compute.Subnetwork("subnetwork-reserved-internal-range",
    name="subnetwork-reserved-internal-range",
    region="us-central1",
    network=default.id,
    reserved_internal_range=reserved.id.apply(lambda id: f"networkconnectivity.googleapis.com/{id}"))
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/networkconnectivity"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_default, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
			Name:                  pulumi.String("network-reserved-internal-range"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		reserved, err := networkconnectivity.NewInternalRange(ctx, "reserved", &networkconnectivity.InternalRangeArgs{
			Name:         pulumi.String("reserved"),
			Network:      _default.ID(),
			Usage:        pulumi.String("FOR_VPC"),
			Peering:      pulumi.String("FOR_SELF"),
			PrefixLength: pulumi.Int(24),
			TargetCidrRanges: pulumi.StringArray{
				pulumi.String("10.0.0.0/8"),
			},
		})
		if err != nil {
			return err
		}
		_, err = compute.NewSubnetwork(ctx, "subnetwork-reserved-internal-range", &compute.SubnetworkArgs{
			Name:    pulumi.String("subnetwork-reserved-internal-range"),
			Region:  pulumi.String("us-central1"),
			Network: _default.ID(),
			ReservedInternalRange: reserved.ID().ApplyT(func(id string) (string, error) {
				return fmt.Sprintf("networkconnectivity.googleapis.com/%v", id), nil
			}).(pulumi.StringOutput),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Gcp = Pulumi.Gcp;

return await Deployment.RunAsync(() => 
{
    var @default = new Gcp.Compute.Network("default", new()
    {
        Name = "network-reserved-internal-range",
        AutoCreateSubnetworks = false,
    });

    var reserved = new Gcp.NetworkConnectivity.InternalRange("reserved", new()
    {
        Name = "reserved",
        Network = @default.Id,
        Usage = "FOR_VPC",
        Peering = "FOR_SELF",
        PrefixLength = 24,
        TargetCidrRanges = new[]
        {
            "10.0.0.0/8",
        },
    });

    var subnetwork_reserved_internal_range = new Gcp.Compute.Subnetwork("subnetwork-reserved-internal-range", new()
    {
        Name = "subnetwork-reserved-internal-range",
        Region = "us-central1",
        Network = @default.Id,
        ReservedInternalRange = reserved.Id.Apply(id => $"networkconnectivity.googleapis.com/{id}"),
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Network;
import com.pulumi.gcp.compute.NetworkArgs;
import com.pulumi.gcp.networkconnectivity.InternalRange;
import com.pulumi.gcp.networkconnectivity.InternalRangeArgs;
import com.pulumi.gcp.compute.Subnetwork;
import com.pulumi.gcp.compute.SubnetworkArgs;
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 default_ = new Network("default", NetworkArgs.builder()
            .name("network-reserved-internal-range")
            .autoCreateSubnetworks(false)
            .build());

        var reserved = new InternalRange("reserved", InternalRangeArgs.builder()
            .name("reserved")
            .network(default_.id())
            .usage("FOR_VPC")
            .peering("FOR_SELF")
            .prefixLength(24)
            .targetCidrRanges("10.0.0.0/8")
            .build());

        var subnetwork_reserved_internal_range = new Subnetwork("subnetwork-reserved-internal-range", SubnetworkArgs.builder()
            .name("subnetwork-reserved-internal-range")
            .region("us-central1")
            .network(default_.id())
            .reservedInternalRange(reserved.id().applyValue(_id -> String.format("networkconnectivity.googleapis.com/%s", _id)))
            .build());

    }
}
resources:
  subnetwork-reserved-internal-range:
    type: gcp:compute:Subnetwork
    properties:
      name: subnetwork-reserved-internal-range
      region: us-central1
      network: ${default.id}
      reservedInternalRange: networkconnectivity.googleapis.com/${reserved.id}
  default:
    type: gcp:compute:Network
    properties:
      name: network-reserved-internal-range
      autoCreateSubnetworks: false
  reserved:
    type: gcp:networkconnectivity:InternalRange
    properties:
      name: reserved
      network: ${default.id}
      usage: FOR_VPC
      peering: FOR_SELF
      prefixLength: 24
      targetCidrRanges:
        - 10.0.0.0/8

The reservedInternalRange property references an InternalRange resource using the networkconnectivity.googleapis.com API format. When specified, you can omit ipCidrRange; the subnet draws its address space from the reserved pool. This approach coordinates IP allocation across VPCs or during network migrations.

Beyond these examples

These snippets focus on specific subnet-level features: primary and secondary IP range allocation, VPC flow logging and traffic analysis, IPv4/IPv6 dual-stack and IPv6-only configurations, and proxy-only subnets for load balancers. They’re intentionally minimal rather than full network architectures.

The examples reference pre-existing infrastructure such as VPC networks (which must have autoCreateSubnetworks disabled) and InternalRange resources for reserved IP allocation. They focus on configuring the subnet rather than provisioning the entire network.

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

  • Private Google Access configuration (privateIpGoogleAccess)
  • CIDR overlap handling (allowSubnetCidrRoutesOverlap)
  • Private NAT gateway subnets (purpose PRIVATE_NAT)
  • Subnet mask resolution modes (resolveSubnetMask)

These omissions are intentional: the goal is to illustrate how each subnet feature is wired, not provide drop-in network modules. See the Subnetwork resource reference for all available configuration options.

Let's configure GCP VPC Subnetworks

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Immutability & Resource Lifecycle
What properties can't I change after creating a subnetwork?
Many properties are immutable and require resource replacement if changed: name, network, region, description, externalIpv6Prefix, internalIpv6Prefix, ipCollection, params, reservedInternalRange, and resolveSubnetMask.
When can I set the IPv6 access type?
The ipv6AccessType property is immutable and can only be specified during creation or the first time the subnet is updated into IPV4_IPV6 dual stack.
IPv6 Configuration
How do I enable IPv6 on a subnetwork?
Set stackType to IPV4_IPV6 for dual stack or IPV6_ONLY for IPv6-only. Configure ipv6AccessType as either EXTERNAL or INTERNAL. For internal IPv6, the parent network must have enableUlaInternalIpv6 set to true.
What's the difference between EXTERNAL and INTERNAL IPv6 access?
EXTERNAL provides public IPv6 connectivity but prevents enabling direct path. INTERNAL provides private IPv6 connectivity and requires the network to have ULA internal IPv6 enabled.
Why can't I enable direct path on my IPv6 subnet?
Direct path cannot be enabled when ipv6AccessType is set to EXTERNAL. Use INTERNAL access type if direct path is required.
Subnet Purpose & Load Balancers
What subnet purposes are available and when should I use them?
Available purposes: PRIVATE (default for general use), REGIONAL_MANAGED_PROXY (preferred for regional Envoy load balancers), GLOBAL_MANAGED_PROXY (for cross-regional Envoy load balancers), PRIVATE_SERVICE_CONNECT (for published services), PEER_MIGRATION (for resource migration), and PRIVATE_NAT (for NAT gateways).
How do I configure a subnet for regional load balancers?
Set purpose to REGIONAL_MANAGED_PROXY and role to ACTIVE for active use or BACKUP for standby. The role property only applies when purpose is REGIONAL_MANAGED_PROXY.
Why can't I enable VPC flow logging on my subnet?
Flow logging isn’t supported when the subnet purpose is set to REGIONAL_MANAGED_PROXY or GLOBAL_MANAGED_PROXY.
IP Ranges & Addressing
Can I use reserved internal ranges instead of specifying CIDR blocks?
Yes, set reservedInternalRange to a networkconnectivity.InternalRange resource ID (prefixed with networkconnectivity.googleapis.com). When using reserved ranges, ipCidrRange becomes optional.
How do I configure secondary IP ranges?
Use the secondaryIpRanges array with either explicit ipCidrRange values or reservedInternalRange references. Secondary ranges are used for VM alias IPs.
What does sendSecondaryIpRangeIfEmpty control?
It controls removal behavior of secondaryIpRange. When false (default), removing secondary ranges from config won’t produce a diff. When true, removal sends an empty list to the API.
Can I create subnets with overlapping CIDR ranges?
Yes, set allowSubnetCidrRoutesOverlap to true. This allows packets to match dynamic BGP routes even when destinations match existing subnet ranges.
Network Requirements & Configuration
What type of VPC network is required for subnetworks?
The network must be in distributed mode with autoCreateSubnetworks set to false. Only distributed mode networks can have user-created subnetworks.
How do I enable VPC flow logging?
Configure logConfig with aggregationInterval, flowSampling, and metadata settings. Note that flow logging isn’t supported for proxy subnets (REGIONAL_MANAGED_PROXY or GLOBAL_MANAGED_PROXY purpose).

Using a different cloud?

Explore networking guides for other cloud providers: