The gcp:compute/disk:Disk resource, part of the Pulumi GCP provider, provisions persistent disks that provide durable block storage for Compute Engine instances. This guide focuses on three capabilities: creating disks from public images, asynchronous cross-zone replication, and guest OS features and licensing.
Persistent disks exist independently from VM instances and reference public images or snapshots. The examples are intentionally small. Combine them with your own VM instances, encryption keys, and snapshot policies.
Create a disk from a public image
Most deployments start by creating a disk from a public image, specifying the disk type and zone.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.compute.Disk("default", {
name: "test-disk",
type: "pd-ssd",
zone: "us-central1-a",
image: "debian-11-bullseye-v20220719",
labels: {
environment: "dev",
},
physicalBlockSizeBytes: 4096,
});
import pulumi
import pulumi_gcp as gcp
default = gcp.compute.Disk("default",
name="test-disk",
type="pd-ssd",
zone="us-central1-a",
image="debian-11-bullseye-v20220719",
labels={
"environment": "dev",
},
physical_block_size_bytes=4096)
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.NewDisk(ctx, "default", &compute.DiskArgs{
Name: pulumi.String("test-disk"),
Type: pulumi.String("pd-ssd"),
Zone: pulumi.String("us-central1-a"),
Image: pulumi.String("debian-11-bullseye-v20220719"),
Labels: pulumi.StringMap{
"environment": pulumi.String("dev"),
},
PhysicalBlockSizeBytes: pulumi.Int(4096),
})
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.Disk("default", new()
{
Name = "test-disk",
Type = "pd-ssd",
Zone = "us-central1-a",
Image = "debian-11-bullseye-v20220719",
Labels =
{
{ "environment", "dev" },
},
PhysicalBlockSizeBytes = 4096,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Disk;
import com.pulumi.gcp.compute.DiskArgs;
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 Disk("default", DiskArgs.builder()
.name("test-disk")
.type("pd-ssd")
.zone("us-central1-a")
.image("debian-11-bullseye-v20220719")
.labels(Map.of("environment", "dev"))
.physicalBlockSizeBytes(4096)
.build());
}
}
resources:
default:
type: gcp:compute:Disk
properties:
name: test-disk
type: pd-ssd
zone: us-central1-a
image: debian-11-bullseye-v20220719
labels:
environment: dev
physicalBlockSizeBytes: 4096
The image property references a public Debian image by name. The type property sets the disk to SSD (pd-ssd) rather than standard HDD. The physicalBlockSizeBytes property controls the block size; 4096 bytes is standard for most workloads. Labels provide metadata for organization and cost tracking.
Set up asynchronous replication between zones
Applications requiring disaster recovery can replicate disks across zones with some replication lag.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const primary = new gcp.compute.Disk("primary", {
name: "async-test-disk",
type: "pd-ssd",
zone: "us-central1-a",
physicalBlockSizeBytes: 4096,
});
const secondary = new gcp.compute.Disk("secondary", {
name: "async-secondary-test-disk",
type: "pd-ssd",
zone: "us-east1-c",
asyncPrimaryDisk: {
disk: primary.id,
},
physicalBlockSizeBytes: 4096,
});
import pulumi
import pulumi_gcp as gcp
primary = gcp.compute.Disk("primary",
name="async-test-disk",
type="pd-ssd",
zone="us-central1-a",
physical_block_size_bytes=4096)
secondary = gcp.compute.Disk("secondary",
name="async-secondary-test-disk",
type="pd-ssd",
zone="us-east1-c",
async_primary_disk={
"disk": primary.id,
},
physical_block_size_bytes=4096)
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 {
primary, err := compute.NewDisk(ctx, "primary", &compute.DiskArgs{
Name: pulumi.String("async-test-disk"),
Type: pulumi.String("pd-ssd"),
Zone: pulumi.String("us-central1-a"),
PhysicalBlockSizeBytes: pulumi.Int(4096),
})
if err != nil {
return err
}
_, err = compute.NewDisk(ctx, "secondary", &compute.DiskArgs{
Name: pulumi.String("async-secondary-test-disk"),
Type: pulumi.String("pd-ssd"),
Zone: pulumi.String("us-east1-c"),
AsyncPrimaryDisk: &compute.DiskAsyncPrimaryDiskArgs{
Disk: primary.ID(),
},
PhysicalBlockSizeBytes: pulumi.Int(4096),
})
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 primary = new Gcp.Compute.Disk("primary", new()
{
Name = "async-test-disk",
Type = "pd-ssd",
Zone = "us-central1-a",
PhysicalBlockSizeBytes = 4096,
});
var secondary = new Gcp.Compute.Disk("secondary", new()
{
Name = "async-secondary-test-disk",
Type = "pd-ssd",
Zone = "us-east1-c",
AsyncPrimaryDisk = new Gcp.Compute.Inputs.DiskAsyncPrimaryDiskArgs
{
Disk = primary.Id,
},
PhysicalBlockSizeBytes = 4096,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Disk;
import com.pulumi.gcp.compute.DiskArgs;
import com.pulumi.gcp.compute.inputs.DiskAsyncPrimaryDiskArgs;
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 primary = new Disk("primary", DiskArgs.builder()
.name("async-test-disk")
.type("pd-ssd")
.zone("us-central1-a")
.physicalBlockSizeBytes(4096)
.build());
var secondary = new Disk("secondary", DiskArgs.builder()
.name("async-secondary-test-disk")
.type("pd-ssd")
.zone("us-east1-c")
.asyncPrimaryDisk(DiskAsyncPrimaryDiskArgs.builder()
.disk(primary.id())
.build())
.physicalBlockSizeBytes(4096)
.build());
}
}
resources:
primary:
type: gcp:compute:Disk
properties:
name: async-test-disk
type: pd-ssd
zone: us-central1-a
physicalBlockSizeBytes: 4096
secondary:
type: gcp:compute:Disk
properties:
name: async-secondary-test-disk
type: pd-ssd
zone: us-east1-c
asyncPrimaryDisk:
disk: ${primary.id}
physicalBlockSizeBytes: 4096
The asyncPrimaryDisk property links the secondary disk to the primary disk’s ID. GCP handles replication automatically; the secondary disk mirrors the primary’s data asynchronously. Both disks must be the same type and size.
Configure guest OS features and licenses
Bootable disks for specialized operating systems need guest OS features and licenses declared explicitly.
import * as pulumi from "@pulumi/pulumi";
import * as gcp from "@pulumi/gcp";
const _default = new gcp.compute.Disk("default", {
name: "test-disk-features",
type: "pd-ssd",
zone: "us-central1-a",
labels: {
environment: "dev",
},
guestOsFeatures: [
{
type: "SECURE_BOOT",
},
{
type: "MULTI_IP_SUBNET",
},
{
type: "WINDOWS",
},
],
licenses: ["https://www.googleapis.com/compute/v1/projects/windows-cloud/global/licenses/windows-server-core"],
physicalBlockSizeBytes: 4096,
});
import pulumi
import pulumi_gcp as gcp
default = gcp.compute.Disk("default",
name="test-disk-features",
type="pd-ssd",
zone="us-central1-a",
labels={
"environment": "dev",
},
guest_os_features=[
{
"type": "SECURE_BOOT",
},
{
"type": "MULTI_IP_SUBNET",
},
{
"type": "WINDOWS",
},
],
licenses=["https://www.googleapis.com/compute/v1/projects/windows-cloud/global/licenses/windows-server-core"],
physical_block_size_bytes=4096)
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.NewDisk(ctx, "default", &compute.DiskArgs{
Name: pulumi.String("test-disk-features"),
Type: pulumi.String("pd-ssd"),
Zone: pulumi.String("us-central1-a"),
Labels: pulumi.StringMap{
"environment": pulumi.String("dev"),
},
GuestOsFeatures: compute.DiskGuestOsFeatureArray{
&compute.DiskGuestOsFeatureArgs{
Type: pulumi.String("SECURE_BOOT"),
},
&compute.DiskGuestOsFeatureArgs{
Type: pulumi.String("MULTI_IP_SUBNET"),
},
&compute.DiskGuestOsFeatureArgs{
Type: pulumi.String("WINDOWS"),
},
},
Licenses: pulumi.StringArray{
pulumi.String("https://www.googleapis.com/compute/v1/projects/windows-cloud/global/licenses/windows-server-core"),
},
PhysicalBlockSizeBytes: pulumi.Int(4096),
})
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.Disk("default", new()
{
Name = "test-disk-features",
Type = "pd-ssd",
Zone = "us-central1-a",
Labels =
{
{ "environment", "dev" },
},
GuestOsFeatures = new[]
{
new Gcp.Compute.Inputs.DiskGuestOsFeatureArgs
{
Type = "SECURE_BOOT",
},
new Gcp.Compute.Inputs.DiskGuestOsFeatureArgs
{
Type = "MULTI_IP_SUBNET",
},
new Gcp.Compute.Inputs.DiskGuestOsFeatureArgs
{
Type = "WINDOWS",
},
},
Licenses = new[]
{
"https://www.googleapis.com/compute/v1/projects/windows-cloud/global/licenses/windows-server-core",
},
PhysicalBlockSizeBytes = 4096,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.gcp.compute.Disk;
import com.pulumi.gcp.compute.DiskArgs;
import com.pulumi.gcp.compute.inputs.DiskGuestOsFeatureArgs;
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 Disk("default", DiskArgs.builder()
.name("test-disk-features")
.type("pd-ssd")
.zone("us-central1-a")
.labels(Map.of("environment", "dev"))
.guestOsFeatures(
DiskGuestOsFeatureArgs.builder()
.type("SECURE_BOOT")
.build(),
DiskGuestOsFeatureArgs.builder()
.type("MULTI_IP_SUBNET")
.build(),
DiskGuestOsFeatureArgs.builder()
.type("WINDOWS")
.build())
.licenses("https://www.googleapis.com/compute/v1/projects/windows-cloud/global/licenses/windows-server-core")
.physicalBlockSizeBytes(4096)
.build());
}
}
resources:
default:
type: gcp:compute:Disk
properties:
name: test-disk-features
type: pd-ssd
zone: us-central1-a
labels:
environment: dev
guestOsFeatures:
- type: SECURE_BOOT
- type: MULTI_IP_SUBNET
- type: WINDOWS
licenses:
- https://www.googleapis.com/compute/v1/projects/windows-cloud/global/licenses/windows-server-core
physicalBlockSizeBytes: 4096
The guestOsFeatures array enables capabilities like SECURE_BOOT, MULTI_IP_SUBNET, and WINDOWS. The licenses property references the Windows Server Core license URL. These settings are immutable after disk creation and only apply to bootable disks.
Beyond these examples
These snippets focus on specific disk-level features: image-based disk creation, cross-zone asynchronous replication, and guest OS features and licensing. They’re intentionally minimal rather than full storage solutions.
The examples may reference pre-existing infrastructure such as GCP projects with compute API enabled, and public images (Debian, Windows) in the project or image family. They focus on configuring the disk rather than provisioning everything around it.
To keep things focused, common disk patterns are omitted, including:
- Disk sizing and resizing (size property)
- Snapshot-based disk creation (snapshot property)
- Customer-managed encryption keys (diskEncryptionKey)
- Performance tuning (provisionedIops, provisionedThroughput)
- Resource policies for automated snapshots (resourcePolicies)
These omissions are intentional: the goal is to illustrate how each disk feature is wired, not provide drop-in storage modules. See the Compute Disk resource reference for all available configuration options.
Let's create and Manage GCP Persistent Disks
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Disk Sizing & Resizing
lifecycle.prevent_destroy to prevent accidental deletion. Only upsizing is supported as an update operation.size value must not be less than the size of the source image or snapshot.Hyperdisk-Specific Features
provisionedIops and provisionedThroughput once every 4 hours. To update more frequently, you’ll need to manually delete and recreate the disk.accessMode (READ_WRITE_MANY, READ_ONLY_SINGLE) is only valid for Hyperdisk disk types. The default READ_WRITE_SINGLE works with all disk types.enableConfidentialCompute to true and provide diskEncryptionKey. This feature is only supported on Hyperdisk skus and is immutable after creation.Immutability & Recreation
name, zone, type, physicalBlockSizeBytes, image, snapshot, sourceDisk, guestOsFeatures, licenses, diskEncryptionKey, multiWriter, enableConfidentialCompute, resourcePolicies, and several others. Check the schema for the complete list.Source Options & Data
You have five options:
- Image - Use
imageto create from a machine image - Snapshot - Use
snapshotto restore from a snapshot - Clone disk - Use
sourceDiskto clone an existing disk - Instant snapshot - Use
sourceInstantSnapshotfor instant snapshot restore - Cloud Storage - Use
sourceStorageObjectwith a GCS tarball (.tar.gz) or vmdk file
Configuration & Features
multiWriter to true (immutable). For Hyperdisk, you can also use accessMode with READ_WRITE_MANY or READ_ONLY_SINGLE for multi-instance attachment.createSnapshotBeforeDestroy to true. The snapshot will be named {{disk-name}}-YYYYMMDD-HHmm by default, or use createSnapshotBeforeDestroyPrefix for a custom prefix.resourcePolicies property for updates, as it doesn’t support updating more than one policy at a time. Instead, use the gcp.compute.DiskResourcePolicyAttachment resource.labels field is non-authoritative and only manages labels present in your configuration. Use effectiveLabels to see all labels on the resource, including those set by other clients or services.