# Unattached GCP Persistent Disk

> Persistent disks detached from every instance keep billing their full provisioned size. ZopNight requires the disk to be READY, referenced by no users list, outside Kubernetes management, and (when a detach timestamp exists) unattached for at least 7 days, then treats the whole monthly charge as recoverable.

Source: https://zop.dev/integrations/gcp/recommendations/unattached-gcp-persistent-disk
Updated: 2026-08-19

---

## Detached is not free

Deleting a VM does not delete its data disks unless someone set auto-delete, so every VM
teardown quietly mints candidates for this rule. A detached persistent disk bills its
full provisioned capacity. A 500 GB SSD disk costs the same attached to nothing as it
did serving production. Discovery of the fleet runs through Cloud Asset Inventory under
`roles/cloudasset.viewer`; note that the Cloud Asset API has to be enabled on the
project, or discovery silently returns an empty fleet rather than an error.

## Four gates before a delete shows up

This rule ships with an unguided delete workflow behind it, so the predicate is the
entire safety margin, and it demands four things. The disk's lifecycle status must be
READY (CREATING, RESTORING, FAILED, and DELETING states never qualify). The API's users
list must be empty, which is the canonical "no instance mounts this" signal. The disk
must not be Kubernetes-managed. And when GCP recorded a `lastDetachTimestamp`, the disk
must have been unattached at least 7 days: a disk detached this morning is usually mid
VM-rebuild and about to be re-attached, not abandoned. Never-attached disks carry no
detach timestamp, so for them the empty-users gate stands alone.

## The PVC trap

GKE persistent-volume backings and node disks can look unattached while a pod is
rescheduling or a node drains, but their lifecycle belongs to the Kubernetes control
plane. Deleting one from underneath a PVC is data loss wearing a cost-saving hat. The
dedicated Kubernetes-managed gate excludes them all before any other logic runs.

## Hunt for userless disks

```bash
gcloud compute disks list --filter="-users:*" \
  --format="table(name,zone,sizeGb,type,lastDetachTimestamp)"
```

Snapshot anything plausibly valuable, then delete; the remediation path uses
`compute.disks.delete` with `compute.zoneOperations.get` from
`roles/compute.instanceAdmin.v1`.

## Snapshot first, delete second

Deleting recovers the disk's full monthly charge; the recommendation's optimized cost is
$0. The rule abstains when pricing for the disk is unknown or zero, and every gate above
fails closed, so an ambiguous disk simply never appears.
