The gcp:compute/routerNat:RouterNat resource, part of the Pulumi GCP provider, configures Cloud NAT on a Cloud Router to enable outbound connectivity and address translation for private instances. This guide focuses on three capabilities: automatic IP allocation for outbound internet access, manual IP allocation with traffic steering rules, and private NAT for inter-VPC communication.
Cloud NAT configurations attach to existing Cloud Routers and reference VPC networks, subnets, and optionally static IP addresses or Network Connectivity resources. The examples are intentionally small. Combine them with your own network infrastructure and routing policies.
Enable outbound internet access with automatic IP allocation
Most VPC deployments need outbound internet connectivity for private instances to reach external APIs, download packages, or access cloud services without assigning public IPs to individual VMs.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const net = new gcp.compute.Network("net", {name: "my-network"});
const subnet = new gcp.compute.Subnetwork("subnet", {
name: "my-subnetwork",
network: net.id,
ipCidrRange: "10.0.0.0/16",
region: "us-central1",
});
const router = new gcp.compute.Router("router", {
name: "my-router",
region: subnet.region,
network: net.id,
bgp: {
asn: 64514,
},
});
const nat = new gcp.compute.RouterNat("nat", {
name: "my-router-nat",
router: router.name,
region: router.region,
natIpAllocateOption: "AUTO_ONLY",
sourceSubnetworkIpRangesToNat: "ALL_SUBNETWORKS_ALL_IP_RANGES",
logConfig: {
enable: true,
filter: "ERRORS_ONLY",
},
});
import pulumi
import pulumi_gcp as gcp
net = gcp.compute.Network("net", name="my-network")
subnet = gcp.compute.Subnetwork("subnet",
name="my-subnetwork",
network=net.id,
ip_cidr_range="10.0.0.0/16",
region="us-central1")
router = gcp.compute.Router("router",
name="my-router",
region=subnet.region,
network=net.id,
bgp={
"asn": 64514,
})
nat = gcp.compute.RouterNat("nat",
name="my-router-nat",
router=router.name,
region=router.region,
nat_ip_allocate_option="AUTO_ONLY",
source_subnetwork_ip_ranges_to_nat="ALL_SUBNETWORKS_ALL_IP_RANGES",
log_config={
"enable": True,
"filter": "ERRORS_ONLY",
})
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 {
net, err := compute.NewNetwork(ctx, "net", &compute.NetworkArgs{
Name: pulumi.String("my-network"),
})
if err != nil {
return err
}
subnet, err := compute.NewSubnetwork(ctx, "subnet", &compute.SubnetworkArgs{
Name: pulumi.String("my-subnetwork"),
Network: net.ID(),
IpCidrRange: pulumi.String("10.0.0.0/16"),
Region: pulumi.String("us-central1"),
})
if err != nil {
return err
}
router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
Name: pulumi.String("my-router"),
Region: subnet.Region,
Network: net.ID(),
Bgp: &compute.RouterBgpArgs{
Asn: pulumi.Int(64514),
},
})
if err != nil {
return err
}
_, err = compute.NewRouterNat(ctx, "nat", &compute.RouterNatArgs{
Name: pulumi.String("my-router-nat"),
Router: router.Name,
Region: router.Region,
NatIpAllocateOption: pulumi.String("AUTO_ONLY"),
SourceSubnetworkIpRangesToNat: pulumi.String("ALL_SUBNETWORKS_ALL_IP_RANGES"),
LogConfig: &compute.RouterNatLogConfigArgs{
Enable: pulumi.Bool(true),
Filter: pulumi.String("ERRORS_ONLY"),
},
})
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 net = new Gcp.Compute.Network("net", new()
{
Name = "my-network",
});
var subnet = new Gcp.Compute.Subnetwork("subnet", new()
{
Name = "my-subnetwork",
Network = net.Id,
IpCidrRange = "10.0.0.0/16",
Region = "us-central1",
});
var router = new Gcp.Compute.Router("router", new()
{
Name = "my-router",
Region = subnet.Region,
Network = net.Id,
Bgp = new Gcp.Compute.Inputs.RouterBgpArgs
{
Asn = 64514,
},
});
var nat = new Gcp.Compute.RouterNat("nat", new()
{
Name = "my-router-nat",
Router = router.Name,
Region = router.Region,
NatIpAllocateOption = "AUTO_ONLY",
SourceSubnetworkIpRangesToNat = "ALL_SUBNETWORKS_ALL_IP_RANGES",
LogConfig = new Gcp.Compute.Inputs.RouterNatLogConfigArgs
{
Enable = true,
Filter = "ERRORS_ONLY",
},
});
});
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.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.compute.inputs.RouterBgpArgs;
import com.pulumi.gcp.compute.RouterNat;
import com.pulumi.gcp.compute.RouterNatArgs;
import com.pulumi.gcp.compute.inputs.RouterNatLogConfigArgs;
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 net = new Network("net", NetworkArgs.builder()
.name("my-network")
.build());
var subnet = new Subnetwork("subnet", SubnetworkArgs.builder()
.name("my-subnetwork")
.network(net.id())
.ipCidrRange("10.0.0.0/16")
.region("us-central1")
.build());
var router = new Router("router", RouterArgs.builder()
.name("my-router")
.region(subnet.region())
.network(net.id())
.bgp(RouterBgpArgs.builder()
.asn(64514)
.build())
.build());
var nat = new RouterNat("nat", RouterNatArgs.builder()
.name("my-router-nat")
.router(router.name())
.region(router.region())
.natIpAllocateOption("AUTO_ONLY")
.sourceSubnetworkIpRangesToNat("ALL_SUBNETWORKS_ALL_IP_RANGES")
.logConfig(RouterNatLogConfigArgs.builder()
.enable(true)
.filter("ERRORS_ONLY")
.build())
.build());
}
}
resources:
net:
type: gcp:compute:Network
properties:
name: my-network
subnet:
type: gcp:compute:Subnetwork
properties:
name: my-subnetwork
network: ${net.id}
ipCidrRange: 10.0.0.0/16
region: us-central1
router:
type: gcp:compute:Router
properties:
name: my-router
region: ${subnet.region}
network: ${net.id}
bgp:
asn: 64514
nat:
type: gcp:compute:RouterNat
properties:
name: my-router-nat
router: ${router.name}
region: ${router.region}
natIpAllocateOption: AUTO_ONLY
sourceSubnetworkIpRangesToNat: ALL_SUBNETWORKS_ALL_IP_RANGES
logConfig:
enable: true
filter: ERRORS_ONLY
When natIpAllocateOption is set to AUTO_ONLY, Google Cloud automatically allocates and manages external IPs for NAT. The sourceSubnetworkIpRangesToNat property controls which subnets can use NAT; ALL_SUBNETWORKS_ALL_IP_RANGES allows all instances in all subnets to initiate outbound connections. The logConfig block enables connection logging, useful for troubleshooting connectivity issues.
Route traffic through specific IPs with NAT rules
Applications that need predictable source IPs for allowlisting or compliance can use NAT rules to route specific traffic through designated addresses.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const net = new gcp.compute.Network("net", {
name: "my-network",
autoCreateSubnetworks: false,
});
const subnet = new gcp.compute.Subnetwork("subnet", {
name: "my-subnetwork",
network: net.id,
ipCidrRange: "10.0.0.0/16",
region: "us-central1",
});
const router = new gcp.compute.Router("router", {
name: "my-router",
region: subnet.region,
network: net.id,
});
const addr1 = new gcp.compute.Address("addr1", {
name: "nat-address1",
region: subnet.region,
});
const addr2 = new gcp.compute.Address("addr2", {
name: "nat-address2",
region: subnet.region,
});
const addr3 = new gcp.compute.Address("addr3", {
name: "nat-address3",
region: subnet.region,
});
const natRules = new gcp.compute.RouterNat("nat_rules", {
name: "my-router-nat",
router: router.name,
region: router.region,
natIpAllocateOption: "MANUAL_ONLY",
natIps: [addr1.selfLink],
sourceSubnetworkIpRangesToNat: "LIST_OF_SUBNETWORKS",
subnetworks: [{
name: subnet.id,
sourceIpRangesToNats: ["ALL_IP_RANGES"],
}],
rules: [{
ruleNumber: 100,
description: "nat rules example",
match: "inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')",
action: {
sourceNatActiveIps: [
addr2.selfLink,
addr3.selfLink,
],
},
}],
enableEndpointIndependentMapping: false,
});
import pulumi
import pulumi_gcp as gcp
net = gcp.compute.Network("net",
name="my-network",
auto_create_subnetworks=False)
subnet = gcp.compute.Subnetwork("subnet",
name="my-subnetwork",
network=net.id,
ip_cidr_range="10.0.0.0/16",
region="us-central1")
router = gcp.compute.Router("router",
name="my-router",
region=subnet.region,
network=net.id)
addr1 = gcp.compute.Address("addr1",
name="nat-address1",
region=subnet.region)
addr2 = gcp.compute.Address("addr2",
name="nat-address2",
region=subnet.region)
addr3 = gcp.compute.Address("addr3",
name="nat-address3",
region=subnet.region)
nat_rules = gcp.compute.RouterNat("nat_rules",
name="my-router-nat",
router=router.name,
region=router.region,
nat_ip_allocate_option="MANUAL_ONLY",
nat_ips=[addr1.self_link],
source_subnetwork_ip_ranges_to_nat="LIST_OF_SUBNETWORKS",
subnetworks=[{
"name": subnet.id,
"source_ip_ranges_to_nats": ["ALL_IP_RANGES"],
}],
rules=[{
"rule_number": 100,
"description": "nat rules example",
"match": "inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')",
"action": {
"source_nat_active_ips": [
addr2.self_link,
addr3.self_link,
],
},
}],
enable_endpoint_independent_mapping=False)
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 {
net, err := compute.NewNetwork(ctx, "net", &compute.NetworkArgs{
Name: pulumi.String("my-network"),
AutoCreateSubnetworks: pulumi.Bool(false),
})
if err != nil {
return err
}
subnet, err := compute.NewSubnetwork(ctx, "subnet", &compute.SubnetworkArgs{
Name: pulumi.String("my-subnetwork"),
Network: net.ID(),
IpCidrRange: pulumi.String("10.0.0.0/16"),
Region: pulumi.String("us-central1"),
})
if err != nil {
return err
}
router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
Name: pulumi.String("my-router"),
Region: subnet.Region,
Network: net.ID(),
})
if err != nil {
return err
}
addr1, err := compute.NewAddress(ctx, "addr1", &compute.AddressArgs{
Name: pulumi.String("nat-address1"),
Region: subnet.Region,
})
if err != nil {
return err
}
addr2, err := compute.NewAddress(ctx, "addr2", &compute.AddressArgs{
Name: pulumi.String("nat-address2"),
Region: subnet.Region,
})
if err != nil {
return err
}
addr3, err := compute.NewAddress(ctx, "addr3", &compute.AddressArgs{
Name: pulumi.String("nat-address3"),
Region: subnet.Region,
})
if err != nil {
return err
}
_, err = compute.NewRouterNat(ctx, "nat_rules", &compute.RouterNatArgs{
Name: pulumi.String("my-router-nat"),
Router: router.Name,
Region: router.Region,
NatIpAllocateOption: pulumi.String("MANUAL_ONLY"),
NatIps: pulumi.StringArray{
addr1.SelfLink,
},
SourceSubnetworkIpRangesToNat: pulumi.String("LIST_OF_SUBNETWORKS"),
Subnetworks: compute.RouterNatSubnetworkArray{
&compute.RouterNatSubnetworkArgs{
Name: subnet.ID(),
SourceIpRangesToNats: pulumi.StringArray{
pulumi.String("ALL_IP_RANGES"),
},
},
},
Rules: compute.RouterNatRuleArray{
&compute.RouterNatRuleArgs{
RuleNumber: pulumi.Int(100),
Description: pulumi.String("nat rules example"),
Match: pulumi.String("inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')"),
Action: &compute.RouterNatRuleActionArgs{
SourceNatActiveIps: pulumi.StringArray{
addr2.SelfLink,
addr3.SelfLink,
},
},
},
},
EnableEndpointIndependentMapping: pulumi.Bool(false),
})
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 net = new Gcp.Compute.Network("net", new()
{
Name = "my-network",
AutoCreateSubnetworks = false,
});
var subnet = new Gcp.Compute.Subnetwork("subnet", new()
{
Name = "my-subnetwork",
Network = net.Id,
IpCidrRange = "10.0.0.0/16",
Region = "us-central1",
});
var router = new Gcp.Compute.Router("router", new()
{
Name = "my-router",
Region = subnet.Region,
Network = net.Id,
});
var addr1 = new Gcp.Compute.Address("addr1", new()
{
Name = "nat-address1",
Region = subnet.Region,
});
var addr2 = new Gcp.Compute.Address("addr2", new()
{
Name = "nat-address2",
Region = subnet.Region,
});
var addr3 = new Gcp.Compute.Address("addr3", new()
{
Name = "nat-address3",
Region = subnet.Region,
});
var natRules = new Gcp.Compute.RouterNat("nat_rules", new()
{
Name = "my-router-nat",
Router = router.Name,
Region = router.Region,
NatIpAllocateOption = "MANUAL_ONLY",
NatIps = new[]
{
addr1.SelfLink,
},
SourceSubnetworkIpRangesToNat = "LIST_OF_SUBNETWORKS",
Subnetworks = new[]
{
new Gcp.Compute.Inputs.RouterNatSubnetworkArgs
{
Name = subnet.Id,
SourceIpRangesToNats = new[]
{
"ALL_IP_RANGES",
},
},
},
Rules = new[]
{
new Gcp.Compute.Inputs.RouterNatRuleArgs
{
RuleNumber = 100,
Description = "nat rules example",
Match = "inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')",
Action = new Gcp.Compute.Inputs.RouterNatRuleActionArgs
{
SourceNatActiveIps = new[]
{
addr2.SelfLink,
addr3.SelfLink,
},
},
},
},
EnableEndpointIndependentMapping = false,
});
});
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.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.compute.Address;
import com.pulumi.gcp.compute.AddressArgs;
import com.pulumi.gcp.compute.RouterNat;
import com.pulumi.gcp.compute.RouterNatArgs;
import com.pulumi.gcp.compute.inputs.RouterNatSubnetworkArgs;
import com.pulumi.gcp.compute.inputs.RouterNatRuleArgs;
import com.pulumi.gcp.compute.inputs.RouterNatRuleActionArgs;
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 net = new Network("net", NetworkArgs.builder()
.name("my-network")
.autoCreateSubnetworks(false)
.build());
var subnet = new Subnetwork("subnet", SubnetworkArgs.builder()
.name("my-subnetwork")
.network(net.id())
.ipCidrRange("10.0.0.0/16")
.region("us-central1")
.build());
var router = new Router("router", RouterArgs.builder()
.name("my-router")
.region(subnet.region())
.network(net.id())
.build());
var addr1 = new Address("addr1", AddressArgs.builder()
.name("nat-address1")
.region(subnet.region())
.build());
var addr2 = new Address("addr2", AddressArgs.builder()
.name("nat-address2")
.region(subnet.region())
.build());
var addr3 = new Address("addr3", AddressArgs.builder()
.name("nat-address3")
.region(subnet.region())
.build());
var natRules = new RouterNat("natRules", RouterNatArgs.builder()
.name("my-router-nat")
.router(router.name())
.region(router.region())
.natIpAllocateOption("MANUAL_ONLY")
.natIps(addr1.selfLink())
.sourceSubnetworkIpRangesToNat("LIST_OF_SUBNETWORKS")
.subnetworks(RouterNatSubnetworkArgs.builder()
.name(subnet.id())
.sourceIpRangesToNats("ALL_IP_RANGES")
.build())
.rules(RouterNatRuleArgs.builder()
.ruleNumber(100)
.description("nat rules example")
.match("inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')")
.action(RouterNatRuleActionArgs.builder()
.sourceNatActiveIps(
addr2.selfLink(),
addr3.selfLink())
.build())
.build())
.enableEndpointIndependentMapping(false)
.build());
}
}
resources:
net:
type: gcp:compute:Network
properties:
name: my-network
autoCreateSubnetworks: false
subnet:
type: gcp:compute:Subnetwork
properties:
name: my-subnetwork
network: ${net.id}
ipCidrRange: 10.0.0.0/16
region: us-central1
router:
type: gcp:compute:Router
properties:
name: my-router
region: ${subnet.region}
network: ${net.id}
addr1:
type: gcp:compute:Address
properties:
name: nat-address1
region: ${subnet.region}
addr2:
type: gcp:compute:Address
properties:
name: nat-address2
region: ${subnet.region}
addr3:
type: gcp:compute:Address
properties:
name: nat-address3
region: ${subnet.region}
natRules:
type: gcp:compute:RouterNat
name: nat_rules
properties:
name: my-router-nat
router: ${router.name}
region: ${router.region}
natIpAllocateOption: MANUAL_ONLY
natIps:
- ${addr1.selfLink}
sourceSubnetworkIpRangesToNat: LIST_OF_SUBNETWORKS
subnetworks:
- name: ${subnet.id}
sourceIpRangesToNats:
- ALL_IP_RANGES
rules:
- ruleNumber: 100
description: nat rules example
match: inIpRange(destination.ip, '1.1.0.0/16') || inIpRange(destination.ip, '2.2.0.0/16')
action:
sourceNatActiveIps:
- ${addr2.selfLink}
- ${addr3.selfLink}
enableEndpointIndependentMapping: false
With MANUAL_ONLY allocation, you create Address resources and reference them in natIps. The rules array defines traffic matching conditions using the match expression (checking destination IP ranges) and specifies which IPs to use via sourceNatActiveIps. The subnetworks block limits NAT to specific subnets rather than all subnets in the network. Note that Address resources used by RouterNat should include create_before_destroy lifecycle rules to avoid resourceInUseByAnotherResource errors when modifying IP assignments.
Enable private NAT for inter-VPC communication
Organizations with hub-and-spoke network topologies need private NAT to translate addresses between VPCs without exposing traffic to the public internet.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const net = new gcp.compute.Network("net", {name: "my-network"});
const subnet = new gcp.compute.Subnetwork("subnet", {
name: "my-subnetwork",
network: net.id,
ipCidrRange: "10.0.0.0/16",
region: "us-central1",
purpose: "PRIVATE_NAT",
});
const router = new gcp.compute.Router("router", {
name: "my-router",
region: subnet.region,
network: net.id,
});
const hub = new gcp.networkconnectivity.Hub("hub", {
name: "my-hub",
description: "vpc hub for inter vpc nat",
});
const spoke = new gcp.networkconnectivity.Spoke("spoke", {
name: "my-spoke",
location: "global",
description: "vpc spoke for inter vpc nat",
hub: hub.id,
linkedVpcNetwork: {
excludeExportRanges: [
"198.51.100.0/24",
"10.10.0.0/16",
],
uri: net.selfLink,
},
});
const natType = new gcp.compute.RouterNat("nat_type", {
name: "my-router-nat",
router: router.name,
region: router.region,
sourceSubnetworkIpRangesToNat: "LIST_OF_SUBNETWORKS",
enableDynamicPortAllocation: false,
enableEndpointIndependentMapping: false,
minPortsPerVm: 32,
type: "PRIVATE",
subnetworks: [{
name: subnet.id,
sourceIpRangesToNats: ["ALL_IP_RANGES"],
}],
rules: [{
ruleNumber: 100,
description: "rule for private nat",
match: "nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\"",
action: {
sourceNatActiveRanges: [subnet.selfLink],
},
}],
});
import pulumi
import pulumi_gcp as gcp
net = gcp.compute.Network("net", name="my-network")
subnet = gcp.compute.Subnetwork("subnet",
name="my-subnetwork",
network=net.id,
ip_cidr_range="10.0.0.0/16",
region="us-central1",
purpose="PRIVATE_NAT")
router = gcp.compute.Router("router",
name="my-router",
region=subnet.region,
network=net.id)
hub = gcp.networkconnectivity.Hub("hub",
name="my-hub",
description="vpc hub for inter vpc nat")
spoke = gcp.networkconnectivity.Spoke("spoke",
name="my-spoke",
location="global",
description="vpc spoke for inter vpc nat",
hub=hub.id,
linked_vpc_network={
"exclude_export_ranges": [
"198.51.100.0/24",
"10.10.0.0/16",
],
"uri": net.self_link,
})
nat_type = gcp.compute.RouterNat("nat_type",
name="my-router-nat",
router=router.name,
region=router.region,
source_subnetwork_ip_ranges_to_nat="LIST_OF_SUBNETWORKS",
enable_dynamic_port_allocation=False,
enable_endpoint_independent_mapping=False,
min_ports_per_vm=32,
type="PRIVATE",
subnetworks=[{
"name": subnet.id,
"source_ip_ranges_to_nats": ["ALL_IP_RANGES"],
}],
rules=[{
"rule_number": 100,
"description": "rule for private nat",
"match": "nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\"",
"action": {
"source_nat_active_ranges": [subnet.self_link],
},
}])
package main
import (
"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 {
net, err := compute.NewNetwork(ctx, "net", &compute.NetworkArgs{
Name: pulumi.String("my-network"),
})
if err != nil {
return err
}
subnet, err := compute.NewSubnetwork(ctx, "subnet", &compute.SubnetworkArgs{
Name: pulumi.String("my-subnetwork"),
Network: net.ID(),
IpCidrRange: pulumi.String("10.0.0.0/16"),
Region: pulumi.String("us-central1"),
Purpose: pulumi.String("PRIVATE_NAT"),
})
if err != nil {
return err
}
router, err := compute.NewRouter(ctx, "router", &compute.RouterArgs{
Name: pulumi.String("my-router"),
Region: subnet.Region,
Network: net.ID(),
})
if err != nil {
return err
}
hub, err := networkconnectivity.NewHub(ctx, "hub", &networkconnectivity.HubArgs{
Name: pulumi.String("my-hub"),
Description: pulumi.String("vpc hub for inter vpc nat"),
})
if err != nil {
return err
}
_, err = networkconnectivity.NewSpoke(ctx, "spoke", &networkconnectivity.SpokeArgs{
Name: pulumi.String("my-spoke"),
Location: pulumi.String("global"),
Description: pulumi.String("vpc spoke for inter vpc nat"),
Hub: hub.ID(),
LinkedVpcNetwork: &networkconnectivity.SpokeLinkedVpcNetworkArgs{
ExcludeExportRanges: pulumi.StringArray{
pulumi.String("198.51.100.0/24"),
pulumi.String("10.10.0.0/16"),
},
Uri: net.SelfLink,
},
})
if err != nil {
return err
}
_, err = compute.NewRouterNat(ctx, "nat_type", &compute.RouterNatArgs{
Name: pulumi.String("my-router-nat"),
Router: router.Name,
Region: router.Region,
SourceSubnetworkIpRangesToNat: pulumi.String("LIST_OF_SUBNETWORKS"),
EnableDynamicPortAllocation: pulumi.Bool(false),
EnableEndpointIndependentMapping: pulumi.Bool(false),
MinPortsPerVm: pulumi.Int(32),
Type: pulumi.String("PRIVATE"),
Subnetworks: compute.RouterNatSubnetworkArray{
&compute.RouterNatSubnetworkArgs{
Name: subnet.ID(),
SourceIpRangesToNats: pulumi.StringArray{
pulumi.String("ALL_IP_RANGES"),
},
},
},
Rules: compute.RouterNatRuleArray{
&compute.RouterNatRuleArgs{
RuleNumber: pulumi.Int(100),
Description: pulumi.String("rule for private nat"),
Match: pulumi.String("nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\""),
Action: &compute.RouterNatRuleActionArgs{
SourceNatActiveRanges: pulumi.StringArray{
subnet.SelfLink,
},
},
},
},
})
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 net = new Gcp.Compute.Network("net", new()
{
Name = "my-network",
});
var subnet = new Gcp.Compute.Subnetwork("subnet", new()
{
Name = "my-subnetwork",
Network = net.Id,
IpCidrRange = "10.0.0.0/16",
Region = "us-central1",
Purpose = "PRIVATE_NAT",
});
var router = new Gcp.Compute.Router("router", new()
{
Name = "my-router",
Region = subnet.Region,
Network = net.Id,
});
var hub = new Gcp.NetworkConnectivity.Hub("hub", new()
{
Name = "my-hub",
Description = "vpc hub for inter vpc nat",
});
var spoke = new Gcp.NetworkConnectivity.Spoke("spoke", new()
{
Name = "my-spoke",
Location = "global",
Description = "vpc spoke for inter vpc nat",
Hub = hub.Id,
LinkedVpcNetwork = new Gcp.NetworkConnectivity.Inputs.SpokeLinkedVpcNetworkArgs
{
ExcludeExportRanges = new[]
{
"198.51.100.0/24",
"10.10.0.0/16",
},
Uri = net.SelfLink,
},
});
var natType = new Gcp.Compute.RouterNat("nat_type", new()
{
Name = "my-router-nat",
Router = router.Name,
Region = router.Region,
SourceSubnetworkIpRangesToNat = "LIST_OF_SUBNETWORKS",
EnableDynamicPortAllocation = false,
EnableEndpointIndependentMapping = false,
MinPortsPerVm = 32,
Type = "PRIVATE",
Subnetworks = new[]
{
new Gcp.Compute.Inputs.RouterNatSubnetworkArgs
{
Name = subnet.Id,
SourceIpRangesToNats = new[]
{
"ALL_IP_RANGES",
},
},
},
Rules = new[]
{
new Gcp.Compute.Inputs.RouterNatRuleArgs
{
RuleNumber = 100,
Description = "rule for private nat",
Match = "nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\"",
Action = new Gcp.Compute.Inputs.RouterNatRuleActionArgs
{
SourceNatActiveRanges = new[]
{
subnet.SelfLink,
},
},
},
},
});
});
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.Router;
import com.pulumi.gcp.compute.RouterArgs;
import com.pulumi.gcp.networkconnectivity.Hub;
import com.pulumi.gcp.networkconnectivity.HubArgs;
import com.pulumi.gcp.networkconnectivity.Spoke;
import com.pulumi.gcp.networkconnectivity.SpokeArgs;
import com.pulumi.gcp.networkconnectivity.inputs.SpokeLinkedVpcNetworkArgs;
import com.pulumi.gcp.compute.RouterNat;
import com.pulumi.gcp.compute.RouterNatArgs;
import com.pulumi.gcp.compute.inputs.RouterNatSubnetworkArgs;
import com.pulumi.gcp.compute.inputs.RouterNatRuleArgs;
import com.pulumi.gcp.compute.inputs.RouterNatRuleActionArgs;
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 net = new Network("net", NetworkArgs.builder()
.name("my-network")
.build());
var subnet = new Subnetwork("subnet", SubnetworkArgs.builder()
.name("my-subnetwork")
.network(net.id())
.ipCidrRange("10.0.0.0/16")
.region("us-central1")
.purpose("PRIVATE_NAT")
.build());
var router = new Router("router", RouterArgs.builder()
.name("my-router")
.region(subnet.region())
.network(net.id())
.build());
var hub = new Hub("hub", HubArgs.builder()
.name("my-hub")
.description("vpc hub for inter vpc nat")
.build());
var spoke = new Spoke("spoke", SpokeArgs.builder()
.name("my-spoke")
.location("global")
.description("vpc spoke for inter vpc nat")
.hub(hub.id())
.linkedVpcNetwork(SpokeLinkedVpcNetworkArgs.builder()
.excludeExportRanges(
"198.51.100.0/24",
"10.10.0.0/16")
.uri(net.selfLink())
.build())
.build());
var natType = new RouterNat("natType", RouterNatArgs.builder()
.name("my-router-nat")
.router(router.name())
.region(router.region())
.sourceSubnetworkIpRangesToNat("LIST_OF_SUBNETWORKS")
.enableDynamicPortAllocation(false)
.enableEndpointIndependentMapping(false)
.minPortsPerVm(32)
.type("PRIVATE")
.subnetworks(RouterNatSubnetworkArgs.builder()
.name(subnet.id())
.sourceIpRangesToNats("ALL_IP_RANGES")
.build())
.rules(RouterNatRuleArgs.builder()
.ruleNumber(100)
.description("rule for private nat")
.match("nexthop.hub == \"//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub\"")
.action(RouterNatRuleActionArgs.builder()
.sourceNatActiveRanges(subnet.selfLink())
.build())
.build())
.build());
}
}
resources:
net:
type: gcp:compute:Network
properties:
name: my-network
subnet:
type: gcp:compute:Subnetwork
properties:
name: my-subnetwork
network: ${net.id}
ipCidrRange: 10.0.0.0/16
region: us-central1
purpose: PRIVATE_NAT
router:
type: gcp:compute:Router
properties:
name: my-router
region: ${subnet.region}
network: ${net.id}
hub:
type: gcp:networkconnectivity:Hub
properties:
name: my-hub
description: vpc hub for inter vpc nat
spoke:
type: gcp:networkconnectivity:Spoke
properties:
name: my-spoke
location: global
description: vpc spoke for inter vpc nat
hub: ${hub.id}
linkedVpcNetwork:
excludeExportRanges:
- 198.51.100.0/24
- 10.10.0.0/16
uri: ${net.selfLink}
natType:
type: gcp:compute:RouterNat
name: nat_type
properties:
name: my-router-nat
router: ${router.name}
region: ${router.region}
sourceSubnetworkIpRangesToNat: LIST_OF_SUBNETWORKS
enableDynamicPortAllocation: false
enableEndpointIndependentMapping: false
minPortsPerVm: 32
type: PRIVATE
subnetworks:
- name: ${subnet.id}
sourceIpRangesToNats:
- ALL_IP_RANGES
rules:
- ruleNumber: 100
description: rule for private nat
match: nexthop.hub == "//networkconnectivity.googleapis.com/projects/acm-test-proj-123/locations/global/hubs/my-hub"
action:
sourceNatActiveRanges:
- ${subnet.selfLink}
Setting type to PRIVATE enables private NAT, which translates addresses for traffic between connected VPCs. The rules block uses a match expression that checks the nexthop.hub to identify traffic flowing through the Network Connectivity Hub. Instead of external IPs, sourceNatActiveRanges references the subnet’s CIDR range for address translation. This configuration requires a subnet with purpose set to PRIVATE_NAT and Network Connectivity Hub and Spoke resources to establish inter-VPC routing.
Beyond these examples
These snippets focus on specific Cloud NAT features: automatic and manual IP allocation, NAT rules for traffic steering, and private NAT for inter-VPC translation. They’re intentionally minimal rather than full network architectures.
The examples may reference pre-existing infrastructure such as VPC networks, subnets, and Cloud Routers, static IP addresses for manual allocation, and Network Connectivity Hub and Spoke resources for private NAT. They focus on configuring the NAT gateway rather than provisioning the entire network topology.
To keep things focused, common NAT patterns are omitted, including:
- Connection timeout tuning (tcpEstablishedIdleTimeoutSec, udpIdleTimeoutSec)
- Dynamic port allocation (enableDynamicPortAllocation, minPortsPerVm, maxPortsPerVm)
- Endpoint-independent mapping (enableEndpointIndependentMapping)
- NAT64 for IPv6 translation (nat64Subnetworks, sourceSubnetworkIpRangesToNat64)
These omissions are intentional: the goal is to illustrate how each NAT feature is wired, not provide drop-in networking modules. See the RouterNat resource reference for all available configuration options.
Let's configure GCP Cloud Router NAT
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Common Errors & Pitfalls
gcp.compute.Address that’s being used by RouterNat will trigger this error. Use lifecycle.create_before_destroy = true on the address resource to avoid it.sourceSubnetworkIpRangesToNat is set to ALL_SUBNETWORKS_ALL_IP_RANGES or ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES, there should not be any other RouterNat section in any Router for this network in this region.IP Allocation & Addressing
AUTO_ONLY lets Google Cloud Platform automatically allocate NAT IPs, while MANUAL_ONLY requires you to provide user-allocated NAT IP addresses via the natIps property.initialNatIps when creating a RouterNat alongside a RouterNatAddress resource. It conflicts with natIps and drainNatIps, and is only valid when natIpAllocateOption is set to MANUAL_ONLY.Subnetwork Configuration
ALL_SUBNETWORKS_ALL_IP_RANGES (all IP ranges in every subnetwork), ALL_SUBNETWORKS_ALL_PRIMARY_IP_RANGES (only primary IP ranges), or LIST_OF_SUBNETWORKS (specify subnetworks in the subnetworks field).sourceSubnetworkIpRangesToNat to LIST_OF_SUBNETWORKS and provide the subnetworks field with your subnetwork configurations.Port Allocation
enableDynamicPortAllocation is enabled, minPortsPerVm must be a power of two greater than or equal to 32, and maxPortsPerVm must be a power of two greater than minPortsPerVm.minPortsPerVm defaults to 64 for static port allocation and 32 for dynamic port allocation. If maxPortsPerVm is not set with dynamic allocation, a maximum of 65,536 ports will be allocated.NAT Types & Advanced Features
PUBLIC NAT (the default) is used for public IP translation, while PRIVATE NAT is used for private IP translation. The type property is immutable after creation.sourceSubnetworkIpRangesToNat64 contains ALL_IPV6_SUBNETWORKS, no other Router.Nat section in this region can also enable NAT64 for any subnetworks in this network. Other Router.Nat sections can still enable NAT44 only.Timeouts & Connection Limits
Using a different cloud?
Explore networking guides for other cloud providers: