The gcp:container/cluster:Cluster resource, part of the Pulumi GCP provider, provisions a GKE cluster: its control plane, networking mode, and default node configuration. This guide focuses on three capabilities: node pool management patterns, Autopilot vs Standard cluster modes, and service account integration.
GKE clusters require IAM service accounts for node identity and typically reference VPC networks and subnets. The examples are intentionally small. Combine them with your own networking, security policies, and workload configurations.
Create a cluster with separately managed node pools
Most production deployments separate the cluster resource from node pool resources, allowing teams to add, remove, or modify node pools without recreating the entire cluster.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.serviceaccount.Account("default", {
accountId: "service-account-id",
displayName: "Service Account",
});
const primary = new gcp.container.Cluster("primary", {
name: "my-gke-cluster",
location: "us-central1",
removeDefaultNodePool: true,
initialNodeCount: 1,
});
const primaryPreemptibleNodes = new gcp.container.NodePool("primary_preemptible_nodes", {
name: "my-node-pool",
location: "us-central1",
cluster: primary.name,
nodeCount: 1,
nodeConfig: {
preemptible: true,
machineType: "e2-medium",
serviceAccount: _default.email,
oauthScopes: ["https://www.googleapis.com/auth/cloud-platform"],
},
});
import pulumi
import pulumi_gcp as gcp
default = gcp.serviceaccount.Account("default",
account_id="service-account-id",
display_name="Service Account")
primary = gcp.container.Cluster("primary",
name="my-gke-cluster",
location="us-central1",
remove_default_node_pool=True,
initial_node_count=1)
primary_preemptible_nodes = gcp.container.NodePool("primary_preemptible_nodes",
name="my-node-pool",
location="us-central1",
cluster=primary.name,
node_count=1,
node_config={
"preemptible": True,
"machine_type": "e2-medium",
"service_account": default.email,
"oauth_scopes": ["https://www.googleapis.com/auth/cloud-platform"],
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_default, err := serviceaccount.NewAccount(ctx, "default", &serviceaccount.AccountArgs{
AccountId: pulumi.String("service-account-id"),
DisplayName: pulumi.String("Service Account"),
})
if err != nil {
return err
}
primary, err := container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("my-gke-cluster"),
Location: pulumi.String("us-central1"),
RemoveDefaultNodePool: pulumi.Bool(true),
InitialNodeCount: pulumi.Int(1),
})
if err != nil {
return err
}
_, err = container.NewNodePool(ctx, "primary_preemptible_nodes", &container.NodePoolArgs{
Name: pulumi.String("my-node-pool"),
Location: pulumi.String("us-central1"),
Cluster: primary.Name,
NodeCount: pulumi.Int(1),
NodeConfig: &container.NodePoolNodeConfigArgs{
Preemptible: pulumi.Bool(true),
MachineType: pulumi.String("e2-medium"),
ServiceAccount: _default.Email,
OauthScopes: pulumi.StringArray{
pulumi.String("https://www.googleapis.com/auth/cloud-platform"),
},
},
})
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.ServiceAccount.Account("default", new()
{
AccountId = "service-account-id",
DisplayName = "Service Account",
});
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "my-gke-cluster",
Location = "us-central1",
RemoveDefaultNodePool = true,
InitialNodeCount = 1,
});
var primaryPreemptibleNodes = new Gcp.Container.NodePool("primary_preemptible_nodes", new()
{
Name = "my-node-pool",
Location = "us-central1",
Cluster = primary.Name,
NodeCount = 1,
NodeConfig = new Gcp.Container.Inputs.NodePoolNodeConfigArgs
{
Preemptible = true,
MachineType = "e2-medium",
ServiceAccount = @default.Email,
OauthScopes = new[]
{
"https://www.googleapis.com/auth/cloud-platform",
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.container.NodePool;
import com.pulumi.gcp.container.NodePoolArgs;
import com.pulumi.gcp.container.inputs.NodePoolNodeConfigArgs;
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 Account("default", AccountArgs.builder()
.accountId("service-account-id")
.displayName("Service Account")
.build());
var primary = new Cluster("primary", ClusterArgs.builder()
.name("my-gke-cluster")
.location("us-central1")
.removeDefaultNodePool(true)
.initialNodeCount(1)
.build());
var primaryPreemptibleNodes = new NodePool("primaryPreemptibleNodes", NodePoolArgs.builder()
.name("my-node-pool")
.location("us-central1")
.cluster(primary.name())
.nodeCount(1)
.nodeConfig(NodePoolNodeConfigArgs.builder()
.preemptible(true)
.machineType("e2-medium")
.serviceAccount(default_.email())
.oauthScopes("https://www.googleapis.com/auth/cloud-platform")
.build())
.build());
}
}
resources:
default:
type: gcp:serviceaccount:Account
properties:
accountId: service-account-id
displayName: Service Account
primary:
type: gcp:container:Cluster
properties:
name: my-gke-cluster
location: us-central1
removeDefaultNodePool: true
initialNodeCount: 1
primaryPreemptibleNodes:
type: gcp:container:NodePool
name: primary_preemptible_nodes
properties:
name: my-node-pool
location: us-central1
cluster: ${primary.name}
nodeCount: 1
nodeConfig:
preemptible: true
machineType: e2-medium
serviceAccount: ${default.email}
oauthScopes:
- https://www.googleapis.com/auth/cloud-platform
When removeDefaultNodePool is true, GKE deletes the default node pool immediately after cluster creation. You must set initialNodeCount to at least 1 to satisfy GKE’s creation requirements, even though that pool is removed. The separate gcp.container.NodePool resource then creates your actual node pools, which you can modify independently. The nodeConfig block on the node pool specifies machine type, service account, and OAuth scopes for node identity.
Configure the default node pool inline
For simpler deployments or testing, you can configure nodes directly on the cluster resource using the default node pool.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.serviceaccount.Account("default", {
accountId: "service-account-id",
displayName: "Service Account",
});
const primary = new gcp.container.Cluster("primary", {
name: "marcellus-wallace",
location: "us-central1-a",
initialNodeCount: 3,
nodeConfig: {
serviceAccount: _default.email,
oauthScopes: ["https://www.googleapis.com/auth/cloud-platform"],
labels: {
foo: "bar",
},
tags: [
"foo",
"bar",
],
},
});
import pulumi
import pulumi_gcp as gcp
default = gcp.serviceaccount.Account("default",
account_id="service-account-id",
display_name="Service Account")
primary = gcp.container.Cluster("primary",
name="marcellus-wallace",
location="us-central1-a",
initial_node_count=3,
node_config={
"service_account": default.email,
"oauth_scopes": ["https://www.googleapis.com/auth/cloud-platform"],
"labels": {
"foo": "bar",
},
"tags": [
"foo",
"bar",
],
})
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_default, err := serviceaccount.NewAccount(ctx, "default", &serviceaccount.AccountArgs{
AccountId: pulumi.String("service-account-id"),
DisplayName: pulumi.String("Service Account"),
})
if err != nil {
return err
}
_, err = container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("marcellus-wallace"),
Location: pulumi.String("us-central1-a"),
InitialNodeCount: pulumi.Int(3),
NodeConfig: &container.ClusterNodeConfigArgs{
ServiceAccount: _default.Email,
OauthScopes: pulumi.StringArray{
pulumi.String("https://www.googleapis.com/auth/cloud-platform"),
},
Labels: pulumi.StringMap{
"foo": pulumi.String("bar"),
},
Tags: pulumi.StringArray{
pulumi.String("foo"),
pulumi.String("bar"),
},
},
})
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.ServiceAccount.Account("default", new()
{
AccountId = "service-account-id",
DisplayName = "Service Account",
});
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "marcellus-wallace",
Location = "us-central1-a",
InitialNodeCount = 3,
NodeConfig = new Gcp.Container.Inputs.ClusterNodeConfigArgs
{
ServiceAccount = @default.Email,
OauthScopes = new[]
{
"https://www.googleapis.com/auth/cloud-platform",
},
Labels =
{
{ "foo", "bar" },
},
Tags = new[]
{
"foo",
"bar",
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
import com.pulumi.gcp.container.inputs.ClusterNodeConfigArgs;
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 Account("default", AccountArgs.builder()
.accountId("service-account-id")
.displayName("Service Account")
.build());
var primary = new Cluster("primary", ClusterArgs.builder()
.name("marcellus-wallace")
.location("us-central1-a")
.initialNodeCount(3)
.nodeConfig(ClusterNodeConfigArgs.builder()
.serviceAccount(default_.email())
.oauthScopes("https://www.googleapis.com/auth/cloud-platform")
.labels(Map.of("foo", "bar"))
.tags(
"foo",
"bar")
.build())
.build());
}
}
resources:
default:
type: gcp:serviceaccount:Account
properties:
accountId: service-account-id
displayName: Service Account
primary:
type: gcp:container:Cluster
properties:
name: marcellus-wallace
location: us-central1-a
initialNodeCount: 3
nodeConfig:
serviceAccount: ${default.email}
oauthScopes:
- https://www.googleapis.com/auth/cloud-platform
labels:
foo: bar
tags:
- foo
- bar
The nodeConfig block defines node properties: service account for GCP API access, OAuth scopes for permissions, and labels/tags for organization. This approach is simpler but less flexible; changing node configuration requires recreating the cluster. For production workloads, use separately managed node pools instead.
Enable Autopilot for fully managed infrastructure
Autopilot mode shifts node management to Google, automatically provisioning and scaling infrastructure based on workload requirements.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.serviceaccount.Account("default", {
accountId: "service-account-id",
displayName: "Service Account",
});
const primary = new gcp.container.Cluster("primary", {
name: "marcellus-wallace",
location: "us-central1-a",
enableAutopilot: true,
});
import pulumi
import pulumi_gcp as gcp
default = gcp.serviceaccount.Account("default",
account_id="service-account-id",
display_name="Service Account")
primary = gcp.container.Cluster("primary",
name="marcellus-wallace",
location="us-central1-a",
enable_autopilot=True)
package main
import (
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/container"
"github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/serviceaccount"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := serviceaccount.NewAccount(ctx, "default", &serviceaccount.AccountArgs{
AccountId: pulumi.String("service-account-id"),
DisplayName: pulumi.String("Service Account"),
})
if err != nil {
return err
}
_, err = container.NewCluster(ctx, "primary", &container.ClusterArgs{
Name: pulumi.String("marcellus-wallace"),
Location: pulumi.String("us-central1-a"),
EnableAutopilot: pulumi.Bool(true),
})
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.ServiceAccount.Account("default", new()
{
AccountId = "service-account-id",
DisplayName = "Service Account",
});
var primary = new Gcp.Container.Cluster("primary", new()
{
Name = "marcellus-wallace",
Location = "us-central1-a",
EnableAutopilot = true,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.serviceaccount.Account;
import com.pulumi.gcp.serviceaccount.AccountArgs;
import com.pulumi.gcp.container.Cluster;
import com.pulumi.gcp.container.ClusterArgs;
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 Account("default", AccountArgs.builder()
.accountId("service-account-id")
.displayName("Service Account")
.build());
var primary = new Cluster("primary", ClusterArgs.builder()
.name("marcellus-wallace")
.location("us-central1-a")
.enableAutopilot(true)
.build());
}
}
resources:
default:
type: gcp:serviceaccount:Account
properties:
accountId: service-account-id
displayName: Service Account
primary:
type: gcp:container:Cluster
properties:
name: marcellus-wallace
location: us-central1-a
enableAutopilot: true
Setting enableAutopilot to true activates Autopilot mode, where GKE manages all node provisioning, scaling, and configuration. You don’t specify nodeConfig, initialNodeCount, or node pools; Google handles infrastructure based on your workload demands. Note that Autopilot restricts certain Standard GKE features; see the official documentation for feature availability.
Beyond these examples
These snippets focus on specific cluster-level features: node pool management strategies, Autopilot vs Standard cluster modes, and service account integration. They’re intentionally minimal rather than full Kubernetes deployments.
The examples may reference pre-existing infrastructure such as IAM service accounts for node identity, and VPC networks and subnets (examples use defaults). They focus on configuring the cluster rather than provisioning everything around it.
To keep things focused, common cluster patterns are omitted, including:
- VPC-native networking (ipAllocationPolicy)
- Private clusters and master authorized networks
- Release channels and version management
- Workload Identity and security features
- Monitoring, logging, and observability configuration
- Add-ons (HTTP load balancing, network policy, etc.)
These omissions are intentional: the goal is to illustrate how each cluster feature is wired, not provide drop-in Kubernetes platforms. See the GKE Cluster resource reference for all available configuration options.
Let's create and Configure GKE Kubernetes Clusters
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Cluster Lifecycle & Deletion
deletionProtection = false and run pulumi up to write the field to state before you can destroy a cluster.enableKubernetesAlpha = true cannot be upgraded and will be automatically deleted after 30 days. Only use this for temporary testing, not production workloads.Node Pool Management
gcp.container.NodePool resources instead of using the inline nodePools property. Set removeDefaultNodePool = true and initialNodeCount = 1 on the cluster. Node pools defined inline cannot be changed after cluster creation without deleting and recreating the entire cluster.gcp.container.NodePool resources rather than the inline nodePools property. This allows you to add and remove node pools without recreating the cluster.Versioning & Upgrades
minMasterVersion is the minimum version you specify, but GKE auto-updates the master beyond this. Use the read-only masterVersion output to get the actual current master version.gcp.container.getEngineVersions datasource with versionPrefix to approximate fuzzy versions.releaseChannel field stops provider management but doesn’t unenroll the cluster. To unenroll, set the channel to "UNSPECIFIED" instead.location parameter. Regions can have different supported versions than their zones, and not all zones in a region support the same version.Cluster Configuration
enableAutopilot = true, certain Standard GKE features are not available. See the official GKE documentation for a comparison of available features between Autopilot and Standard modes.enableFqdnNetworkPolicy on existing Standard clusters, you must manually restart the GKE Dataplane V2 anetd DaemonSet.Using a different cloud?
Explore containers guides for other cloud providers: