Google Cloud persistent disks attach to running VMs without downtime. Create the disk with gcloud compute disks create, attach it with gcloud compute instances attach-disk, then format and mount it from inside the VM. The two things people get wrong: skipping the snapshot before mounting a disk that already has data, and using the device path (/dev/sdb) in /etc/fstab instead of the UUID, which guarantees a broken fstab the next time the kernel reorders devices. Below: the exact procedure for Console, gcloud, and Terraform; the disk-type comparison; multi-writer caveats; auto-delete behavior; and the verification commands that prove the mount survives a reboot.
How do I add a persistent disk to a Google Cloud VM?
Create the disk with gcloud compute disks create DISK_NAME --size SIZE --type pd-balanced --zone ZONE, then attach it to the running instance with gcloud compute instances attach-disk VM_NAME --disk DISK_NAME --zone ZONE. The VM sees the new block device immediately (run lsblk to confirm). Format with sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb, create a mount point, mount it, then add a /etc/fstab entry using UUID= (not /dev/sdb) so the mount survives reboots. The disk stays available across VM stops, starts, and migrations. Auto-delete defaults to off for disks attached this way, which is the safe default.
Jump to:
- Before you start: prerequisites and snapshot
- Step 1: Create the persistent disk
- Step 2: Attach the disk to the VM
- Step 3: Format and mount inside the VM
- Step 4: Persist the mount with fstab and UUID
- Disk types compared
- Console vs gcloud vs Terraform
- Auto-delete and multi-writer mode
- Caveats and gotchas
- Verify the disk survives a reboot
- What to do next
- FAQ
Before you start: prerequisites and snapshot
You need the Compute Instance Admin (v1) role (roles/compute.instanceAdmin.v1) or equivalent. The VM can be running. No reboot at any point.
If the disk already contains data (snapshot restore, migration, import), snapshot first:
gcloud compute snapshots create pre-attach-$(date +%Y%m%d-%H%M) \
--source-disk DISK_NAME \
--source-disk-zone ZONESnapshots cost cents and give you a single-command rollback. Skip it only on a fresh empty disk.
Step 1: Create the persistent disk
A 500 GB balanced disk in the same zone as the VM:
gcloud compute disks create data-disk-01 \
--size 500 \
--type pd-balanced \
--zone us-central1-a--size is in GiB. --type accepts pd-standard, pd-balanced, pd-ssd, or pd-extreme (compared below). --zone must match the VM. For replicated disks use --region plus --replica-zones.
To restore from a snapshot, swap --size for --source-snapshot:
gcloud compute disks create data-disk-01 \
--source-snapshot SNAPSHOT_NAME \
--type pd-balanced \
--zone us-central1-aSize is inherited from the snapshot; you can enlarge on creation by passing --size (must be larger than the source).
Step 2: Attach the disk to the VM
gcloud compute instances attach-disk web-01 \
--disk data-disk-01 \
--device-name data-disk-01 \
--zone us-central1-a--device-name is optional but worth setting. It creates a stable symlink at /dev/disk/by-id/google-DEVICE_NAME inside the VM, which beats referencing /dev/sdb in scripts.
Attach completes in seconds. From inside the VM, confirm with lsblk:
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 100G 0 disk
└─sda1 8:1 0 100G 0 part /
sdb 8:16 0 500G 0 disk
sdb (or nvme0n2 on third-gen instances like C3, N4) is the new disk. No partitions, no filesystem.
Console method: Compute Engine, VM instances, click the VM, Edit, Additional disks, Attach existing disk. Same effect, slower for batches.
Step 3: Format and mount inside the VM
Identify the device via the stable symlink GCP provides:
ls -l /dev/disk/by-id/google-*You should see google-data-disk-01 -> ../../sdb. Format with ext4 using Google's recommended flags for persistent-disk performance:
sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb-m 0skips the default 5% root reservation (wasted on a data disk).lazy_itable_init=0,lazy_journal_init=0initializes the inode table and journal up front. Slightly slower mkfs, faster steady-state.discardissues a TRIM at format time so the storage backend knows which blocks are free.
This is destructive. Double-check the device path on a system with multiple disks.
Mount point, mount, permissions:
sudo mkdir -p /mnt/disks/data
sudo mount -o discard,defaults /dev/sdb /mnt/disks/data
sudo chmod a+w /mnt/disks/dataThe discard mount option enables continuous TRIM. On GCP persistent disks it is a small, free win.
Confirm with df -h /mnt/disks/data.
Step 4: Persist the mount with fstab and UUID
The mount is live but ephemeral. Reboot now and the disk is gone.
The fix is /etc/fstab. Two ways to write the entry, only one is right.
Wrong (the version that bites people):
/dev/sdb /mnt/disks/data ext4 discard,defaults 0 2
Works until you attach another disk, detach and re-attach the boot disk, or move to an instance type with different controllers. The kernel can re-letter the devices and /dev/sdb ends up being a different disk, or nothing at all. The VM either mounts the wrong filesystem at /mnt/disks/data or fails boot entirely.
Right (UUID, baked into the filesystem at format time):
sudo blkid /dev/sdbOutput:
/dev/sdb: UUID="d2a4f1d0-9e5a-4b1c-83d9-7f0c2a8b6a3e" TYPE="ext4"
Add it to fstab:
echo "UUID=d2a4f1d0-9e5a-4b1c-83d9-7f0c2a8b6a3e /mnt/disks/data ext4 discard,nofail 0 2" \
| sudo tee -a /etc/fstabnofail matters. Without it, a missing disk (detached, migrated, snapshot-restored elsewhere) prevents the VM from booting at all. With it, boot continues and you can SSH in to diagnose.
Test without rebooting:
sudo umount /mnt/disks/data
sudo mount -a
df -h /mnt/disks/dataClean output from mount -a plus the disk in df -h means the line is correct.
Critical: before you ever run gcloud compute instances detach-disk, remove or comment out the fstab line. Otherwise the next reboot fails because fstab references a disk that no longer exists.
Disk types compared
GCP offers four persistent disk types. The choice is a cost/performance trade-off.
| Type | Reference | Media | Min size | Max size | Best for |
|---|---|---|---|---|---|
| Standard | pd-standard | HDD-backed | 10 GiB | 64 TiB | Cold storage, archives, backup targets |
| Balanced | pd-balanced | SSD-backed | 10 GiB | 64 TiB | General workloads, boot disks, web servers |
| Performance SSD | pd-ssd | SSD-backed | 10 GiB | 64 TiB | Databases, high-IOPS workloads |
| Extreme | pd-extreme | SSD with provisioned IOPS | 500 GiB | 64 TiB | Very high IOPS, in-memory caches |
Rough cost ratio in us-central1: pd-standard ~$0.04/GB-month, pd-balanced ~$0.10, pd-ssd ~$0.17, pd-extreme priced per-IOPS on top of capacity.
Default to pd-balanced. Use pd-standard only for cold-tier data. Reach for pd-ssd when a latency-sensitive database sits on top. Use pd-extreme only when you have measured pd-ssd and need more IOPS than its capacity-linked ceiling allows.
Performance scales with capacity on most types. A 100 GB pd-balanced has a much lower IOPS ceiling than a 1 TB pd-balanced. If a workload feels slow, growing the disk is often the fastest fix: see How to Increase Google Cloud VM Disk Size Without Rebooting for the live-resize procedure.
Console vs gcloud vs Terraform
Same operation, three interfaces. Pick by workflow:
| Approach | Best for | Command / action |
|---|---|---|
| Cloud Console | One-off disks, exploring | Compute Engine, Disks, Create disk, then VM, Edit, Attach |
gcloud CLI | Scripted, batched | gcloud compute disks create plus attach-disk |
| Terraform | Infra-as-code, audit trails | google_compute_disk plus google_compute_attached_disk |
| REST API | Custom tooling | POST /compute/v1/projects/.../disks |
The Terraform snippet:
resource "google_compute_disk" "data" {
name = "data-disk-01"
type = "pd-balanced"
zone = "us-central1-a"
size = 500
labels = {
environment = "production"
role = "data"
}
}
resource "google_compute_attached_disk" "data" {
disk = google_compute_disk.data.self_link
instance = google_compute_instance.web.self_link
device_name = "data-disk-01"
}
Two resources, not one. google_compute_disk creates the disk; google_compute_attached_disk attaches without forcing a VM replacement. The attached_disk block on google_compute_instance would tie the disk's lifecycle to the VM, which is rarely what you want for data disks.
The format / mount / fstab steps still happen inside the VM. Use a remote-exec provisioner, an Ansible playbook, or a startup script.
To attach disks across a fleet, a Bash for loop over the VM list does it cleanly:
for vm in web-01 web-02 web-03; do
gcloud compute disks create "${vm}-data" \
--size 500 --type pd-balanced --zone us-central1-a
gcloud compute instances attach-disk "$vm" \
--disk "${vm}-data" --device-name data \
--zone us-central1-a
doneAuto-delete and multi-writer mode
By default a disk attached with gcloud compute instances attach-disk is not deleted when the VM is deleted. The disk persists as an unattached resource. This is the safe default for data disks.
Flip it with --auto-delete at attach time, or change later:
gcloud compute instances set-disk-auto-delete web-01 \
--disk data-disk-01 \
--no-auto-delete \
--zone us-central1-aFor boot disks the Console default is auto-delete ON, which is a disaster for stateful boot disks. Check with gcloud compute instances describe VM_NAME and look at autoDelete on each disk.
Multi-writer mode attaches one disk to two VMs read-write. Caveats are significant:
pd-ssdonly. Not pd-standard, pd-balanced, or pd-extreme.- Two VMs maximum.
- Same zone.
- No snapshots while active.
- ext4 and XFS corrupt themselves in multi-writer mode. You need a cluster-aware filesystem (OCFS2, GFS2).
The flag is --multi-writer on disk creation. For read-only fanout across many VMs, attach as --mode ro instead, which works on all disk types and any number of VMs.
For most use cases, NFS, GCS Fuse, or Filestore is a better fit than multi-writer pd-ssd.
Caveats and gotchas
fstab without UUID is a time bomb. Single most common reason a GCP VM fails to boot after a disk operation. Always use UUID=. Always add nofail.
Zonal disks cannot attach across zones. Migrating a VM across zones means snapshot, restore in the new zone, attach the new disk.
Maximum 127 secondary disks per VM, 257 TB total attached capacity.
Don't mount a snapshot-restored disk without fsck -n first if the snapshot came from a VM that may have crashed mid-write.
NVMe instance types (C3, N4, third-gen+) use /dev/nvme0n2, not /dev/sdb. The /dev/disk/by-id/google-DEVICE_NAME symlink works regardless.
Encryption at rest is on by default with Google-managed keys. For BYOK use --kms-key at disk-creation time.
Verify the disk survives a reboot
The only verification that actually proves the fstab entry works:
sudo rebootWait for the VM to come back, SSH in, confirm the mount is present without manual intervention:
df -h /mnt/disks/data
mount | grep /mnt/disks/dataIf the mount is missing, check dmesg | tail and journalctl -xb | grep -i fstab. The usual suspects: UUID typo from a copy-paste, filesystem type mismatch (ext4 declared but disk is XFS), missing nofail blocking boot at the fstab stage.
What to do next
- The companion article: How to Increase Google Cloud VM Disk Size Without Rebooting covers the live-resize flow for when the disk you just attached fills up.
- The canonical reference: Google Cloud's Add a persistent disk guide documents gcloud, Console, and REST flows.
- Format and mount a disk on a Linux VM is the upstream version of the mkfs / mount / fstab section.
- How to Export or Backup All MySQL Databases: the most common reason to attach a dedicated data disk is giving a database its own block device with its own snapshot cadence.
- Bash For Loops and Bash While Loops for provisioning across many VMs.
- SSH Cheat Sheet: the minimum SSH reference for the operations on the VM side.
FAQ
Run gcloud compute disks create DISK_NAME --size SIZE --type pd-balanced --zone ZONE, then gcloud compute instances attach-disk VM_NAME --disk DISK_NAME --zone ZONE. The new block device appears in lsblk immediately.
Format with sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb, create a mount point, mount it, then add a UUID-based /etc/fstab entry so the mount survives reboots. The VM stays running throughout.
Device paths like /dev/sdb are not stable. The kernel assigns them in the order it sees disks at boot. Attach another disk, detach and re-attach the boot disk, or move to an instance type with different controllers, and the same physical disk can show up as /dev/sdc or /dev/nvme0n2.
Filesystem UUIDs are baked into the filesystem at format time and never change. UUID=... references the filesystem directly, not whichever device letter won the boot-time race. Always add nofail so a missing disk does not block boot.
pd-standard is HDD-backed: cheap, slow, fine for cold storage and backup targets. pd-balanced is SSD-backed at a lower cost than pd-ssd: the right default for most workloads. pd-ssd is higher-performance SSD: reach for it when a latency-sensitive database sits on top. pd-extreme is SSD with provisioned IOPS: only when you have measured pd-ssd and need more.
Default to pd-balanced. The cost gap to pd-ssd is small, the performance gap to pd-standard is large, and most workloads land in the balanced sweet spot.
Not by default for disks attached via gcloud compute instances attach-disk. The disk survives VM deletion as an unattached resource you can re-attach or delete manually.
The exception is boot disks, where the Console default is auto-delete ON. Check with gcloud compute instances describe VM_NAME and look at the autoDelete field on each disk. Flip it with gcloud compute instances set-disk-auto-delete (or --no-auto-delete) if it is set incorrectly.
For read-only access, yes: attach with --mode ro to any number of VMs. Works on every disk type and is the right answer for shared static assets and reference datasets.
For read-write from two VMs simultaneously, you need multi-writer mode (--multi-writer on creation). Limited to pd-ssd, two VMs maximum, same zone, no snapshots while active, and requires a cluster-aware filesystem like OCFS2 or GFS2. ext4 and XFS corrupt themselves under multi-writer. For most use cases NFS, GCS Fuse, or Filestore is a better fit.
Add an entry to /etc/fstab using the filesystem UUID. Get the UUID with sudo blkid /dev/sdb, then add a line like UUID=<uuid> /mnt/disks/data ext4 discard,nofail 0 2. Test without rebooting: sudo umount /mnt/disks/data && sudo mount -a.
The nofail option is critical. Without it, a missing or unattached disk blocks the VM from booting entirely. With nofail, boot continues and you can SSH in to diagnose.
127 secondary (non-boot) persistent disks per VM, plus the boot disk. Total attached capacity caps at 257 TB per instance. The actual limit you hit first is usually IOPS or throughput, not the disk count.
Performance is provisioned per-disk and scales with disk size, so larger disks of the right type often outperform many small disks.
No. GCP supports online disk resize: you can grow a persistent disk while it is attached to a running VM, then expand the partition and filesystem from inside the VM without rebooting. The full procedure is in How to Increase Google Cloud VM Disk Size Without Rebooting.
Shrinking is not supported on any persistent disk type. The only path to a smaller disk is creating a new smaller disk and copying data over.
Sources
Authoritative references this article was fact-checked against.
- GCP — extreme persistent disk (docs)docs.cloud.google.com
- GCP — persistent disks overview (docs)docs.cloud.google.com





