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.





