TechEarl

How to Add a Persistent Disk to a Google Cloud VM

Create a GCP persistent disk, attach it to a running VM, format it, mount it, and survive reboots with a UUID-based fstab entry. Console, gcloud, and Terraform walkthroughs.

Ishan KarunaratneIshan Karunaratne⏱️ 13 min readUpdated
Create a GCP persistent disk, attach it to a running VM, format it with mkfs.ext4, mount it, and persist across reboots with a UUID-based /etc/fstab entry. gcloud, Console, and Terraform.

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

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:

bash
gcloud compute snapshots create pre-attach-$(date +%Y%m%d-%H%M) \
    --source-disk DISK_NAME \
    --source-disk-zone ZONE

Snapshots 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:

bash
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:

bash
gcloud compute disks create data-disk-01 \
    --source-snapshot SNAPSHOT_NAME \
    --type pd-balanced \
    --zone us-central1-a

Size 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

bash
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:

code
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:

bash
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:

bash
sudo mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdb
  • -m 0 skips the default 5% root reservation (wasted on a data disk).
  • lazy_itable_init=0,lazy_journal_init=0 initializes the inode table and journal up front. Slightly slower mkfs, faster steady-state.
  • discard issues 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:

bash
sudo mkdir -p /mnt/disks/data
sudo mount -o discard,defaults /dev/sdb /mnt/disks/data
sudo chmod a+w /mnt/disks/data

The 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):

code
/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):

bash
sudo blkid /dev/sdb

Output:

code
/dev/sdb: UUID="d2a4f1d0-9e5a-4b1c-83d9-7f0c2a8b6a3e" TYPE="ext4"

Add it to fstab:

bash
echo "UUID=d2a4f1d0-9e5a-4b1c-83d9-7f0c2a8b6a3e /mnt/disks/data ext4 discard,nofail 0 2" \
    | sudo tee -a /etc/fstab

nofail 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:

bash
sudo umount /mnt/disks/data
sudo mount -a
df -h /mnt/disks/data

Clean 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.

TypeReferenceMediaMin sizeMax sizeBest for
Standardpd-standardHDD-backed10 GiB64 TiBCold storage, archives, backup targets
Balancedpd-balancedSSD-backed10 GiB64 TiBGeneral workloads, boot disks, web servers
Performance SSDpd-ssdSSD-backed10 GiB64 TiBDatabases, high-IOPS workloads
Extremepd-extremeSSD with provisioned IOPS500 GiB64 TiBVery 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:

ApproachBest forCommand / action
Cloud ConsoleOne-off disks, exploringCompute Engine, Disks, Create disk, then VM, Edit, Attach
gcloud CLIScripted, batchedgcloud compute disks create plus attach-disk
TerraformInfra-as-code, audit trailsgoogle_compute_disk plus google_compute_attached_disk
REST APICustom toolingPOST /compute/v1/projects/.../disks

The Terraform snippet:

hcl
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:

bash
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
done

Auto-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:

bash
gcloud compute instances set-disk-auto-delete web-01 \
    --disk data-disk-01 \
    --no-auto-delete \
    --zone us-central1-a

For 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-ssd only. 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:

bash
sudo reboot

Wait for the VM to come back, SSH in, confirm the mount is present without manual intervention:

bash
df -h /mnt/disks/data
mount | grep /mnt/disks/data

If 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

FAQ

TagsGoogle CloudGCPCompute EnginePersistent DiskLinuxgcloudfstabmkfsTerraform
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

Connect to a GCP Compute Engine VM with plain OpenSSH and no gcloud CLI. Add a public key via instance metadata, ssh to the external IP, configure ~/.ssh/config, plus OS Login and IAP.

How to SSH into a Google Cloud VM Without gcloud

Connect to a GCP VM using plain OpenSSH, no gcloud required. Add a public key to instance metadata, fetch the external IP, and ssh in like any normal Linux box. Plus OS Login, IAP, and a Windows PuTTY path.

Add semantic search to an existing MySQL app with MySQL 9's VECTOR type and embeddings from Voyage or OpenAI. Index, query, and rank without a separate vector DB.

How to Add Semantic Search to a MySQL App

Add semantic search to an existing MySQL app with MySQL 9's VECTOR type, an embedding model (Voyage, OpenAI), and a cosine-similarity index. No separate vector database needed.

Create an EBS volume with aws ec2 create-volume, attach it to a running EC2 instance, format with mkfs.ext4 or mkfs.xfs, mount it, and persist across reboots with a UUID-based /etc/fstab entry. Console, AWS CLI, and Terraform walkthroughs.

How to Add an EBS Volume to an EC2 Instance

Create an EBS volume, attach it to a running EC2 instance, format and mount it, and survive reboots with a UUID-based fstab entry. Console, AWS CLI, and Terraform walkthroughs plus the Nitro device-naming gotcha that trips everyone.