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 three capabilities: external IP reservation for public endpoints, internal IP allocation within subnets, and purpose-based addressing for specialized use cases.

Addresses reference VPC networks and subnets that must exist separately. VM instances attach addresses through network interface configuration. The examples are intentionally small. Combine them with your own VPC infrastructure and instance definitions.

Reserve an external IP address

Most deployments start by reserving a static external IP that persists beyond individual VM lifecycles, providing a stable endpoint for DNS and external clients.

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, it defaults to EXTERNAL. The name property must follow RFC1035 naming rules. The address value is assigned by GCP if not specified, and the region defaults to your provider configuration.

Reserve an internal IP within a subnet

Applications communicating within a VPC often need predictable internal addresses for databases, internal APIs, or service discovery.

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 and specifying a subnetwork pins the address within that subnet’s CIDR range. The address property must fall within the subnet’s IP range (here, 10.0.42.42 within 10.0.0.0/16). The region must match the subnet’s region.

Reserve an internal address for VM endpoints

VM instances and load balancers require internal addresses with the GCE_ENDPOINT purpose for proper VPC routing.

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

const internalWithGceEndpoint = new gcp.compute.Address("internal_with_gce_endpoint", {
    name: "my-internal-address-",
    addressType: "INTERNAL",
    purpose: "GCE_ENDPOINT",
});
import pulumi
import pulumi_gcp as gcp

internal_with_gce_endpoint = gcp.compute.Address("internal_with_gce_endpoint",
    name="my-internal-address-",
    address_type="INTERNAL",
    purpose="GCE_ENDPOINT")
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, "internal_with_gce_endpoint", &compute.AddressArgs{
			Name:        pulumi.String("my-internal-address-"),
			AddressType: pulumi.String("INTERNAL"),
			Purpose:     pulumi.String("GCE_ENDPOINT"),
		})
		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 internalWithGceEndpoint = new Gcp.Compute.Address("internal_with_gce_endpoint", new()
    {
        Name = "my-internal-address-",
        AddressType = "INTERNAL",
        Purpose = "GCE_ENDPOINT",
    });

});
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 internalWithGceEndpoint = new Address("internalWithGceEndpoint", AddressArgs.builder()
            .name("my-internal-address-")
            .addressType("INTERNAL")
            .purpose("GCE_ENDPOINT")
            .build());

    }
}
resources:
  internalWithGceEndpoint:
    type: gcp:compute:Address
    name: internal_with_gce_endpoint
    properties:
      name: my-internal-address-
      addressType: INTERNAL
      purpose: GCE_ENDPOINT

The purpose property controls how the address is used. GCE_ENDPOINT indicates the address will be attached to VM instances, alias IP ranges, or load balancers. When purpose is set, addressType must be INTERNAL.

Attach a static IP to a VM instance

VM instances serving public traffic need stable external IPs that survive instance recreation.

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 networkInterfaces block references the reserved address through accessConfigs. The natIp property points to the address resource’s address output property, which contains the actual IP value assigned by GCP.

Reserve an IP range for IPsec interconnect

HA VPN over Cloud Interconnect requires reserved IP ranges with specific prefix lengths 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

The IPSEC_INTERCONNECT purpose reserves an IP range rather than a single address. The prefixLength property defines the range size (29 provides 8 addresses). The network property specifies which VPC network owns the range, and the address property sets the range’s starting IP.

Beyond these examples

These snippets focus on specific address-level features: external and internal IP reservation, subnet-specific and network-wide addressing, and purpose-based allocation. They’re intentionally minimal rather than full networking deployments.

The examples may reference pre-existing infrastructure such as VPC networks and subnets, and VM instances for IP attachment. 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)
  • VPC peering and shared load balancer addresses (VPC_PEERING, SHARED_LOADBALANCER_VIP purposes)
  • 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

Address Types & Configuration
What's the difference between INTERNAL and EXTERNAL addresses?
EXTERNAL addresses (the default) are used for internet-facing resources, while INTERNAL addresses are for private communication within your VPC. Internal addresses require a purpose value and should not have networkTier set, as internal traffic is always Premium tier.
What are the different purpose values and when do I use them?

Purpose values are only for INTERNAL addresses:

  • GCE_ENDPOINT for VM instances, alias IPs, and load balancers
  • SHARED_LOADBALANCER_VIP for addresses shared by multiple internal load balancers
  • VPC_PEERING for VPC peer network addresses
  • IPSEC_INTERCONNECT for HA VPN over Cloud Interconnect
  • PRIVATE_SERVICE_CONNECT for Private Service Connect (global internal only)
What properties can I modify without recreating the address?
Only labels can be modified. All other properties (including address, name, addressType, region, subnetwork, purpose, and networkTier) are immutable and require recreating the resource.
Network Configuration
Why am I getting an error when setting networkTier on an INTERNAL address?
Network tier cannot be set for internal traffic because it’s always Premium. Leave networkTier unset when addressType is INTERNAL.
When do I need subnetwork vs network?
Use subnetwork for INTERNAL addresses with GCE_ENDPOINT or DNS_RESOLVER purposes. Use network for INTERNAL addresses with VPC_PEERING or IPSEC_INTERCONNECT purposes. External addresses don’t require either.
IP Address Management
How do I reserve a specific internal IP address?
Set addressType to INTERNAL, specify the subnetwork, and provide your desired IP in the address field. The IP must be within the subnetwork’s IP range.
How do I attach a static IP to a VM instance?
Create an external address resource, then reference its address output in your VM’s networkInterfaces.accessConfigs.natIp field.
Resource Management
Why aren't all my labels showing up in the labels field?
The labels field is non-authoritative and only manages labels in your configuration. Use effectiveLabels to see all labels on the resource, including those set by other clients or services.

Using a different cloud?

Explore networking guides for other cloud providers: