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 examples reference InternalRange resources for centralized IP management. 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, plus secondary ranges for GKE 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 additional CIDR blocks that can be used for container pods or alias IP addresses. Each secondary range needs a unique rangeName for reference by other resources like GKE clusters.
Enable VPC flow logs for traffic analysis
Teams monitoring network traffic or troubleshooting connectivity enable VPC flow logs to capture metadata about IP flows.
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 controls flow log behavior. The aggregationInterval sets how frequently logs are written (here, every 10 minutes). The flowSampling value (0.5) captures 50% of traffic to balance detail with cost. Setting metadata to INCLUDE_ALL_METADATA captures full packet headers for detailed analysis.
Reserve a proxy-only subnet for load balancers
Regional internal Application Load Balancers require dedicated proxy-only subnets that provide IP space for Envoy proxies without hosting VM instances.
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; they exist solely to provide IP addresses for load balancer proxies.
Enable dual-stack networking with external IPv6
Applications that need IPv6 connectivity to the internet configure dual-stack subnets that assign both IPv4 and IPv6 addresses to instances.
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 mode. The ipv6AccessType value EXTERNAL allows instances to receive public IPv6 addresses and communicate with IPv6 clients on the internet. Instances in this subnet get both an IPv4 address from ipCidrRange and an IPv6 address automatically.
Configure internal IPv6 for VPC-only communication
Some workloads need IPv6 addressing for internal communication without exposing addresses to the internet.
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
Setting ipv6AccessType to INTERNAL restricts IPv6 addresses to VPC-internal communication. The parent network must have enableUlaInternalIpv6 enabled to support this configuration. Instances receive Unique Local Addresses (ULA) that work within the VPC but aren’t routable on the public internet.
Allocate IP ranges from reserved internal pools
Organizations managing IP addresses centrally can reserve internal ranges and reference them when creating subnets.
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. This approach centralizes IP address planning: you define address pools once and reference them across multiple subnets. When using reserved ranges, the ipCidrRange property becomes optional; the subnet inherits its CIDR from the reserved pool.
Beyond these examples
These snippets focus on specific subnet-level features: primary and secondary IP range allocation, VPC flow logging and traffic analysis, IPv6 dual-stack and internal-only configurations, and proxy-only subnets for load balancers. They’re intentionally minimal rather than full VPC deployments.
The examples reference pre-existing infrastructure such as VPC networks (which must have autoCreateSubnetworks disabled) and InternalRange resources for reserved IP pools. They focus on configuring the subnet rather than provisioning the entire network topology.
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 networking 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 FREEFrequently Asked Questions
IP Addressing & Ranges
reservedInternalRange when allocating IP ranges from a reserved internal range resource (created via gcp.networkconnectivity.InternalRange). When reservedInternalRange is defined, ipCidrRange becomes optional. The value must be prefixed with networkconnectivity.googleapis.com, for example: networkconnectivity.googleapis.com/projects/{project}/locations/global/internalRanges/{rangeId}.secondaryIpRanges property with an array of range configurations. Each range can specify either ipCidrRange or reservedInternalRange. Secondary ranges are commonly used for GKE pod IPs and other alias IP scenarios.sendSecondaryIpRangeIfEmpty: false), removing secondaryIpRange from your config won’t produce a diff, as Pulumi defaults to the API’s existing value. Set sendSecondaryIpRangeIfEmpty: true to treat removal as sending an empty list to the API.IPv6 Configuration
stackType to IPV4_IPV6 (or IPV6_ONLY) and configure ipv6AccessType to either EXTERNAL or INTERNAL. For INTERNAL access, your network must have enableUlaInternalIpv6 set to true.EXTERNAL provides public IPv6 addresses for internet connectivity, but disables direct path. INTERNAL provides ULA (Unique Local Address) IPv6 for internal VPC communication only. The ipv6AccessType is immutable after the first update to IPV4_IPV6 dual stack.ipv6AccessType is immutable and can only be specified during subnet creation or the first time you update the subnet to IPV4_IPV6 dual stack. Choose carefully between EXTERNAL and INTERNAL during initial IPv6 configuration.Proxy & Load Balancer Subnets
purpose to REGIONAL_MANAGED_PROXY and role to ACTIVE. Use a /22 or larger CIDR range. Note that REGIONAL_MANAGED_PROXY is the preferred setting for all regional Envoy load balancers.purpose is REGIONAL_MANAGED_PROXY or GLOBAL_MANAGED_PROXY. Don’t configure logConfig for proxy-only subnets.PRIVATE (default for general use), REGIONAL_MANAGED_PROXY (regional Envoy load balancers), GLOBAL_MANAGED_PROXY (cross-regional Envoy load balancers), PRIVATE_SERVICE_CONNECT (PSC published services), PEER_MIGRATION (network peering migration), or PRIVATE_NAT (Private NAT gateways).Immutability & Lifecycle
name, network, region, project, description, externalIpv6Prefix, internalIpv6Prefix, ipCollection, reservedInternalRange, and resolveSubnetMask. Additionally, ipv6AccessType becomes immutable after the first update to IPV4_IPV6 dual stack.a-z?, meaning they must start with a lowercase letter, followed by lowercase letters, digits, or dashes, and cannot end with a dash.Network Configuration
autoCreateSubnetworks set to false (distributed mode). Only networks in distributed mode can have user-created subnets.privateIpGoogleAccess is enabled, VMs in the subnet without external IP addresses can access Google APIs and services using internal IP addresses.Using a different cloud?
Explore networking guides for other cloud providers: