Create GCP Cloud Workstations Clusters

The gcp:workstations/workstationCluster:WorkstationCluster resource, part of the Pulumi GCP provider, defines a regional container for Cloud Workstations: its network placement, access controls, and organizational metadata. This guide focuses on four capabilities: VPC network placement, private endpoint configuration, custom domain setup, and resource manager tag binding.

Workstation clusters require VPC networks and subnets. Custom domains require DNS configuration and certificate management outside the cluster resource. The examples are intentionally small. Combine them with your own network infrastructure and governance policies.

Create a cluster in a VPC subnet

Cloud Workstations deployments begin by creating a cluster that defines where workstation VMs will run. The cluster acts as a regional container for workstation configurations.

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

const defaultNetwork = new gcp.compute.Network("default", {
    name: "workstation-cluster",
    autoCreateSubnetworks: false,
});
const defaultSubnetwork = new gcp.compute.Subnetwork("default", {
    name: "workstation-cluster",
    ipCidrRange: "10.0.0.0/24",
    region: "us-central1",
    network: defaultNetwork.name,
});
const _default = new gcp.workstations.WorkstationCluster("default", {
    workstationClusterId: "workstation-cluster",
    network: defaultNetwork.id,
    subnetwork: defaultSubnetwork.id,
    location: "us-central1",
    labels: {
        label: "key",
    },
    annotations: {
        "label-one": "value-one",
    },
});
const project = gcp.organizations.getProject({});
import pulumi
import pulumi_gcp as gcp

default_network = gcp.compute.Network("default",
    name="workstation-cluster",
    auto_create_subnetworks=False)
default_subnetwork = gcp.compute.Subnetwork("default",
    name="workstation-cluster",
    ip_cidr_range="10.0.0.0/24",
    region="us-central1",
    network=default_network.name)
default = gcp.workstations.WorkstationCluster("default",
    workstation_cluster_id="workstation-cluster",
    network=default_network.id,
    subnetwork=default_subnetwork.id,
    location="us-central1",
    labels={
        "label": "key",
    },
    annotations={
        "label-one": "value-one",
    })
project = gcp.organizations.get_project()
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		defaultNetwork, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
			Name:                  pulumi.String("workstation-cluster"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		defaultSubnetwork, err := compute.NewSubnetwork(ctx, "default", &compute.SubnetworkArgs{
			Name:        pulumi.String("workstation-cluster"),
			IpCidrRange: pulumi.String("10.0.0.0/24"),
			Region:      pulumi.String("us-central1"),
			Network:     defaultNetwork.Name,
		})
		if err != nil {
			return err
		}
		_, err = workstations.NewWorkstationCluster(ctx, "default", &workstations.WorkstationClusterArgs{
			WorkstationClusterId: pulumi.String("workstation-cluster"),
			Network:              defaultNetwork.ID(),
			Subnetwork:           defaultSubnetwork.ID(),
			Location:             pulumi.String("us-central1"),
			Labels: pulumi.StringMap{
				"label": pulumi.String("key"),
			},
			Annotations: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
		})
		if err != nil {
			return err
		}
		_, err = organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		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 defaultNetwork = new Gcp.Compute.Network("default", new()
    {
        Name = "workstation-cluster",
        AutoCreateSubnetworks = false,
    });

    var defaultSubnetwork = new Gcp.Compute.Subnetwork("default", new()
    {
        Name = "workstation-cluster",
        IpCidrRange = "10.0.0.0/24",
        Region = "us-central1",
        Network = defaultNetwork.Name,
    });

    var @default = new Gcp.Workstations.WorkstationCluster("default", new()
    {
        WorkstationClusterId = "workstation-cluster",
        Network = defaultNetwork.Id,
        Subnetwork = defaultSubnetwork.Id,
        Location = "us-central1",
        Labels = 
        {
            { "label", "key" },
        },
        Annotations = 
        {
            { "label-one", "value-one" },
        },
    });

    var project = Gcp.Organizations.GetProject.Invoke();

});
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.workstations.WorkstationCluster;
import com.pulumi.gcp.workstations.WorkstationClusterArgs;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
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 defaultNetwork = new Network("defaultNetwork", NetworkArgs.builder()
            .name("workstation-cluster")
            .autoCreateSubnetworks(false)
            .build());

        var defaultSubnetwork = new Subnetwork("defaultSubnetwork", SubnetworkArgs.builder()
            .name("workstation-cluster")
            .ipCidrRange("10.0.0.0/24")
            .region("us-central1")
            .network(defaultNetwork.name())
            .build());

        var default_ = new WorkstationCluster("default", WorkstationClusterArgs.builder()
            .workstationClusterId("workstation-cluster")
            .network(defaultNetwork.id())
            .subnetwork(defaultSubnetwork.id())
            .location("us-central1")
            .labels(Map.of("label", "key"))
            .annotations(Map.of("label-one", "value-one"))
            .build());

        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

    }
}
resources:
  default:
    type: gcp:workstations:WorkstationCluster
    properties:
      workstationClusterId: workstation-cluster
      network: ${defaultNetwork.id}
      subnetwork: ${defaultSubnetwork.id}
      location: us-central1
      labels:
        label: key
      annotations:
        label-one: value-one
  defaultNetwork:
    type: gcp:compute:Network
    name: default
    properties:
      name: workstation-cluster
      autoCreateSubnetworks: false
  defaultSubnetwork:
    type: gcp:compute:Subnetwork
    name: default
    properties:
      name: workstation-cluster
      ipCidrRange: 10.0.0.0/24
      region: us-central1
      network: ${defaultNetwork.name}
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The cluster provisions in a specific region and subnet. The workstationClusterId provides a unique identifier, while network and subnetwork specify where workstation VMs will be placed. The location property determines which GCP region hosts the cluster control plane.

Restrict access with private endpoints

Organizations with strict network policies often require that workstation control plane endpoints remain private, preventing public internet access.

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

const defaultNetwork = new gcp.compute.Network("default", {
    name: "workstation-cluster-private",
    autoCreateSubnetworks: false,
});
const defaultSubnetwork = new gcp.compute.Subnetwork("default", {
    name: "workstation-cluster-private",
    ipCidrRange: "10.0.0.0/24",
    region: "us-central1",
    network: defaultNetwork.name,
});
const _default = new gcp.workstations.WorkstationCluster("default", {
    workstationClusterId: "workstation-cluster-private",
    network: defaultNetwork.id,
    subnetwork: defaultSubnetwork.id,
    location: "us-central1",
    privateClusterConfig: {
        enablePrivateEndpoint: true,
    },
    labels: {
        label: "key",
    },
    annotations: {
        "label-one": "value-one",
    },
});
const project = gcp.organizations.getProject({});
import pulumi
import pulumi_gcp as gcp

default_network = gcp.compute.Network("default",
    name="workstation-cluster-private",
    auto_create_subnetworks=False)
default_subnetwork = gcp.compute.Subnetwork("default",
    name="workstation-cluster-private",
    ip_cidr_range="10.0.0.0/24",
    region="us-central1",
    network=default_network.name)
default = gcp.workstations.WorkstationCluster("default",
    workstation_cluster_id="workstation-cluster-private",
    network=default_network.id,
    subnetwork=default_subnetwork.id,
    location="us-central1",
    private_cluster_config={
        "enable_private_endpoint": True,
    },
    labels={
        "label": "key",
    },
    annotations={
        "label-one": "value-one",
    })
project = gcp.organizations.get_project()
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		defaultNetwork, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
			Name:                  pulumi.String("workstation-cluster-private"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		defaultSubnetwork, err := compute.NewSubnetwork(ctx, "default", &compute.SubnetworkArgs{
			Name:        pulumi.String("workstation-cluster-private"),
			IpCidrRange: pulumi.String("10.0.0.0/24"),
			Region:      pulumi.String("us-central1"),
			Network:     defaultNetwork.Name,
		})
		if err != nil {
			return err
		}
		_, err = workstations.NewWorkstationCluster(ctx, "default", &workstations.WorkstationClusterArgs{
			WorkstationClusterId: pulumi.String("workstation-cluster-private"),
			Network:              defaultNetwork.ID(),
			Subnetwork:           defaultSubnetwork.ID(),
			Location:             pulumi.String("us-central1"),
			PrivateClusterConfig: &workstations.WorkstationClusterPrivateClusterConfigArgs{
				EnablePrivateEndpoint: pulumi.Bool(true),
			},
			Labels: pulumi.StringMap{
				"label": pulumi.String("key"),
			},
			Annotations: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
		})
		if err != nil {
			return err
		}
		_, err = organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		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 defaultNetwork = new Gcp.Compute.Network("default", new()
    {
        Name = "workstation-cluster-private",
        AutoCreateSubnetworks = false,
    });

    var defaultSubnetwork = new Gcp.Compute.Subnetwork("default", new()
    {
        Name = "workstation-cluster-private",
        IpCidrRange = "10.0.0.0/24",
        Region = "us-central1",
        Network = defaultNetwork.Name,
    });

    var @default = new Gcp.Workstations.WorkstationCluster("default", new()
    {
        WorkstationClusterId = "workstation-cluster-private",
        Network = defaultNetwork.Id,
        Subnetwork = defaultSubnetwork.Id,
        Location = "us-central1",
        PrivateClusterConfig = new Gcp.Workstations.Inputs.WorkstationClusterPrivateClusterConfigArgs
        {
            EnablePrivateEndpoint = true,
        },
        Labels = 
        {
            { "label", "key" },
        },
        Annotations = 
        {
            { "label-one", "value-one" },
        },
    });

    var project = Gcp.Organizations.GetProject.Invoke();

});
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.workstations.WorkstationCluster;
import com.pulumi.gcp.workstations.WorkstationClusterArgs;
import com.pulumi.gcp.workstations.inputs.WorkstationClusterPrivateClusterConfigArgs;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
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 defaultNetwork = new Network("defaultNetwork", NetworkArgs.builder()
            .name("workstation-cluster-private")
            .autoCreateSubnetworks(false)
            .build());

        var defaultSubnetwork = new Subnetwork("defaultSubnetwork", SubnetworkArgs.builder()
            .name("workstation-cluster-private")
            .ipCidrRange("10.0.0.0/24")
            .region("us-central1")
            .network(defaultNetwork.name())
            .build());

        var default_ = new WorkstationCluster("default", WorkstationClusterArgs.builder()
            .workstationClusterId("workstation-cluster-private")
            .network(defaultNetwork.id())
            .subnetwork(defaultSubnetwork.id())
            .location("us-central1")
            .privateClusterConfig(WorkstationClusterPrivateClusterConfigArgs.builder()
                .enablePrivateEndpoint(true)
                .build())
            .labels(Map.of("label", "key"))
            .annotations(Map.of("label-one", "value-one"))
            .build());

        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

    }
}
resources:
  default:
    type: gcp:workstations:WorkstationCluster
    properties:
      workstationClusterId: workstation-cluster-private
      network: ${defaultNetwork.id}
      subnetwork: ${defaultSubnetwork.id}
      location: us-central1
      privateClusterConfig:
        enablePrivateEndpoint: true
      labels:
        label: key
      annotations:
        label-one: value-one
  defaultNetwork:
    type: gcp:compute:Network
    name: default
    properties:
      name: workstation-cluster-private
      autoCreateSubnetworks: false
  defaultSubnetwork:
    type: gcp:compute:Subnetwork
    name: default
    properties:
      name: workstation-cluster-private
      ipCidrRange: 10.0.0.0/24
      region: us-central1
      network: ${defaultNetwork.name}
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The privateClusterConfig block controls control plane visibility. Setting enablePrivateEndpoint to true ensures the cluster’s management APIs are only accessible from within your VPC, blocking public internet access. This extends the basic cluster configuration with network isolation.

Configure custom domain for workstation access

Teams that need branded URLs or certificate management often configure custom domains rather than using Google-generated hostnames.

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

const defaultNetwork = new gcp.compute.Network("default", {
    name: "workstation-cluster-custom-domain",
    autoCreateSubnetworks: false,
});
const defaultSubnetwork = new gcp.compute.Subnetwork("default", {
    name: "workstation-cluster-custom-domain",
    ipCidrRange: "10.0.0.0/24",
    region: "us-central1",
    network: defaultNetwork.name,
});
const _default = new gcp.workstations.WorkstationCluster("default", {
    workstationClusterId: "workstation-cluster-custom-domain",
    network: defaultNetwork.id,
    subnetwork: defaultSubnetwork.id,
    location: "us-central1",
    privateClusterConfig: {
        enablePrivateEndpoint: true,
    },
    domainConfig: {
        domain: "workstations.example.com",
    },
    labels: {
        label: "key",
    },
    annotations: {
        "label-one": "value-one",
    },
});
const project = gcp.organizations.getProject({});
import pulumi
import pulumi_gcp as gcp

default_network = gcp.compute.Network("default",
    name="workstation-cluster-custom-domain",
    auto_create_subnetworks=False)
default_subnetwork = gcp.compute.Subnetwork("default",
    name="workstation-cluster-custom-domain",
    ip_cidr_range="10.0.0.0/24",
    region="us-central1",
    network=default_network.name)
default = gcp.workstations.WorkstationCluster("default",
    workstation_cluster_id="workstation-cluster-custom-domain",
    network=default_network.id,
    subnetwork=default_subnetwork.id,
    location="us-central1",
    private_cluster_config={
        "enable_private_endpoint": True,
    },
    domain_config={
        "domain": "workstations.example.com",
    },
    labels={
        "label": "key",
    },
    annotations={
        "label-one": "value-one",
    })
project = gcp.organizations.get_project()
package main

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

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		defaultNetwork, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
			Name:                  pulumi.String("workstation-cluster-custom-domain"),
			AutoCreateSubnetworks: pulumi.Bool(false),
		})
		if err != nil {
			return err
		}
		defaultSubnetwork, err := compute.NewSubnetwork(ctx, "default", &compute.SubnetworkArgs{
			Name:        pulumi.String("workstation-cluster-custom-domain"),
			IpCidrRange: pulumi.String("10.0.0.0/24"),
			Region:      pulumi.String("us-central1"),
			Network:     defaultNetwork.Name,
		})
		if err != nil {
			return err
		}
		_, err = workstations.NewWorkstationCluster(ctx, "default", &workstations.WorkstationClusterArgs{
			WorkstationClusterId: pulumi.String("workstation-cluster-custom-domain"),
			Network:              defaultNetwork.ID(),
			Subnetwork:           defaultSubnetwork.ID(),
			Location:             pulumi.String("us-central1"),
			PrivateClusterConfig: &workstations.WorkstationClusterPrivateClusterConfigArgs{
				EnablePrivateEndpoint: pulumi.Bool(true),
			},
			DomainConfig: &workstations.WorkstationClusterDomainConfigArgs{
				Domain: pulumi.String("workstations.example.com"),
			},
			Labels: pulumi.StringMap{
				"label": pulumi.String("key"),
			},
			Annotations: pulumi.StringMap{
				"label-one": pulumi.String("value-one"),
			},
		})
		if err != nil {
			return err
		}
		_, err = organizations.LookupProject(ctx, &organizations.LookupProjectArgs{}, nil)
		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 defaultNetwork = new Gcp.Compute.Network("default", new()
    {
        Name = "workstation-cluster-custom-domain",
        AutoCreateSubnetworks = false,
    });

    var defaultSubnetwork = new Gcp.Compute.Subnetwork("default", new()
    {
        Name = "workstation-cluster-custom-domain",
        IpCidrRange = "10.0.0.0/24",
        Region = "us-central1",
        Network = defaultNetwork.Name,
    });

    var @default = new Gcp.Workstations.WorkstationCluster("default", new()
    {
        WorkstationClusterId = "workstation-cluster-custom-domain",
        Network = defaultNetwork.Id,
        Subnetwork = defaultSubnetwork.Id,
        Location = "us-central1",
        PrivateClusterConfig = new Gcp.Workstations.Inputs.WorkstationClusterPrivateClusterConfigArgs
        {
            EnablePrivateEndpoint = true,
        },
        DomainConfig = new Gcp.Workstations.Inputs.WorkstationClusterDomainConfigArgs
        {
            Domain = "workstations.example.com",
        },
        Labels = 
        {
            { "label", "key" },
        },
        Annotations = 
        {
            { "label-one", "value-one" },
        },
    });

    var project = Gcp.Organizations.GetProject.Invoke();

});
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.workstations.WorkstationCluster;
import com.pulumi.gcp.workstations.WorkstationClusterArgs;
import com.pulumi.gcp.workstations.inputs.WorkstationClusterPrivateClusterConfigArgs;
import com.pulumi.gcp.workstations.inputs.WorkstationClusterDomainConfigArgs;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
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 defaultNetwork = new Network("defaultNetwork", NetworkArgs.builder()
            .name("workstation-cluster-custom-domain")
            .autoCreateSubnetworks(false)
            .build());

        var defaultSubnetwork = new Subnetwork("defaultSubnetwork", SubnetworkArgs.builder()
            .name("workstation-cluster-custom-domain")
            .ipCidrRange("10.0.0.0/24")
            .region("us-central1")
            .network(defaultNetwork.name())
            .build());

        var default_ = new WorkstationCluster("default", WorkstationClusterArgs.builder()
            .workstationClusterId("workstation-cluster-custom-domain")
            .network(defaultNetwork.id())
            .subnetwork(defaultSubnetwork.id())
            .location("us-central1")
            .privateClusterConfig(WorkstationClusterPrivateClusterConfigArgs.builder()
                .enablePrivateEndpoint(true)
                .build())
            .domainConfig(WorkstationClusterDomainConfigArgs.builder()
                .domain("workstations.example.com")
                .build())
            .labels(Map.of("label", "key"))
            .annotations(Map.of("label-one", "value-one"))
            .build());

        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

    }
}
resources:
  default:
    type: gcp:workstations:WorkstationCluster
    properties:
      workstationClusterId: workstation-cluster-custom-domain
      network: ${defaultNetwork.id}
      subnetwork: ${defaultSubnetwork.id}
      location: us-central1
      privateClusterConfig:
        enablePrivateEndpoint: true
      domainConfig:
        domain: workstations.example.com
      labels:
        label: key
      annotations:
        label-one: value-one
  defaultNetwork:
    type: gcp:compute:Network
    name: default
    properties:
      name: workstation-cluster-custom-domain
      autoCreateSubnetworks: false
  defaultSubnetwork:
    type: gcp:compute:Subnetwork
    name: default
    properties:
      name: workstation-cluster-custom-domain
      ipCidrRange: 10.0.0.0/24
      region: us-central1
      network: ${defaultNetwork.name}
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

The domainConfig block specifies a custom domain for workstation endpoints. Users access workstations via your organization’s domain instead of Google-generated URLs. You must configure DNS records and manage SSL certificates separately; the cluster resource only binds the domain name.

Apply resource manager tags for governance

Enterprise environments use resource manager tags to enforce policies, track costs, and control access across projects.

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

