Reserve and Manage GCP IP Addresses

The gcp:compute/address:Address resource, part of the Pulumi GCP provider, reserves static IP addresses that persist independently of VM instances. This guide focuses on four capabilities: external IP reservation, internal IP allocation within subnets, VM instance attachment, and IPsec Interconnect IP ranges.

Addresses reference VPC networks and subnets that must exist separately. Internal addresses require explicit addressType and subnet configuration. The examples are intentionally small. Combine them with your own VPC infrastructure and VM configurations.

Reserve an external IP address

Most deployments start by reserving a static external IP that persists beyond individual VM lifecycles.

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

const ipAddress = new gcp.compute.Address("ip_address", {name: "my-address"});
import pulumi
import pulumi_gcp as gcp

ip_address = gcp.compute.Address("ip_address", name="my-address")
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 {
		_, err := compute.NewAddress(ctx, "ip_address", &compute.AddressArgs{
			Name: pulumi.String("my-address"),
		})
		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 ipAddress = new Gcp.Compute.Address("ip_address", new()
    {
        Name = "my-address",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Address;
import com.pulumi.gcp.compute.AddressArgs;
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 ipAddress = new Address("ipAddress", AddressArgs.builder()
            .name("my-address")
            .build());

    }
}
resources:
  ipAddress:
    type: gcp:compute:Address
    name: ip_address
    properties:
      name: my-address

When addressType is omitted, the address defaults to EXTERNAL. The reserved IP can be attached to VM instances, load balancers, or other resources that need stable external connectivity. The name must comply with RFC1035 naming conventions.

Reserve an internal IP within a subnet

Applications communicating within a VPC often need static internal IPs to maintain stable connectivity between services.

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

const _default = new gcp.compute.Network("default", {name: "my-network"});
const defaultSubnetwork = new gcp.compute.Subnetwork("default", {
    name: "my-subnet",
    ipCidrRange: "10.0.0.0/16",
    region: "us-central1",
    network: _default.id,
});
const internalWithSubnetAndAddress = new gcp.compute.Address("internal_with_subnet_and_address", {
    name: "my-internal-address",
    subnetwork: defaultSubnetwork.id,
    addressType: "INTERNAL",
    address: "10.0.42.42",
    region: "us-central1",
});
import pulumi
import pulumi_gcp as gcp

default = gcp.compute.Network("default", name="my-network")
default_subnetwork = gcp.compute.Subnetwork("default",
    name="my-subnet",
    ip_cidr_range="10.0.0.0/16",
    region="us-central1",
    network=default.id)
internal_with_subnet_and_address = gcp.compute.Address("internal_with_subnet_and_address",
    name="my-internal-address",
    subnetwork=default_subnetwork.id,
    address_type="INTERNAL",
    address="10.0.42.42",
    region="us-central1")
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 {
		_default, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
			Name: pulumi.String("my-network"),
		})
		if err != nil {
			return err
		}
		defaultSubnetwork, err := compute.NewSubnetwork(ctx, "default", &compute.SubnetworkArgs{
			Name:        pulumi.String("my-subnet"),
			IpCidrRange: pulumi.String("10.0.0.0/16"),
			Region:      pulumi.String("us-central1"),
			Network:     _default.ID(),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewAddress(ctx, "internal_with_subnet_and_address", &compute.AddressArgs{
			Name:        pulumi.String("my-internal-address"),
			Subnetwork:  defaultSubnetwork.ID(),
			AddressType: pulumi.String("INTERNAL"),
			Address:     pulumi.String("10.0.42.42"),
			Region:      pulumi.String("us-central1"),
		})
		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 = "my-network",
    });

    var defaultSubnetwork = new Gcp.Compute.Subnetwork("default", new()
    {
        Name = "my-subnet",
        IpCidrRange = "10.0.0.0/16",
        Region = "us-central1",
        Network = @default.Id,
    });

    var internalWithSubnetAndAddress = new Gcp.Compute.Address("internal_with_subnet_and_address", new()
    {
        Name = "my-internal-address",
        Subnetwork = defaultSubnetwork.Id,
        AddressType = "INTERNAL",
        IPAddress = "10.0.42.42",
        Region = "us-central1",
    });

});
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.Address;
import com.pulumi.gcp.compute.AddressArgs;
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("my-network")
            .build());

        var defaultSubnetwork = new Subnetwork("defaultSubnetwork", SubnetworkArgs.builder()
            .name("my-subnet")
            .ipCidrRange("10.0.0.0/16")
            .region("us-central1")
            .network(default_.id())
            .build());

        var internalWithSubnetAndAddress = new Address("internalWithSubnetAndAddress", AddressArgs.builder()
            .name("my-internal-address")
            .subnetwork(defaultSubnetwork.id())
            .addressType("INTERNAL")
            .address("10.0.42.42")
            .region("us-central1")
            .build());

    }
}
resources:
  default:
    type: gcp:compute:Network
    properties:
      name: my-network
  defaultSubnetwork:
    type: gcp:compute:Subnetwork
    name: default
    properties:
      name: my-subnet
      ipCidrRange: 10.0.0.0/16
      region: us-central1
      network: ${default.id}
  internalWithSubnetAndAddress:
    type: gcp:compute:Address
    name: internal_with_subnet_and_address
    properties:
      name: my-internal-address
      subnetwork: ${defaultSubnetwork.id}
      addressType: INTERNAL
      address: 10.0.42.42
      region: us-central1

Setting addressType to INTERNAL reserves an IP within the VPC’s private address space. The address property specifies the exact IP (must fall within the subnet’s CIDR range), and subnetwork ties the reservation to a specific subnet. The region must match the subnet’s region.

Attach a static IP to a VM instance

VM instances serving public traffic or requiring consistent DNS records need static external IPs.

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

const static = new gcp.compute.Address("static", {name: "ipv4-address"});
const debianImage = gcp.compute.getImage({
    family: "debian-11",
    project: "debian-cloud",
});
const instanceWithIp = new gcp.compute.Instance("instance_with_ip", {
    name: "vm-instance",
    machineType: "f1-micro",
    zone: "us-central1-a",
    bootDisk: {
        initializeParams: {
            image: debianImage.then(debianImage => debianImage.selfLink),
        },
    },
    networkInterfaces: [{
        network: "default",
        accessConfigs: [{
            natIp: static.address,
        }],
    }],
});
import pulumi
import pulumi_gcp as gcp

static = gcp.compute.Address("static", name="ipv4-address")
debian_image = gcp.compute.get_image(family="debian-11",
    project="debian-cloud")
instance_with_ip = gcp.compute.Instance("instance_with_ip",
    name="vm-instance",
    machine_type="f1-micro",
    zone="us-central1-a",
    boot_disk={
        "initialize_params": {
            "image": debian_image.self_link,
        },
    },
    network_interfaces=[{
        "network": "default",
        "access_configs": [{
            "nat_ip": static.address,
        }],
    }])
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 {
		static, err := compute.NewAddress(ctx, "static", &compute.AddressArgs{
			Name: pulumi.String("ipv4-address"),
		})
		if err != nil {
			return err
		}
		debianImage, err := compute.LookupImage(ctx, &compute.LookupImageArgs{
			Family:  pulumi.StringRef("debian-11"),
			Project: pulumi.StringRef("debian-cloud"),
		}, nil)
		if err != nil {
			return err
		}
		_, err = compute.NewInstance(ctx, "instance_with_ip", &compute.InstanceArgs{
			Name:        pulumi.String("vm-instance"),
			MachineType: pulumi.String("f1-micro"),
			Zone:        pulumi.String("us-central1-a"),
			BootDisk: &compute.InstanceBootDiskArgs{
				InitializeParams: &compute.InstanceBootDiskInitializeParamsArgs{
					Image: pulumi.String(debianImage.SelfLink),
				},
			},
			NetworkInterfaces: compute.InstanceNetworkInterfaceArray{
				&compute.InstanceNetworkInterfaceArgs{
					Network: pulumi.String("default"),
					AccessConfigs: compute.InstanceNetworkInterfaceAccessConfigArray{
						&compute.InstanceNetworkInterfaceAccessConfigArgs{
							NatIp: static.Address,
						},
					},
				},
			},
		})
		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 @static = new Gcp.Compute.Address("static", new()
    {
        Name = "ipv4-address",
    });

    var debianImage = Gcp.Compute.GetImage.Invoke(new()
    {
        Family = "debian-11",
        Project = "debian-cloud",
    });

    var instanceWithIp = new Gcp.Compute.Instance("instance_with_ip", new()
    {
        Name = "vm-instance",
        MachineType = "f1-micro",
        Zone = "us-central1-a",
        BootDisk = new Gcp.Compute.Inputs.InstanceBootDiskArgs
        {
            InitializeParams = new Gcp.Compute.Inputs.InstanceBootDiskInitializeParamsArgs
            {
                Image = debianImage.Apply(getImageResult => getImageResult.SelfLink),
            },
        },
        NetworkInterfaces = new[]
        {
            new Gcp.Compute.Inputs.InstanceNetworkInterfaceArgs
            {
                Network = "default",
                AccessConfigs = new[]
                {
                    new Gcp.Compute.Inputs.InstanceNetworkInterfaceAccessConfigArgs
                    {
                        NatIp = @static.IPAddress,
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Address;
import com.pulumi.gcp.compute.AddressArgs;
import com.pulumi.gcp.compute.ComputeFunctions;
import com.pulumi.gcp.compute.inputs.GetImageArgs;
import com.pulumi.gcp.compute.Instance;
import com.pulumi.gcp.compute.InstanceArgs;
import com.pulumi.gcp.compute.inputs.InstanceBootDiskArgs;
import com.pulumi.gcp.compute.inputs.InstanceBootDiskInitializeParamsArgs;
import com.pulumi.gcp.compute.inputs.InstanceNetworkInterfaceArgs;
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 static_ = new Address("static", AddressArgs.builder()
            .name("ipv4-address")
            .build());

        final var debianImage = ComputeFunctions.getImage(GetImageArgs.builder()
            .family("debian-11")
            .project("debian-cloud")
            .build());

        var instanceWithIp = new Instance("instanceWithIp", InstanceArgs.builder()
            .name("vm-instance")
            .machineType("f1-micro")
            .zone("us-central1-a")
            .bootDisk(InstanceBootDiskArgs.builder()
                .initializeParams(InstanceBootDiskInitializeParamsArgs.builder()
                    .image(debianImage.selfLink())
                    .build())
                .build())
            .networkInterfaces(InstanceNetworkInterfaceArgs.builder()
                .network("default")
                .accessConfigs(InstanceNetworkInterfaceAccessConfigArgs.builder()
                    .natIp(static_.address())
                    .build())
                .build())
            .build());

    }
}
resources:
  static:
    type: gcp:compute:Address
    properties:
      name: ipv4-address
  instanceWithIp:
    type: gcp:compute:Instance
    name: instance_with_ip
    properties:
      name: vm-instance
      machineType: f1-micro
      zone: us-central1-a
      bootDisk:
        initializeParams:
          image: ${debianImage.selfLink}
      networkInterfaces:
        - network: default
          accessConfigs:
            - natIp: ${static.address}
variables:
  debianImage:
    fn::invoke:
      function: gcp:compute:getImage
      arguments:
        family: debian-11
        project: debian-cloud

The VM’s accessConfigs block references the reserved address via natIp. This attaches the static external IP to the instance’s network interface, ensuring the IP persists across instance restarts or replacements.

Reserve an IP range for IPsec Interconnect

HA VPN over Cloud Interconnect configurations require reserved IP ranges for VLAN attachments.

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

const network = new gcp.compute.Network("network", {
    name: "test-network",
    autoCreateSubnetworks: false,
});
const ipsec_interconnect_address = new gcp.compute.Address("ipsec-interconnect-address", {
    name: "test-address",
    addressType: "INTERNAL",
    purpose: "IPSEC_INTERCONNECT",
    address: "192.168.1.0",
    prefixLength: 29,
    network: network.selfLink,
});
import pulumi
import pulumi_gcp as gcp

network = gcp.compute.Network("network",
    name="test-network",
    auto_create_subnetworks=False)
ipsec_interconnect_address = gcp.compute.Address("ipsec-interconnect-address",
    name="test-address",
    address_type="INTERNAL",
    purpose="IPSEC_INTERCONNECT",
    address="192.168.1.0",
    prefix_length=29,
    network=network.self_link)
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 {
		network, err := compute.NewNetwork(ctx, "network", &compute.NetworkArgs{
			Name:                  pulumi.String("test-network"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		_, err = compute.NewAddress(ctx, "ipsec-interconnect-address", &compute.AddressArgs{
			Name:         pulumi.String("test-address"),
			AddressType:  pulumi.String("INTERNAL"),
			Purpose:      pulumi.String("IPSEC_INTERCONNECT"),
			Address:      pulumi.String("192.168.1.0"),
			PrefixLength: pulumi.Int(29),
			Network:      network.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 network = new Gcp.Compute.Network("network", new()
    {
        Name = "test-network",
        AutoCreateSubnetworks = false,
    });

    var ipsec_interconnect_address = new Gcp.Compute.Address("ipsec-interconnect-address", new()
    {
        Name = "test-address",
        AddressType = "INTERNAL",
        Purpose = "IPSEC_INTERCONNECT",
        IPAddress = "192.168.1.0",
        PrefixLength = 29,
        Network = network.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.Address;
import com.pulumi.gcp.compute.AddressArgs;
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 network = new Network("network", NetworkArgs.builder()
            .name("test-network")
            .autoCreateSubnetworks(false)
            .build());

        var ipsec_interconnect_address = new Address("ipsec-interconnect-address", AddressArgs.builder()
            .name("test-address")
            .addressType("INTERNAL")
            .purpose("IPSEC_INTERCONNECT")
            .address("192.168.1.0")
            .prefixLength(29)
            .network(network.selfLink())
            .build());

    }
}
resources:
  ipsec-interconnect-address:
    type: gcp:compute:Address
    properties:
      name: test-address
      addressType: INTERNAL
      purpose: IPSEC_INTERCONNECT
      address: 192.168.1.0
      prefixLength: 29
      network: ${network.selfLink}
  network:
    type: gcp:compute:Network
    properties:
      name: test-network
      autoCreateSubnetworks: false

Setting purpose to IPSEC_INTERCONNECT reserves an IP range (not a single address) for VPN connectivity. The prefixLength defines the range size (29 provides 8 addresses), and network ties the reservation to a specific VPC. This configuration is required for HA VPN over Cloud Interconnect.

Beyond these examples

These snippets focus on specific address-level features: external and internal IP reservation, subnet-specific addressing, and VM instance attachment and IPsec Interconnect ranges. They’re intentionally minimal rather than full networking deployments.

The examples may reference pre-existing infrastructure such as VPC networks and subnets, and VM images from public projects. They focus on configuring the address rather than provisioning everything around it.

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

  • Network tier selection (networkTier for PREMIUM vs STANDARD)
  • IPv6 addressing (ipVersion, ipv6EndpointType)
  • Shared load balancer VIPs (purpose: SHARED_LOADBALANCER_VIP)
  • VPC peering addresses (purpose: VPC_PEERING)
  • Labels and metadata (labels property)

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

Let's reserve and Manage GCP IP Addresses

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Common Pitfalls & Limitations
Why can't I set networkTier for internal addresses?
Internal addresses always use Premium tier, so networkTier must be left unset when addressType is INTERNAL. Setting it causes configuration errors.
What properties are immutable after creation?
All properties are immutable. Changing any property (name, address, region, addressType, purpose, etc.) requires recreating the resource.
Why doesn't the labels field show all labels on my address?
The labels field is non-authoritative and only manages labels in your configuration. Use effectiveLabels to see all labels present on the resource, including those set by other clients.
Address Types & Configuration
What's the difference between INTERNAL and EXTERNAL addresses?
EXTERNAL addresses (default) are public IPs for internet communication. INTERNAL addresses are private IPs within your VPC for internal communication between resources.
What are the different purpose values and when should I use them?

Purpose is only for INTERNAL addresses:

  • GCE_ENDPOINT - VM instances, alias IPs, load balancers (most common)
  • SHARED_LOADBALANCER_VIP - Multiple internal load balancers
  • VPC_PEERING - VPC peer networks
  • IPSEC_INTERCONNECT - HA VPN over Cloud Interconnect
  • PRIVATE_SERVICE_CONNECT - Private Service Connect (global only)
What naming rules must I follow for addresses?
Names must be 1-63 characters, start with a lowercase letter, and contain only lowercase letters, digits, and dashes. The last character can’t be a dash (regex: [a-z][-a-z0-9]*[a-z0-9]?).
IP Address Assignment
How do I reserve a specific IP address?
Set the address property to your desired IP. For internal addresses, it must be within the subnetwork’s IP range (e.g., address: "10.0.42.42").
Can I let GCP auto-assign the IP address?
Yes, leave the address property unset and GCP will automatically assign an available IP address.
Usage & Integration
How do I reserve an internal IP address in a specific subnet?
Set addressType to INTERNAL, specify the subnetwork ID, and optionally provide a specific address within the subnet’s range.
How do I attach a reserved IP address to a VM instance?
Reference the address in your VM’s networkInterfaces configuration using accessConfigs.natIp set to the address’s address output property.

Using a different cloud?

Explore networking guides for other cloud providers: