From vm-sre-skills
Expand VM disks (OS or data) when disk space is running low. Covers both Linux and Windows VMs. Use when a disk space alert fires, a user reports a full disk, or monitoring shows disk usage above 90%. Handles Azure managed disk resize, partition expansion, and filesystem growth. Always snapshots before changes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/vm-sre-skills:disk-expansionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when:
Use this skill when:
This skill guides you through a structured disk expansion:
CRITICAL: Always get operator confirmation before proceeding with steps that cause downtime (VM deallocation). Data disk expansion can often be done online; OS disk expansion usually requires deallocation.
First, determine the VM's OS type:
az vm show --resource-group {rg} --name {vm-name} --query "{os:storageProfile.osDisk.osType, size:hardwareProfile.vmSize, location:location}" -o json
Then check disk usage inside the VM.
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "df -Th | grep -v tmpfs && echo '---INODES---' && df -i | grep -v tmpfs"
This shows filesystem type (ext4, xfs, etc.), size, used, available, and mount points. Also check inode usage — a disk can appear to have space but be out of inodes.
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunPowerShellScript --scripts "Get-Volume | Where-Object {$_.DriveLetter} | Select-Object DriveLetter, FileSystemLabel, FileSystem, @{N='SizeGB';E={[math]::Round($_.Size/1GB,2)}}, @{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,2)}}, @{N='UsedPct';E={[math]::Round(($_.Size - $_.SizeRemaining)/$_.Size * 100,1)}} | Format-Table -AutoSize"
az vm show --resource-group {rg} --name {vm-name} --query "{osDisk:{name:storageProfile.osDisk.name, diskSizeGB:storageProfile.osDisk.diskSizeGb, caching:storageProfile.osDisk.caching}, dataDisks:storageProfile.dataDisks[].{name:name, lun:lun, diskSizeGB:diskSizeGb, caching:caching}}" -o json
If the full disk is a data disk, find which Azure LUN it maps to:
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "ls -la /dev/disk/azure/scsi1/ 2>/dev/null && echo '---LSBLK---' && lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,TYPE"
The LUN number from /dev/disk/azure/scsi1/lun{N} corresponds to the LUN in the Azure disk list.
If the full disk is a data disk, correlate the Azure LUN to the Windows disk number before resizing:
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunPowerShellScript --scripts "Get-Disk | Select-Object Number, FriendlyName, @{N='SizeGB';E={[math]::Round($_.Size/1GB,2)}}, Location | Format-Table -AutoSize"
On Azure Windows VMs, the Location field typically includes LUN {N}, which you can match to the LUN shown in the Azure disk list.
Once you know the disk name:
az disk show --resource-group {rg} --name {disk-name} --query "{name:name, sizeGB:diskSizeGb, sku:sku.name, tier:sku.tier, state:diskState, maxShares:maxShares, encryption:encryption.type}" -o json
ALWAYS take a snapshot before expanding a disk. This is non-negotiable.
az snapshot create --resource-group {rg} --name {disk-name}-snap-$(date +%Y%m%d%H%M) --source {disk-name} --query "{name:name, provisioningState:provisioningState, diskSizeGB:diskSizeGb}" -o json
Wait for the snapshot to complete before proceeding.
Online resize (no downtime) is supported when ALL of these are true:
If the disk is the OS disk, the VM almost always needs to be deallocated first.
az disk update --resource-group {rg} --name {disk-name} --size-gb {new-size-gb}
⚠️ WARN THE OPERATOR: This will cause VM downtime. Get explicit confirmation before proceeding.
az vm deallocate --resource-group {rg} --name {vm-name}
Wait for deallocation to complete, then resize:
az disk update --resource-group {rg} --name {disk-name} --size-gb {new-size-gb}
Then start the VM:
az vm start --resource-group {rg} --name {vm-name}
Calculate the new size to ensure at least 20% free space after expansion:
Required total = Current used space / 0.80
New disk size = Round up to nearest sensible increment (e.g. 64, 128, 256, 512, 1024 GB)
Examples:
| Current Size | Used | Used % | Calculated Minimum | Suggested New Size |
|---|---|---|---|---|
| 30 GB | 28 GB | 93% | 35 GB | 64 GB |
| 128 GB | 120 GB | 94% | 150 GB | 256 GB |
| 512 GB | 490 GB | 96% | 613 GB | 1024 GB |
Rules:
After the Azure managed disk is resized, the OS needs to recognize and use the new space.
If the VM was NOT rebooted (online data disk expansion), the OS needs to rescan:
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "echo 1 | tee /sys/class/block/{device}/device/rescan && sleep 2 && lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT"
Replace {device} with the block device name (e.g., sdc, sda). If the VM was deallocated and restarted, this step is not needed.
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,TYPE && echo '---PVDISPLAY---' && pvdisplay 2>/dev/null | head -20 && echo '---LVDISPLAY---' && lvdisplay 2>/dev/null | head -30"
This tells you:
/dev/sda1, /dev/sdc1)Standard partition (no LVM):
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "growpart /dev/{device} {partition-number} && echo 'Partition expanded successfully'"
Example: growpart /dev/sda 1 expands partition 1 on /dev/sda.
Note:
growpartmay need to be installed:apt-get install -y cloud-guest-utils(Debian/Ubuntu) oryum install -y cloud-utils-growpart(RHEL/CentOS).
LVM — expand physical volume, logical volume, then filesystem:
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "growpart /dev/{device} {partition-number} && pvresize /dev/{partition} && lvextend -l +100%FREE /dev/{vg-name}/{lv-name} && echo 'LVM expanded successfully'"
After the partition (or logical volume) is expanded, grow the filesystem.
ext4:
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "resize2fs /dev/{partition-or-lv} && echo 'ext4 filesystem expanded' && df -Th {mount-point}"
xfs:
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "xfs_growfs {mount-point} && echo 'xfs filesystem expanded' && df -Th {mount-point}"
Note:
xfs_growfstakes the mount point, not the device. XFS cannot be shrunk.
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunPowerShellScript --scripts "Update-Disk -Number {disk-number}; Get-Partition -DiskNumber {disk-number} | Select-Object PartitionNumber, DriveLetter, @{N='SizeGB';E={[math]::Round($_.Size/1GB,2)}} | Format-Table -AutoSize"
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunPowerShellScript --scripts "$partition = Get-Partition -DriveLetter {drive-letter}; Get-Volume -DriveLetter {drive-letter} | Select-Object DriveLetter, FileSystem, FileSystemLabel, @{N='SizeGB';E={[math]::Round($_.Size/1GB,2)}}, @{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,2)}}; $maxSize = (Get-PartitionSupportedSize -DriveLetter {drive-letter}).SizeMax; Write-Output \"Current: $([math]::Round($partition.Size/1GB,2)) GB\"; Write-Output \"Maximum: $([math]::Round($maxSize/1GB,2)) GB\""
Note:
Resize-Partitionis the usual NTFS path. IfGet-VolumeshowsReFS, verify supported online resize behavior before proceeding because ReFS handling can differ from NTFS.
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunPowerShellScript --scripts "Get-Disk -Number {disk-number} | Select-Object Number, FriendlyName, IsPartOfPool | Format-Table -AutoSize"
If IsPartOfPool is True, the disk is part of a Storage Spaces pool. In that case, expand the virtual disk and refresh the storage pool with Resize-VirtualDisk and Update-StoragePool before resizing the partition.
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunPowerShellScript --scripts "$maxSize = (Get-PartitionSupportedSize -DriveLetter {drive-letter}).SizeMax; Resize-Partition -DriveLetter {drive-letter} -Size $maxSize; Get-Volume -DriveLetter {drive-letter} | Select-Object DriveLetter, FileSystem, @{N='SizeGB';E={[math]::Round($_.Size/1GB,2)}}, @{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,2)}} | Format-Table -AutoSize"
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunShellScript --scripts "df -Th | grep -v tmpfs && echo '---' && lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT"
az vm run-command invoke --resource-group {rg} --name {vm-name} --command-id RunPowerShellScript --scripts "Get-Volume | Where-Object {$_.DriveLetter} | Select-Object DriveLetter, FileSystemLabel, @{N='SizeGB';E={[math]::Round($_.Size/1GB,2)}}, @{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,2)}} | Format-Table -AutoSize"
Confirm that:
## Disk Expansion Report
**VM**: {vm-name}
**Resource Group**: {rg}
**OS**: {Linux/Windows}
### Disk Expanded
- **Azure Disk Name**: {disk-name}
- **Disk Type**: {OS disk / Data disk (LUN {n})}
- **Previous Size**: {old-size} GB
- **New Size**: {new-size} GB
- **SKU**: {Standard_LRS / Premium_LRS / etc.}
### Safety
- **Snapshot**: {snapshot-name} created at {timestamp}
- **Method**: {Online resize / Deallocated VM}
### OS-Level Changes
- **Partition**: {device/partition} expanded via {growpart / Resize-Partition}
- **Filesystem**: {ext4/xfs/NTFS} expanded via {resize2fs / xfs_growfs / Resize-Partition}
### Verification
- Disk usage before: {old-usage}%
- Disk usage after: {new-usage}%
### Notes
{Any warnings, issues encountered, or follow-up recommendations}
growpart and filesystem expansion on very large disks may take longer — warn if the disk is very large.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub raskip/azure-sre-agent-stuff --plugin vm-sre-skills