As software engineers, our local development environments and Linux VMs are constantly under pressure. Docker images build up, dependencies proliferate, and build artifacts quietly consume system resources. Eventually, every developer faces that dreaded error message: No space left on device.
When your disk fills up, the immediate instinct is to start deleting files or run a simple cleanup command. But if you want to properly manage, expand, or debug system storage—especially in virtualized environments like QEMU, KVM, or cloud instances—you need to understand what is happening at the block device level.
In this first part of our series, we will look beyond basic file deletion and explore how to accurately diagnose disk usage, inspect block devices, and read partition sector geometry.
1. Quick Diagnostics: df vs. du
When a system complains about disk space, your diagnostic workflow starts with two fundamental Linux utility tools: df and du.
System-Wide Health: df -h
The df (disk free) command inspects mounted filesystems and returns high-level utilization metrics. Always run it with the -h flag to format byte counts into human-readable units (GiB/MiB):
Bash
df -h
Plaintext
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 38G 35G 1.2G 97% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
The output reveals your primary drive status:
- Mounted on
/: This is your root filesystem. - Avail: The actual usable space remaining for non-root processes.
- Use%: If this ticks above 90%, system processes like log rotations or database writes will begin failing.
Granular Directory Profiling: du -sh
While df tells you which mounted disk is full, du (disk usage) helps you pinpoint where the storage is being consumed inside the directory tree.
To find the top storage-consuming directories under a given path without hanging your terminal, run:
Bash
sudo du -sh /* 2>/dev/null | sort -hr | head -n 10
-s: Summarizes total usage for each directory rather than listing every nested file.-h: Human-readable numbers.sort -hr: Sorts numerical values in descending order.
Pro Tip: For a faster, interactive terminal UI to locate space-hogging files, install
ncdu(sudo apt install ncdu).
2. Block Devices vs. Filesystems: Understanding the Architecture
A common point of confusion for engineers transitioning into system-level debugging is the difference between a Block Device and a Filesystem.
Plaintext
+-------------------------------------------------------------+
| Filesystem (ext4, XFS, btrfs) <-- Managed by resize2fs |
+-------------------------------------------------------------+
| Partition (/dev/vda1) <-- Managed by fdisk/parted|
+-------------------------------------------------------------+
| Physical Block Device (/dev/vda) <-- Managed by hypervisor |
+-------------------------------------------------------------+
- Physical Block Device (
/dev/vdaor/dev/sda): The underlying virtual or physical disk exposed to the OS kernel. It is simply an indexed sequence of raw storage blocks. - Partition (
/dev/vda1): A logical boundary defined on the block device using a partition table standard like MBR or GPT. - Filesystem (
ext4,xfs): The actual data structure written inside a partition that organizes your files, directories, and permissions.
To list all block devices recognized by the kernel, use lsblk:
Bash
lsblk
Plaintext
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vda 252:0 0 90G 0 disk
├─vda1 252:1 0 37.9G 0 part /
├─vda2 252:2 0 2.1G 0 part
└─vda5 252:5 0 2.1G 0 part [SWAP]
Notice how vda reports a total capacity of 90GB, but vda1 only spans 37.9GB. There is unallocated space on the disk, but vda1 cannot use it yet. To see why, we must inspect the disk’s sector geometry.
3. Reading Partition Sector Geometry with fdisk
Disk partitioning relies on sectors—the smallest addressable unit of storage on a drive, almost universally sized at 512 bytes.
To read the exact layout of a drive, open it using fdisk (make sure to specify the parent drive device /dev/vda, not a partition like /dev/vda1):
Bash
sudo fdisk -l /dev/vda
Plaintext
Disk /dev/vda: 90 GiB, 96636764160 bytes, 188743680 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x1371e6c3
Device Boot Start End Sectors Size Id Type
/dev/vda1 2048 79509503 79507456 37.9G 83 Linux
/dev/vda2 79511550 83884031 4372482 2.1G f W95 Ext'd (LBA)
/dev/vda5 79511552 83884031 4372480 2.1G 82 Linux swap / Solaris
Decoding the Metrics
- Total Disk Capacity:
188,743,680 sectors(~90 GiB). - Partition 1 (
/dev/vda1):- Start Sector:
2048. (Why 2048? This leaves a 1 MiB offset at the beginning of the disk for modern 4KiB sector alignment and bootloader data). - End Sector:
79,509,503.
- Start Sector:
- Partition 2 (
/dev/vda2):- Start Sector:
79,511,550.
- Start Sector:
The Boundary Trap
Here lies the core issue preventing immediate disk expansion: partitions must occupy a continuous, contiguous range of sectors.
Look at the end boundary of /dev/vda1 (79,509,503) and the start boundary of /dev/vda2 (79,511,550). The extended swap container (/dev/vda2) sits directly adjacent to our main system partition.
Even though the disk has open space out to sector 188,743,679, /dev/vda1 cannot grow into that space because /dev/vda2 is physically standing in the way!
What’s Next?
Now that we have diagnosed our storage and decoded our disk’s sector map, we understand the exact problem: we need to remove the adjacent blocking partition and expand safely /dev/vda1 across the continuous block device without destroying the ext4 data signature.
In Part 2 of this series, we will cover how to expand QEMU dynamically .qcow2 images, trigger live kernel rescans, and rewrite partition boundaries in fdisk safely.
Stay tuned!