const project = gcp.organizations.getProject({});
const tagKey = new gcp.tags.TagKey("tag_key", {
    parent: project.then(project => `projects/${project.number}`),
    shortName: "keyname",
});
const tagValue = new gcp.tags.TagValue("tag_value", {
    parent: pulumi.interpolate`tagKeys/${tagKey.name}`,
    shortName: "valuename",
});
const defaultNetwork = new gcp.compute.Network("default", {
    name: "workstation-cluster-tags",
    autoCreateSubnetworks: false,
});
const defaultSubnetwork = new gcp.compute.Subnetwork("default", {
    name: "workstation-cluster-tags",
    ipCidrRange: "10.0.0.0/24",
    region: "us-central1",
    network: defaultNetwork.name,
});
const _default = new gcp.workstations.WorkstationCluster("default", {
    workstationClusterId: "workstation-cluster-tags",
    network: defaultNetwork.id,
    subnetwork: defaultSubnetwork.id,
    location: "us-central1",
    tags: pulumi.all([project, tagKey.shortName, tagValue.shortName]).apply(([project, tagKeyShortName, tagValueShortName]) => {
        [`${project.projectId}/${tagKeyShortName}`]: tagValueShortName,
    }),
});
import pulumi
import pulumi_gcp as gcp

project = gcp.organizations.get_project()
tag_key = gcp.tags.TagKey("tag_key",
    parent=f"projects/{project.number}",
    short_name="keyname")
tag_value = gcp.tags.TagValue("tag_value",
    parent=tag_key.name.apply(lambda name: f"tagKeys/{name}"),
    short_name="valuename")
default_network = gcp.compute.Network("default",
    name="workstation-cluster-tags",
    auto_create_subnetworks=False)
default_subnetwork = gcp.compute.Subnetwork("default",
    name="workstation-cluster-tags",
    ip_cidr_range="10.0.0.0/24",
    region="us-central1",
    network=default_network.name)
default = gcp.workstations.WorkstationCluster("default",
    workstation_cluster_id="workstation-cluster-tags",
    network=default_network.id,
    subnetwork=default_subnetwork.id,
    location="us-central1",
    tags=pulumi.Output.all(
        tagKeyShort_name=tag_key.short_name,
        tagValueShort_name=tag_value.short_name
).apply(lambda resolved_outputs: {
        f"{project.project_id}/{resolved_outputs['tagKeyShort_name']}": resolved_outputs['tagValueShort_name'],
    })
)
package main

import (
	"fmt"

	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/organizations"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/tags"
	"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/workstations"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
project, err := organizations.LookupProject(ctx, &organizations.LookupProjectArgs{
}, nil);
if err != nil {
return err
}
tagKey, err := tags.NewTagKey(ctx, "tag_key", &tags.TagKeyArgs{
Parent: pulumi.Sprintf("projects/%v", project.Number),
ShortName: pulumi.String("keyname"),
})
if err != nil {
return err
}
tagValue, err := tags.NewTagValue(ctx, "tag_value", &tags.TagValueArgs{
Parent: tagKey.Name.ApplyT(func(name string) (string, error) {
return fmt.Sprintf("tagKeys/%v", name), nil
}).(pulumi.StringOutput),
ShortName: pulumi.String("valuename"),
})
if err != nil {
return err
}
defaultNetwork, err := compute.NewNetwork(ctx, "default", &compute.NetworkArgs{
Name: pulumi.String("workstation-cluster-tags"),
AutoCreateSubnetworks: pulumi.Bool(false),
})
if err != nil {
return err
}
defaultSubnetwork, err := compute.NewSubnetwork(ctx, "default", &compute.SubnetworkArgs{
Name: pulumi.String("workstation-cluster-tags"),
IpCidrRange: pulumi.String("10.0.0.0/24"),
Region: pulumi.String("us-central1"),
Network: defaultNetwork.Name,
})
if err != nil {
return err
}
_, err = workstations.NewWorkstationCluster(ctx, "default", &workstations.WorkstationClusterArgs{
WorkstationClusterId: pulumi.String("workstation-cluster-tags"),
Network: defaultNetwork.ID(),
Subnetwork: defaultSubnetwork.ID(),
Location: pulumi.String("us-central1"),
Tags: pulumi.All(tagKey.ShortName,tagValue.ShortName).ApplyT(func(_args []interface{}) (map[string]string, error) {
tagKeyShortName := _args[0].(string)
tagValueShortName := _args[1].(string)
return map[string]string{
fmt.Sprintf("%v/%v", project.ProjectId, tagKeyShortName): tagValueShortName,
}, nil
}).(pulumi.Map[string]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 project = Gcp.Organizations.GetProject.Invoke();

    var tagKey = new Gcp.Tags.TagKey("tag_key", new()
    {
        Parent = $"projects/{project.Apply(getProjectResult => getProjectResult.Number)}",
        ShortName = "keyname",
    });

    var tagValue = new Gcp.Tags.TagValue("tag_value", new()
    {
        Parent = tagKey.Name.Apply(name => $"tagKeys/{name}"),
        ShortName = "valuename",
    });

    var defaultNetwork = new Gcp.Compute.Network("default", new()
    {
        Name = "workstation-cluster-tags",
        AutoCreateSubnetworks = false,
    });

    var defaultSubnetwork = new Gcp.Compute.Subnetwork("default", new()
    {
        Name = "workstation-cluster-tags",
        IpCidrRange = "10.0.0.0/24",
        Region = "us-central1",
        Network = defaultNetwork.Name,
    });

    var @default = new Gcp.Workstations.WorkstationCluster("default", new()
    {
        WorkstationClusterId = "workstation-cluster-tags",
        Network = defaultNetwork.Id,
        Subnetwork = defaultSubnetwork.Id,
        Location = "us-central1",
        Tags = Output.Tuple(project, tagKey.ShortName, tagValue.ShortName).Apply(values =>
        {
            var project = values.Item1;
            var tagKeyShortName = values.Item2;
            var tagValueShortName = values.Item3;
            return 
            {
                { $"{project.Apply(getProjectResult => getProjectResult.ProjectId)}/{tagKeyShortName}", tagValueShortName },
            };
        }),
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.organizations.OrganizationsFunctions;
import com.pulumi.gcp.organizations.inputs.GetProjectArgs;
import com.pulumi.gcp.tags.TagKey;
import com.pulumi.gcp.tags.TagKeyArgs;
import com.pulumi.gcp.tags.TagValue;
import com.pulumi.gcp.tags.TagValueArgs;
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.workstations.WorkstationCluster;
import com.pulumi.gcp.workstations.WorkstationClusterArgs;
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) {
        final var project = OrganizationsFunctions.getProject(GetProjectArgs.builder()
            .build());

        var tagKey = new TagKey("tagKey", TagKeyArgs.builder()
            .parent(String.format("projects/%s", project.number()))
            .shortName("keyname")
            .build());

        var tagValue = new TagValue("tagValue", TagValueArgs.builder()
            .parent(tagKey.name().applyValue(_name -> String.format("tagKeys/%s", _name)))
            .shortName("valuename")
            .build());

        var defaultNetwork = new Network("defaultNetwork", NetworkArgs.builder()
            .name("workstation-cluster-tags")
            .autoCreateSubnetworks(false)
            .build());

        var defaultSubnetwork = new Subnetwork("defaultSubnetwork", SubnetworkArgs.builder()
            .name("workstation-cluster-tags")
            .ipCidrRange("10.0.0.0/24")
            .region("us-central1")
            .network(defaultNetwork.name())
            .build());

        var default_ = new WorkstationCluster("default", WorkstationClusterArgs.builder()
            .workstationClusterId("workstation-cluster-tags")
            .network(defaultNetwork.id())
            .subnetwork(defaultSubnetwork.id())
            .location("us-central1")
            .tags(Output.tuple(tagKey.shortName(), tagValue.shortName()).applyValue(values -> {
                var tagKeyShortName = values.t1;
                var tagValueShortName = values.t2;
                return Map.of(String.format("%s/%s", project.projectId(),tagKeyShortName), tagValueShortName);
            }))
            .build());

    }
}
resources:
  tagKey:
    type: gcp:tags:TagKey
    name: tag_key
    properties:
      parent: projects/${project.number}
      shortName: keyname
  tagValue:
    type: gcp:tags:TagValue
    name: tag_value
    properties:
      parent: tagKeys/${tagKey.name}
      shortName: valuename
  default:
    type: gcp:workstations:WorkstationCluster
    properties:
      workstationClusterId: workstation-cluster-tags
      network: ${defaultNetwork.id}
      subnetwork: ${defaultSubnetwork.id}
      location: us-central1
      tags:
        ${project.projectId}/${tagKey.shortName}: ${tagValue.shortName}
  defaultNetwork:
    type: gcp:compute:Network
    name: default
    properties:
      name: workstation-cluster-tags
      autoCreateSubnetworks: false
  defaultSubnetwork:
    type: gcp:compute:Subnetwork
    name: default
    properties:
      name: workstation-cluster-tags
      ipCidrRange: 10.0.0.0/24
      region: us-central1
      network: ${defaultNetwork.name}
variables:
  project:
    fn::invoke:
      function: gcp:organizations:getProject
      arguments: {}

Resource manager tags differ from labels: they’re hierarchical policy bindings managed at the organization level. The example creates a TagKey and TagValue, then binds them to the cluster via the tags property. These tags enable organization-wide policy enforcement and cost allocation.

Beyond these examples

These snippets focus on specific cluster-level features: VPC network placement and subnet configuration, private endpoint access controls, and custom domain configuration and resource manager tags. They’re intentionally minimal rather than full workstation deployments.

The examples may reference pre-existing infrastructure such as VPC networks and subnets (examples create them inline but production typically references existing), DNS records for custom domains, and SSL certificates for custom domains. They focus on configuring the cluster rather than provisioning everything around it.

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

  • Display names and human-readable identifiers (displayName)
  • Annotations for client-specific metadata
  • Labels for resource organization (shown but not explained)
  • IAM permissions and service account configuration

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

Let's create GCP Cloud Workstations Clusters

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Provider & Beta Features
What provider do I need to use for workstation clusters?
Workstation clusters are in beta and require the terraform-provider-google-beta provider.
Resource Configuration & Immutability
What properties can't I change after creating a workstation cluster?
The following properties are immutable: network, project, subnetwork, workstationClusterId, location, and tags. Plan these values carefully during initial creation.
What are the required properties for creating a workstation cluster?
You must provide network, project, subnetwork, and workstationClusterId. All four are required and immutable.
Networking & Connectivity
How do I set up the VPC network for a workstation cluster?
Create a VPC network and subnetwork, then reference them using network (in format projects/{projectNumber}/global/networks/{network_id}) and subnetwork properties. The subnetwork must be part of the specified network.
How do I create a private workstation cluster?
Configure privateClusterConfig with enablePrivateEndpoint set to true.
What firewall rules do I need for workstation VMs?
Workstation VMs need egress access to the controlPlaneIp address to communicate with the service. Configure your firewall rules to allow this traffic.
How do I configure a custom domain for my workstation cluster?
Use the domainConfig property with a domain field specifying your custom domain (e.g., workstations.example.com).
Labels & Annotations
Why don't my annotations and labels match what's in GCP?
The annotations and labels fields are non-authoritative and only manage values present in your configuration. Use effectiveAnnotations and effectiveLabels to see all annotations and labels on the resource, including those set by other clients or services.
Monitoring & Troubleshooting
What does it mean when my workstation cluster is in degraded mode?
The degraded field indicates the resource may require user action to restore full functionality. Check the conditions field for details about the current resource state.

Using a different cloud?

Explore compute guides for other cloud providers: