Module 12 — Storage, Disks, and Filesystems
Updated 2 September 2026
"The disk is full" is the most common Linux incident there is, and it has a dozen causes that look identical until you know how storage actually works. This module builds the whole stack from the bottom: block devices → partitions → filesystems → mounts → the abstractions (LVM, swap) on top. You'll learn to triage a full disk in under a minute, and to understand the counter-intuitive failures — space that won't free, "disk full" with space to spare — that stump people who only memorized df.
Legend used throughout: 🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
You need Modules Module 1 — What Linux Is–Module 11 — systemd and Services — especially inodes and the file-vs-name distinction (Module 3, the key to two of this module's hardest ideas), the filesystem tree and /dev//proc (Module 2), processes holding files open (Module 8), and pipes/sort (Modules 5–7). Tools: an Ubuntu 24.04 machine. Read-only exploration (df, du, lsblk, free, findmnt) is safe to run anywhere, including inside containers. The mounting, partitioning, and LVM exercises change disks and are marked — do those only on a throwaway VM or a spare disk you can afford to wipe, never on a machine whose data you need.
Goes deeper: the kernel side of storage — the block layer, page cache, and I/O — is the companion track's Module 10 — I/O, the Block Layer & iowait.
Part A — The storage stack
A1. Block devices — what a disk is to Linux
🧠 To Linux, a disk is a block device: a thing you read and write in fixed-size blocks, exposed as a special file under /dev. Real disks get names like /dev/sda, /dev/vda (virtio disks on VMs), or /dev/nvme0n1 (NVMe SSDs); their partitions append a number or p: /dev/sda1, /dev/nvme0n1p1. A block device is raw storage — a numbered sequence of blocks with no notion of files yet. Turning it into something you can store files on takes two more layers (partitions, then a filesystem), which the rest of Part A builds. The tool that shows the whole picture is lsblk (list block devices).
A block device is an empty warehouse: a big space measured in numbered floor-tiles, with no shelving, no aisles, no filing system — just addressable square footage. You could pile things on the floor by tile number, but nobody works that way. The shelving and the index (partitions and a filesystem) come next.
Where the analogy stops working. A warehouse floor is visible and physical; a block device is an abstraction the kernel presents, and the "warehouse" might be a physical SSD, a slice of a SAN, a network volume, or a file pretending to be a disk — Linux treats them identically. That uniformity is powerful (the same tools work on all of them) and occasionally a trap: the /dev/vda "disk" on your cloud VM is really a slice of some storage array three racks away, with performance nothing like a local SSD.
🧪 Exercise A1.1 — See the block devices (read-only, safe anywhere)
lsblk # the tree of disks, partitions, and where they're mounted
lsblk -f # add filesystem type, label, and usage✅ Expected result — click to reveal
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vda 254:0 0 256G 0 disk
├─vda1 254:1 0 255G 0 part /
└─vda2 254:2 0 1G 0 part [SWAP](on a cloud VM; a laptop shows sda/nvme0n1, and yours will differ)
What to read out of it:
- TYPE disk is the whole device; TYPE part are partitions within it, shown as a tree (├─). Here one 256G disk is split into a 255G root partition and a 1G swap partition — the layout Part A explains bottom-up.
- MOUNTPOINTS ties it back to Module 2's tree: vda1 is mounted at /, so the root of everything you've been using lives on that partition. [SWAP] marks swap (Part D).
- SIZE and RO (read-only) at a glance; lsblk -f adds the filesystem type (ext4, xfs) and how full each is. lsblk is the first command of any storage investigation — it shows the physical reality beneath the tidy filesystem you normally see.
A2. Partitions — dividing a disk
🧠 A partition is a contiguous slice of a block device, treated as its own smaller block device. Why divide a disk: to separate concerns (keep /var/log from filling /), to run different filesystems on one disk, or to isolate swap. The partition table — the map of slices — comes in two formats: MBR (old, max 2TB, ≤4 primary partitions) and GPT (modern, huge disks, many partitions — the default now). Partitioning tools are fdisk, parted, and friends. Crucially, a partition is still raw space — slicing the warehouse into rooms doesn't install shelving; that's the filesystem, next.
Partitioning erects internal walls in the empty warehouse: this room for shipping, that room for cold storage, a small room for overflow. Each room is now a self-contained space you can fit out independently. The partition table is the floor plan pinned by the door.
Where the analogy stops working. Rebuilding a warehouse wall is disruptive but recoverable — the stuff inside can be moved first. Rewriting a partition table doesn't "move the stuff"; it rewrites the floor plan while pretending the rooms are empty, and the filesystem inside a repartitioned slice is simply orphaned. There is no "the movers will handle it" — you evacuate (back up) first, or you lose it.
🧪 Exercise A2.1 — Read the partition layout (read-only, safe anywhere)
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS # choose exactly the columns you want
cat /proc/partitions | head # the kernel's raw partition list✅ Expected result — click to reveal
NAME SIZE TYPE FSTYPE MOUNTPOINTS
vda 256G disk
├─vda1 255G part ext4 /
└─vda2 1G part swap [SWAP]What to read out of it:
- The FSTYPE column is the layer A3 adds: vda1 isn't just a 255G slice, it holds an ext4 filesystem; vda2 holds swap. A partition with an empty FSTYPE is raw — sliced but not yet formatted, useless for files until you put a filesystem on it.
- /proc/partitions is the kernel's own view (Module 2's /proc) — the ground truth lsblk prettifies. Reading storage from /proc and /sys is how monitoring tools get their numbers without shelling out.
- lsblk -o (custom columns) is the habit worth keeping — like ps -o (Module 8), you ask for exactly the fields the question needs.
A3. Filesystems — turning raw space into files
🧠 A filesystem is the organizing structure written onto a partition that turns numbered blocks into named files and directories — the layer that makes /etc/hostname meaningful instead of "block 4,192,006". Creating one is formatting: mkfs.ext4 /dev/sdb1 writes an empty ext4 filesystem onto that partition. The common types: ext4 (the reliable Linux default), xfs (great for large files, Red Hat's default), btrfs/zfs (advanced — snapshots, checksums), vfat/exfat (for USB sticks and cross-OS compatibility), tmpfs (a filesystem in RAM, not on disk — that's what /dev/shm and much of /run are). Every filesystem carries the inode table you met in Module 3 — which is why a filesystem can run out of inodes independently of running out of space (Part D's counter-intuitive incident).
The filesystem is the shelving, aisle signs, and card catalogue installed in the empty room: now things have addresses ("aisle 3, shelf B"), there's an index to find them, and a rule for where new items go. mkfs is the fit-out crew — and formatting an occupied room means throwing out everything first (formatting erases).
Where the analogy stops working. A shelving system is one fixed design. Filesystems are plural and chosen — ext4 vs xfs vs btrfs are genuinely different fit-outs with different strengths (xfs for a few enormous items, btrfs for snapshotting the whole room), and picking the wrong one for the workload is a real performance and operations decision, not a cosmetic one. The room looks the same from the aisle; the machinery behind the shelves differs a lot.
🧪 Exercise A3.1 — Identify your filesystems (read-only, safe anywhere)
df -T # -T adds the filesystem TYPE column
findmnt --real # a clean tree of only the "real" mounts (skips the virtual ones)✅ Expected result — click to reveal
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/vda1 ext4 263174148 12093104 237656916 5% /
tmpfs tmpfs 4108096 0 4108096 0% /dev/shmthen findmnt as a tree of TARGET/SOURCE/FSTYPE/OPTIONS.
What to read out of it:
- / is ext4 on a real partition; /dev/shm is tmpfs — a filesystem living in RAM, which is why it's blazing fast and why anything there vanishes on reboot. Not everything that looks like a disk is a disk.
- findmnt draws mounts as the tree they actually form (Part C), far more readable than raw mount output — the modern tool for "what's mounted where, and how?"
- You've now seen all three layers in one view: the device (/dev/vda1), its filesystem (ext4), and where it's attached in the tree (/). Everything else in this module operates on this stack.
Part A — Interview questions
🎯 "What is a File system in Linux? Explain different file system types in Linux." — asked verbatim in Turing's 100+ Linux interview questions (2025)
A filesystem is the on-disk structure that organizes a block device's raw storage into named files and directories, with metadata (inodes — owner, permissions, timestamps, block locations) and a directory hierarchy. Common Linux types: ext4 (the general-purpose default — reliable, journaled), xfs (high-performance for large files/parallel I/O, Red Hat's default), btrfs/zfs (copy-on-write with snapshots and checksums), vfat/exfat (cross-platform removable media), plus in-memory tmpfs and the virtual proc/sysfs that aren't storage at all. You create one with mkfs.type, identify it with df -T/lsblk -f, and it sits on a partition of a block device.
The details that separate candidates: the three-layer stack (block device → partition → filesystem) rather than treating "disk" and "filesystem" as one thing; a real reason to pick xfs over ext4 (huge files, high parallelism) instead of listing types; and that /proc and /sys are virtual filesystems (kernel data dressed as files, Module 2), which shows you understand a filesystem is an interface, not necessarily storage.
🎯 "How would you create an ext4 file system?" — asked verbatim in Turing's 100+ Linux interview questions (2025)
sudo mkfs.ext4 /dev/sdb1 — format the target partition (not the whole disk unless you mean to) with an empty ext4 filesystem, optionally -L label to name it. Then create a mount point (sudo mkdir /mnt/data), mount it (sudo mount /dev/sdb1 /mnt/data), and add an /etc/fstab entry (Part C) so it mounts at boot. Confirm with lsblk -f and df -T.
The details that separate candidates: the emphatic warning that mkfs erases the target — so you confirm the device with lsblk first and never format a mounted/in-use partition; formatting the partition not the raw disk; and the full workflow (format → mkdir → mount → fstab → verify), because "create a filesystem" in the real world means "make it usable and persistent", not just the one mkfs command.
Part B — Measuring space, and the full-disk incident
B1. df — how full is each filesystem
🧠 df (disk free) answers "how full is each filesystem?" — one line per mounted filesystem, with size, used, available, and use%. Always run it as df -h (human-readable: G/M instead of raw 1K-blocks). It reads the filesystem's own accounting, so it's instant regardless of how many files exist — it does not walk the tree. This is the first command of every disk-full incident: it tells you which filesystem is full, which is not always the obvious one (a full /var and an empty /home are separate filesystems that df shows separately).
df is the fuel gauge: one glance tells you how full each tank is, instantly, without measuring the fuel. A car with several tanks (several filesystems) has a gauge per tank — and "the car is out of fuel" always means one specific tank, which the gauges tell you at once.
Where the analogy stops working. A fuel gauge reads the actual liquid. df reads the filesystem's bookkeeping, and bookkeeping can lie in one specific, famous way: a file that's been deleted but is still held open by a process still occupies space the filesystem counts as used — so df shows the tank full while du (which walks visible files) shows it should be empty. That df-says-full-but-du-says-empty gap is Part B's signature incident, and it's not a bug — it's the fuel still in a hidden reserve nobody can see by looking at the visible tank.
🧪 Exercise B1.1 — Read the gauges (read-only, safe anywhere)
df -h # every filesystem, human-readable
df -h / # just the one holding the root
df -h /var # which filesystem is /var on? (may be / or its own)✅ Expected result — click to reveal
Filesystem Size Used Avail Use% Mounted on
tmpfs 7.9G 0 7.9G 0% /dev/shm
/dev/vda1 252G 12G 237G 5% /
tmpfs 4.0G 0 4.0G 0% /runWhat to read out of it (your sizes and device names differ):
- Use% is the number monitoring alerts on, and the one to read first in an incident — a filesystem at 100% is the problem; the others are noise. Avail is what you actually have left (note it can be less than Size−Used, because filesystems reserve ~5% for root — Part D).
- Mounted on maps each filesystem to its place in the tree. If df -h /var shows the same device as df -h /, then /var lives on the root filesystem — so a log flood in /var/log fills / and can take down the whole machine. If it shows a different device, /var is isolated and a log flood only fills that. Which one your machine is decides how a log flood plays out — check before you need to know.
- The tmpfs lines are RAM filesystems (A3) — they "use disk" of zero because they're memory; filling one is a memory problem, not a disk one.
B2. du — what's using the space
🧠 df says which filesystem is full; du (disk usage) says what in it is using the space, by walking directories and summing file sizes. The incident-response idiom, worth memorizing exactly: du -h /var | sort -rh | head — human sizes, sorted largest-first, top few. More surgically, du -sh /var/* gives one summary line per item in /var, and you drill into the biggest (du -sh /var/log/*, then deeper) until you find the culprit. Because du walks the tree, it's slow on huge directories — but it's the tool that turns "the disk is full" into "this 40GB log file is the problem".
If df is the fuel gauge, du is sending an auditor to physically weigh every box in the warehouse and total it up. The audit tells you what's heavy — invaluable for "what do I delete?" — but it takes time proportional to the number of boxes, and it can only weigh boxes it can see.
Where the analogy stops working. The auditor counts visible boxes; the gauge measures total load including a crate that fell behind the shelving (deleted-but-open file) that the auditor walks right past. When the gauge and the audit disagree, the discrepancy is the clue — something is consuming space that has no visible name, and chasing that gap is exactly how you solve the incidents that defeat people who only know du.
🧪 Exercise B2.1 — Hunt the space (read-only, safe anywhere)
du -sh /usr/share # total size of one directory
du -h /var 2>/dev/null | sort -rh | head -5 # biggest things under /var
du -sh /var/* 2>/dev/null | sort -rh | head -6 # one line per item in /var, biggest first✅ Expected result — click to reveal
1.1G /usr/sharethen, for /var:
236M /var/lib
5.7M /var/cache
1.4M /var/log
20K /var/spool
4.0K /var/tmpWhat to read out of it (your sizes vary hugely by machine):
- du -sh /var/* is the incident-response drill: one number per subdirectory, sorted, so the culprit is line one. Here /var/lib dominates (package and app data) — on a machine with a real problem, you'd see a /var/log at 40G and know instantly where to look.
- The 2>/dev/null (Module 5) hides permission-denied noise on directories you can't fully read — the same wide-search etiquette as Module 6's find. The totals stay honest for what you can see; run with sudo for the complete picture.
- Drill down by repeating on the winner: du -sh /var/lib/*, then into its biggest, until you reach the specific file or directory. Three or four du -sh .../* | sort -rh | head steps find almost any space hog.
B3. The full-disk incident, end to end
🧠 Put it together into the procedure you'll run for real. 1. df -h — which filesystem is full? 2. du -sh /that/fs/* | sort -rh | head, drilling down — what's using it? 3a. If du finds a big file you can delete or rotate — do it (but see the deleted-open trap). 3b. If df says full but du can't account for it — a deleted-but-open file: find the culprit with sudo lsof | grep deleted, and restart the process holding it (or truncate via /proc/PID/fd/), because there's no filename to rm. 4. Get breathing room fast if needed: apt clean (Module 9's package cache), truncate/rotate the offending log, clear /tmp. 5. Fix the root cause: rotation, a separate filesystem, or a quota — so it doesn't recur tonight.
A full disk is a flooding basement: first find which basement is flooding (df), then find the source — the burst pipe, not just the water (du drilling down). Sometimes the water has an obvious source you shut off (delete the big file); sometimes the water keeps rising with no visible source (deleted-open file — the pipe is behind the wall, and mopping the visible floor does nothing).
Where the analogy stops working. A plumber can always eventually see the pipe. The deleted-open file is genuinely invisible to the file tools — ls, du, find all show nothing, because the name is gone while the data lives on in a process's grip. You can only reach it sideways, through the process holding it (lsof, /proc/PID/fd). It's the one storage problem where the obvious tools are blind by design, which is exactly why it defeats people and why it's a favourite interview question.
🧪 Exercise B3.1 — Reproduce the deleted-open-file mystery (safe — uses a small temp file in /tmp)
cd /tmp
# Create a 50MB file, hold it open in a background process, then DELETE its name:
python3 -c "
import time
f = open('/tmp/ghost.bin','wb'); f.write(b'x'*(50*1024*1024)); f.flush()
import os; os.remove('/tmp/ghost.bin') # name gone
print('name deleted; now sleeping while holding the file open')
time.sleep(30)
" &
sleep 1
ls -la /tmp/ghost.bin 2>&1 # the name is GONE — du/ls can't see it
sudo lsof 2>/dev/null | grep -i ghost | head -2 # but a process still holds it (space still used)
wait # let the python finish; space frees when it closes the file✅ Expected result — click to reveal
name deleted; now sleeping while holding the file open
ls: cannot access '/tmp/ghost.bin': No such file or directory
python3 40231 root 3w REG 254,0 52428800 ... /tmp/ghost.bin (deleted)What to read out of it:
- ls (and du, and find) report the file gone — the name is deleted. Yet 50MB is still allocated, because the python process holds the file open (Module 3: a file survives until its link count is zero and no process holds it open).
- lsof reveals the truth: the process, its open file descriptor (3w), the 52428800 bytes, and the tell-tale (deleted) marker. This is exactly what you'd see on a real box where df says full but du says empty — scaled up to a 40GB deleted log some service is still writing to.
- The fix on a real incident is not rm (no name to remove) — it's restart the process (systemctl restart theservice, Module 11), which closes the descriptor and frees the space instantly; or, to avoid downtime, truncate it in place via > /proc/40231/fd/3. Here, wait lets python exit and the space frees on its own.
Part B — Interview questions
🎯 "How do you check disk usage?" — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026); Adaface (2024) asks "What is the command to check disk space usage?" and its per-directory variant
Two complementary tools: df -h for filesystem-level free space (which filesystem is how full — the fuel gauge), and du -sh path / du -h path | sort -rh | head for what's consuming space within a directory (the itemized audit). df reads filesystem accounting instantly; du walks the tree, so it's slower but tells you what to delete. lsblk/df -T add the device and filesystem-type view.
The details that separate candidates: using them together as a drill (df to localize, du to hunt) rather than naming one; the df/du divergence and the deleted-open-file cause (the standout answer); the du -sh /* drill-down technique; and mentioning that at scale you don't run these live — monitoring alerts on df before 100%, and you run du on the one flagged host.
🎯 "How do you find the top 10 largest files and directories on your Linux system?" / "Explain how to find the largest files in a directory tree, sorted by size." — asked verbatim in Turing's set (2025) and Adaface's (2024) respectively
Largest directories: du -h /path | sort -rh | head -10. Largest files specifically: find /path -type f -printf '%s\t%p\n' 2>/dev/null | sort -rn | head -10 (or find /path -type f -exec du -h {} + | sort -rh | head — Module 6's find + Module 7's sort composed). For a live incident, du -sh /path/* | sort -rh | head and drill into the winner.
The details that separate candidates: distinguishing largest files (find) from largest directories (du) — they're different questions; the composition of find + du + sort from earlier modules; sort -rh (human-numeric reverse, so 2G sorts above 900M — Module 7's exact trap); and scoping the search to the relevant filesystem rather than / to avoid crossing into other mounts (-xdev).
Part C — Mounting: attaching storage to the tree
C1. mount and umount — grafting a filesystem in
🧠 Module 2 taught that Linux has one tree and no drive letters — a second disk is grafted into the tree at a directory. That grafting is mounting. sudo mount /dev/sdb1 /mnt/data attaches the filesystem on /dev/sdb1 at the directory /mnt/data — now reading /mnt/data/file reads from that disk. The directory you mount onto is the mount point (an ordinary directory; anything already in it is hidden while the mount is active — B2's du gotcha). sudo umount /mnt/data detaches it. mount with no arguments (or findmnt) lists everything currently mounted. This is the mechanism behind every disk, USB stick, and network share appearing in the tree.
A new building (a filesystem) is finished, but nobody can reach it until it's connected to the campus walkway system at a specific door (the mount point). Mounting builds that connection; now the building is part of the campus and people walk to it by the normal paths. Unmounting seals the connection — and you can't seal a doorway people are currently walking through.
Where the analogy stops working. A sealed-off building still physically exists and its contents are safe. An unmounted filesystem's data is intact too — but a filesystem unmounted uncleanly (power pulled, force-unmounted mid-write) can be left inconsistent, because writes buffered in memory never reached the disk. The campus walkway doesn't lose the building's contents when disconnected roughly; a filesystem can lose recent writes, which is why clean unmount (and sync) matters and why "just pull the USB" corrupts sticks.
🧪 Exercise C1.1 — Read the mount table (read-only, safe anywhere)
findmnt --real # the real (non-virtual) mounts as a tree
mount | grep -w / # how is the root filesystem mounted?
cat /proc/mounts | grep -v tmpfs | head # the kernel's raw mount list✅ Expected result — click to reveal
TARGET SOURCE FSTYPE OPTIONS
/ /dev/vda1 ext4 rw,relatimethen the root's mount line, e.g.:
/dev/vda1 on / type ext4 (rw,relatime)What to read out of it:
- Each mount is SOURCE (the device) → TARGET (where in the tree) → FSTYPE → OPTIONS. The root / is /dev/vda1, ext4, mounted rw (read-write). Everything you've done all track has happened on this one mount.
- The OPTIONS matter operationally: ro (read-only), noexec (no running programs from here — a security hardening for /tmp), nosuid, relatime (a performance choice about atime updates — Module 3's timestamps). Mount options are a real security and performance lever, set per-filesystem in fstab (next).
- /proc/mounts is the kernel's authoritative list; findmnt and mount are friendlier views of the same truth.
C2. /etc/fstab — mounts that survive reboot
🧠 A mount command mounts now but is forgotten on reboot — the machine comes up without it. /etc/fstab (filesystem table) is the file listing filesystems to mount automatically at boot: systemd reads it and mounts everything in it. Each line has six fields: device (best identified by UUID= — see the trap), mount point, filesystem type, options (defaults, ro, noexec…), dump (backup flag, usually 0), and fsck order (boot-time check order — 0 skip, 1 for root, 2 for others). "Permanently mount a filesystem" — a verbatim interview question — means: add its fstab line.
A live mount command is asking the doorman to open a connection right now — helpful today, forgotten tomorrow. An fstab entry is the standing instruction in the building's opening procedure: "every morning, connect these buildings to the walkway". Written once, followed at every start-up.
Where the analogy stops working. A doorman who finds a standing instruction impossible ("that building doesn't exist any more") shrugs and moves on. Boot-time fstab is far less forgiving: a mount it can't satisfy can halt the whole opening procedure (drop to emergency mode) rather than skip the one building — unless you explicitly wrote nofail. The standing instruction has veto power over the entire startup, which is why a careless edit is dangerous and mount -a testing is non-negotiable.
🧪 Exercise C2.1 — Read your fstab and match it to reality (read-only, safe anywhere)
cat /etc/fstab # the standing mount instructions
lsblk -f # the UUIDs to reference (compare against fstab)
# The safe test after any edit (harmless to run now — mounts anything not yet mounted):
sudo mount -a && echo "fstab is consistent — no errors"✅ Expected result — click to reveal
# <file system> <mount point> <type> <options> <dump> <pass>
UUID=a1b2c3d4-... / ext4 defaults 0 1
UUID=e5f6... none swap sw 0 0(a minimal cloud image may have very few lines; yours will differ)
What to read out of it:
- The root filesystem is referenced by UUID=, not /dev/vda1 — the robust identification the trap insists on. Compare the UUID against lsblk -f's output to see it's the same filesystem, named two ways.
- Six fields per line, in order: device, mount point, type, options, dump, pass. The pass field (1 for root, 2 for others, 0 to skip) controls boot-time filesystem checking order — root gets checked first.
- sudo mount -a is the safety check: it attempts every fstab mount that isn't already mounted and errors loudly if a line is broken — run it after every fstab edit, before you ever reboot. On a healthy system it's silent (Module 1: silence is success). This one habit prevents the unbootable-server incident.
Part C — Interview questions
🎯 "How do you permanently mount a filesystem?" — asked verbatim in GeeksforGeeks' Linux Interview Questions (70+ questions, updated July 2026)
Add an entry to /etc/fstab: device (as UUID=…), mount point, filesystem type, options, dump flag, and fsck pass — then sudo mount -a to apply and test it without rebooting. A live mount /dev/sdb1 /mnt/data mounts only until reboot; fstab is what makes it survive, because systemd reads fstab at every boot and mounts everything listed.
The details that separate candidates: the temporary-vs-permanent distinction (mount now vs fstab forever); using UUID over device names (which can change) and why; testing with mount -a before rebooting to avoid an unbootable machine; and knowing nofail for removable/network mounts so a missing one doesn't halt boot. This question is a magnet for the "I edited fstab and the server wouldn't boot" war story — leading with the UUID + mount -a safeguards signals you've been burned and learned.
🎯 "How would you configure a Linux system to automatically mount a network share on boot?" / "How can you mount a partition with the NTFS file system type in read-write mode?" — asked verbatim in Adaface (2024) and Turing (2025) respectively
Network share (NFS example): install the client (nfs-common), then an fstab line server:/export /mnt/share nfs defaults,_netdev,nofail 0 0 — _netdev waits for the network before mounting (crucial: a network mount attempted before networking is up fails), nofail so a missing server doesn't block boot. NTFS read-write: install ntfs-3g, then sudo mount -t ntfs-3g /dev/sdb1 /mnt/win (or fstab with type ntfs-3g) — the ntfs-3g driver provides safe read-write NTFS that the older kernel driver didn't.
The details that separate candidates: _netdev/nofail for network mounts (the two options that prevent boot hangs on a dependency that may be slow or absent — the systemd ordering lesson from Module 11, applied to storage); knowing NTFS read-write needs the ntfs-3g driver historically; and that CIFS/SMB (Windows shares) vs NFS (Unix) are different filesystem types with different fstab syntax — picking the right one for the share.
Part D — The layers on top: LVM, swap, inodes
D1. LVM — storage you can resize
🧠 A raw partition has a fixed size — outgrow it and you're repartitioning, which is destructive (Part A). LVM (Logical Volume Manager) inserts a flexible layer between physical disks and filesystems so you can resize, combine, and snapshot storage without repartitioning. Its three concepts stack: physical volumes (PV) — whole disks or partitions handed to LVM (pvcreate /dev/sdb); volume groups (VG) — a pool combining one or more PVs into one big allocatable space (vgcreate data /dev/sdb /dev/sdc); logical volumes (LV) — the "virtual partitions" you carve from a VG and put filesystems on (lvcreate -L 50G -n web data). The payoff: a filesystem running low can be grown live — add a disk to the VG, extend the LV (lvextend), grow the filesystem (resize2fs) — no downtime, no repartition. This is why servers and cloud VMs almost always use LVM.
Fixed partitions are permanent drywall — an office is the size it's built, and enlarging it means demolition. LVM is a modern floor with movable partition walls on tracks: the whole floor is a pool of space (the VG), and you draw rooms (LVs) of whatever size, resizing them by sliding walls as needs change — and you can annex the floor next door (add a PV) to make the pool bigger.
Where the analogy stops working. Movable office walls still divide a fixed total floor area. LVM can grow the total by annexing more disks into the VG, so the pool itself expands — there's no fixed building footprint. And LVM snapshots have no physical-wall equivalent at all: a frozen point-in-time copy of a volume you can back up or roll back to, taken instantly. The flexibility is genuinely beyond what rearranging physical space can do.
🧪 Exercise D1.1 — Inspect LVM if present (read-only, safe; may show nothing on a non-LVM machine)
lsblk # LVM volumes appear as TYPE lvm under their disks
sudo pvs 2>/dev/null || echo "no LVM physical volumes (or lvm2 not installed)"
sudo vgs 2>/dev/null; sudo lvs 2>/dev/null # volume groups and logical volumes✅ Expected result — click to reveal
On an LVM machine:
PV VG Fmt Attr PSize PFree
/dev/sda3 ubuntu-vg lvm2 a-- <62.00g 0
VG #PV #LV #SN Attr VSize VFree
ubuntu-vg 1 1 0 wz--n- <62.00g 0
LV VG Attr LSize
ubuntu-lv ubuntu-vg -wi-ao---- <62.00gOn a machine without LVM (many minimal cloud images), the pvs line prints the "no LVM" message — that's a valid result too.
What to read out of it:
- pvs/vgs/lvs are the three-level view: physical volumes fed in, grouped into a VG pool, carved into LVs. In lsblk, an LV shows as TYPE lvm nested under the partition that's its PV — the flexible layer made visible.
- VFree on the VG is the headroom: space in the pool not yet assigned to any LV, available to extend an LV live. VFree 0 means the pool is fully allocated — to grow anything you'd first add a disk (pvcreate + vgextend).
- Ubuntu's guided installer names things ubuntu-vg/ubuntu-lv by default — recognizing that layout tells you a machine is LVM-managed and can be grown without downtime, which changes how you respond to a filling disk.
D2. Swap — disk as overflow memory
🧠 Swap is disk space used as overflow when physical RAM fills: the kernel moves less-used memory pages out to a swap partition or swap file, freeing RAM for active work. It's a safety valve, not a speed feature — disk is thousands of times slower than RAM, so a system swapping heavily ("thrashing") is crawling. free -h shows RAM and swap usage; swapon --show lists active swap. Swap lets a box survive a memory spike instead of instantly OOM-killing (Module 8's kernel killer, Module 14's subject); but sustained swap use means you need more RAM, not more swap. Modern systems often use a swap file (/swapfile) rather than a partition — easier to resize.
RAM is your desk — fast to reach, limited surface. Swap is the filing cabinet across the room: far more room, far slower to fetch from. When the desk fills, you move rarely-needed papers to the cabinet (swap out) to keep working space clear. Occasional trips to the cabinet are fine; constantly shuttling papers back and forth because everything is needed at once is thrashing — you spend all your time walking, none working.
Where the analogy stops working. A desk that's "full" of papers is obviously full. RAM that looks full is usually mostly cache — papers you left out because there was room, instantly clearable when you need the space, not actually cluttering your work. The desk metaphor makes low "free" RAM look like a problem; in Linux it's the system using spare RAM productively as cache, and empty RAM would actually be waste. That inversion — full-looking memory is healthy, the available column is the truth — is the single most misread thing in Linux operations.
🧪 Exercise D2.1 — Read memory and swap honestly (read-only, safe anywhere)
free -h # RAM and swap, human-readable
swapon --show # active swap devices/files (may be empty)
cat /proc/meminfo | grep -E 'MemTotal|MemAvailable|SwapTotal|SwapFree' # the raw truth✅ Expected result — click to reveal
total used free shared buff/cache available
Mem: 7.8Gi 684Mi 6.8Gi 12Mi 644Mi 7.2Gi
Swap: 0B 0B 0B(this machine has no swap configured — common on containers and some cloud images)
What to read out of it:
- Read the columns in the right order: available (7.2Gi here) is what programs can actually get — not free. buff/cache (644Mi) is RAM used as disk cache, counted as "used" but reclaimable — which is why used + free doesn't tell the real story and available does.
- Swap: 0B — this machine has none, so a memory spike here goes straight to the OOM killer with no cushion (Module 8/14). A production box usually has some swap as a shock absorber.
- /proc/meminfo is the kernel's raw memory accounting (Module 2's /proc); free and every memory monitor read from it. When you want the exact numbers behind a monitoring alert, this is the source.
D3. Inodes — running out of files, not space
🧠 Module 3 taught that every file needs an inode (its metadata record), and that a filesystem has a fixed number of them, set at format time. The consequence lands here: a filesystem can run out of inodes while it has plenty of space — millions of tiny files (a runaway mail queue, a session-file leak, cache gone wild) exhaust the inode table, and then new files fail with "No space left on device" even though df -h shows the disk half empty. It's the most confusing storage error there is, because the message says "no space" and df seems to disagree. The tell is df -i (inode usage) — IUse% 100% with Use% 45% is the fingerprint. The fix: find and delete the swarm of tiny files (du-drill, then a careful find … -delete), because an ext4 filesystem's inode density (inodes per byte) is set at format time and can't be raised in place — growing the filesystem adds some inodes proportionally, but you can't simply mint more into the existing size.
A library has shelf space (bytes) and a card catalogue with a fixed number of blank cards (inodes). Fill the shelves and you're out of room the obvious way. But run out of catalogue cards — because someone shelved a million pamphlets, one card each — and you can't add a single new book even with empty shelves, because there's no card left to index it. "The library is full" is true, and pointing at the empty shelves doesn't help: the constraint is the catalogue, not the space.
Where the analogy stops working. A librarian can order more blank cards. An ext4 filesystem's inode density is fixed at format time — you can't raise inodes-per-byte in place. Growing the filesystem (LVM + resize2fs, D1) adds inodes proportional to the new space, but at the same density; to get more inodes for the same size you must delete files, reformat with a higher inode count, or move to a filesystem like xfs that allocates inodes dynamically. The catalogue's density can't be increased in place, which makes chronic inode exhaustion a delete-files, grow-the-volume, or reformat problem — not a quick in-place top-up.
🧪 Exercise D3.1 — Check the inode gauge (read-only, safe anywhere)
df -h / # the BYTE gauge
df -i / # the INODE gauge — the one people forget
# find which directory holds the most files (inode consumers), not the most bytes:
sudo find /var -xdev -type f 2>/dev/null | cut -d/ -f1-3 | sort | uniq -c | sort -rn | head✅ Expected result — click to reveal
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 252G 12G 237G 5% /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 16777216 276190 16501026 2% /then a count of files per top-level /var subdirectory, biggest first.
What to read out of it:
- Two independent gauges: 5% bytes used, 2% inodes used — a healthy machine has headroom on both. An inode-exhausted machine shows the second line at IUse% 100% while the first looks fine, and that mismatch is the whole diagnosis.
- The find | cut | sort | uniq -c | sort -rn pipeline (Modules 6–7, composed) counts files per directory — the inode-hunting equivalent of du's byte-hunting. When inodes are the problem, you hunt directories with the most files, not the most bytes; a directory with a million 1KB files is invisible to du -sh (1GB, unremarkable) but is the entire inode problem.
- -xdev keeps find on the one filesystem (Module 6's etiquette) — essential here, because you're diagnosing this filesystem's inode table, not wandering into others.
Part D — Interview questions
🎯 "Why is LVM required?" / "If a volume group VG0 already exists and we need to extend it up to 4 GB, how do we do it?" — both asked verbatim in Turing's 100+ Linux interview questions (2025)
LVM is required for flexible storage: resizing filesystems live (grow a volume running low without downtime or repartitioning), pooling multiple disks into one allocatable space, and snapshots for consistent backups — none of which fixed partitions allow. To extend VG0: add a new physical volume (sudo pvcreate /dev/sdc) then sudo vgextend VG0 /dev/sdc; to then grow a logical volume into that new space, sudo lvextend -L +4G /dev/VG0/lvname followed by sudo resize2fs /dev/VG0/lvname (ext4) or xfs_growfs (xfs) to grow the filesystem into the enlarged volume.
The details that separate candidates: the PV→VG→LV three-layer model stated cleanly; the crucial two-step growth (extend the LV and then grow the filesystem — people forget the filesystem doesn't auto-grow into a bigger LV); doing it live with no unmount as LVM's headline benefit; and snapshots as the other reason LVM earns its complexity. Naming resize2fs/xfs_growfs as the filesystem-grow step is the tell of someone who's actually done it.
🎯 A synthesis the banks under-ask — "the disk is full but df shows free space, what's happening?"
Two prime suspects, both taught here. Inode exhaustion: df -h shows free bytes but df -i shows IUse% 100% — too many files, not too many bytes; find and delete the swarm of tiny files (you can't add inodes to a live ext4 filesystem). Deleted-but-open file: df counts space that du can't see because a process holds a deleted file open — sudo lsof | grep deleted finds it, and restarting the process (or truncating via /proc/PID/fd) frees the space, since there's no name to rm. A third: files hidden under a mount point.
The details that separate candidates: checking both df -h and df -i reflexively when the error doesn't match visible space; knowing the deleted-open-file fix is restart/truncate, not rm; and connecting both back to Module 3 (inodes are finite; a file lives until its link count is zero and it's not held open). This is the storage question that most cleanly separates people who memorized commands from people who understand the filesystem — which is exactly why interviewers ask it.
Part E — Toolkit
E1. Production practice — symptoms and fixes
| Symptom | What is really happening | What to run | The fix |
|---|---|---|---|
| "No space left on device", df -h shows free space | Inode exhaustion — out of inodes, not bytes | df -i (look for IUse% 100%) | Find and delete the swarm of tiny files; long-term, more inodes / xfs |
| Deleted a huge file, df didn't shrink | A process still holds the deleted file open | sudo lsof | grep deleted | Restart the process, or truncate via > /proc/PID/fd/N — not rm |
| df says full but du of the same path finds far less | Deleted-open file, or files hidden under a mount point | lsof | grep deleted; findmnt to check for overlaid mounts | Free the open file, or investigate what's mounted over the directory |
| umount fails: "target is busy" | A process (maybe your shell) has a file open on it | cd / out first; sudo lsof /mnt/x or fuser -m /mnt/x | Stop the holders, then unmount; umount -l (lazy) as a careful last resort |
| Server won't boot after an fstab edit | A broken fstab line dropped boot to emergency mode | (from console/rescue) fix the line; had you run mount -a first… | Use UUID=, add nofail for optional mounts, always mount -a before reboot |
| Filesystem full and growing, but it's LVM | You can grow it live instead of firefighting deletions | vgs (check VFree) | lvextend -L +NG … then resize2fs/xfs_growfs — no downtime |
| Alarm: "memory almost full" but the app is fine | Low free is normal — RAM used as reclaimable cache | free -h — read the available column, not free | Nothing, unless available is near zero or swap is actively churning |
E2. Capstone — four tickets
🎫 Ticket 1 — "Root filesystem at 100%, site is down, fix it NOW"
Ticket text: "vm-web-2 is alerting / at 100% and the app is throwing write errors. Get us breathing room immediately, then find and fix the cause."
Worked answer — run the incident procedure (B3) in order. Localize: df -h confirms / is the full filesystem (not a separate /var). Breathing room, fast and safe: sudo apt clean (reclaims the package cache, Module 9 — instant, harmless), clear old logs if rotation failed (journalctl --vacuum-size=200M, Module 11), and check df -h recovered a few %. Hunt the cause: sudo du -sh /* 2>/dev/null | sort -rh | head, drill into the winner (/var → /var/log → the giant file). The deleted-open check — if du can't account for the space df shows used: sudo lsof | grep deleted for a service still writing a deleted log; restart it to free the space. Root cause: almost always log rotation that stopped working or was never configured — fix logrotate/journald limits so /var/log self-caps, and consider moving /var to its own filesystem so a future flood can't take down /. The reply names the immediate reclaim, the specific culprit, and the prevention — not just "I deleted some files".
🎫 Ticket 2 — "'No space left on device' but df says the disk is half empty"
Ticket text: "The mail server can't write new files — every write fails with 'No space left on device' — but df -h shows /var at 48%. Is the disk lying?"
Worked answer: the disk isn't lying — you're reading the wrong gauge. df -h measures bytes; the error is almost certainly inode exhaustion. Confirm: df -i /var → expect IUse% 100% alongside the 48% bytes. Cause: a swarm of tiny files exhausted the fixed inode table — on a mail server, classically a stuck queue (/var/spool/) with millions of undelivered messages, or a session/cache leak. Find the swarm by file count, not size: sudo find /var -xdev -type f 2>/dev/null | cut -d/ -f1-4 | sort | uniq -c | sort -rn | head points at the directory holding the millions. Fix: carefully delete the swarm (find /var/spool/stuck -type f -delete after confirming it's safe to purge), which frees inodes; df -i recovers. Prevention: fix whatever produces the file swarm (flush/limit the mail queue, fix the leaking app), and note you cannot add inodes to a live ext4 filesystem — so if this recurs by design, the real fix is reformatting with more inodes or moving to xfs (dynamic inodes). The one-line lesson: when "no space" contradicts df -h, run df -i.
🎫 Ticket 3 — "We deleted the 30GB log but the disk is still full"
Ticket text: "The app log hit 30GB and filled the disk. We rm'd it — ls confirms it's gone — but df still shows the disk full. What now?"
Worked answer: the file's name is gone but its data isn't freed, because the app process still has it open and is still writing to it (Module 3: a file lives until link count zero AND no open handle). du can't see it (no name), df still counts it (blocks still allocated) — the classic divergence. Confirm: sudo lsof | grep deleted (or sudo lsof +L1) shows the app process, its file descriptor, the ~30GB size, and the (deleted) marker. Two fixes: (a) restart the app (sudo systemctl restart theapp, Module 11) — closes the descriptor, frees 30GB instantly — the clean choice if a brief restart is acceptable; (b) truncate in place without downtime: sudo truncate -s 0 /proc/<pid>/fd/<n> (or > /proc/<pid>/fd/<n>) empties the file the process is still holding, freeing the space while the app keeps running. Prevention: this is why apps should log to the journal (Module 11) or use logrotate with copytruncate/signals — so rotation frees space correctly instead of leaving a giant deleted-but-open ghost. Explain that rm was the wrong tool because there was no name to remove — the space was never the filename's to free.
🎫 Ticket 4 — "Database volume is filling and we can't take downtime"
Ticket text: "The Postgres data volume (/dev/data/pgdata, an LVM volume) is at 88% and climbing. We cannot stop the database. Give us headroom without an outage."
Worked answer — this is exactly what LVM exists for (D1). Because it's a logical volume, you grow it live, no unmount, no downtime. Check the pool: sudo vgs — is there VFree in the volume group? If yes, skip to extend. If the VG is full, add capacity first: attach a new disk (cloud console or physical), sudo pvcreate /dev/sdX, sudo vgextend data /dev/sdX — the pool grows. Extend the volume: sudo lvextend -L +50G /dev/data/pgdata (or -l +100%FREE to take all remaining pool space). Grow the filesystem into the bigger volume — the step people forget: sudo resize2fs /dev/data/pgdata (ext4) or sudo xfs_growfs /pgdata-mountpoint (xfs). df -h now shows the headroom, Postgres never noticed. Then the real conversation: an 88%-and-climbing volume means growth outpacing capacity — set monitoring to alert earlier, and plan capacity rather than repeatedly firefighting. The framing: LVM turned a potential emergency-downtime into a two-minute live operation, which is the entire reason production storage is built on it.
E3. Documentation reference
| Topic | Authoritative source | Verified link |
|---|---|---|
| Block devices and partitions | lsblk(8) | lsblk(8) |
| Space usage | df(1), du(1) | df(1) · du(1) |
| Mounting | mount(8), fstab(5) | mount(8) · fstab(5) |
| Open files (the deleted-open trap) | lsof(8) | lsof(8) |
| LVM, swap, inodes | lvm(8), swapon(8), free(1), inode(7) | lvm(8) · swapon(8) · free(1) · inode(7) |
| The kernel side beneath this module | Companion track | Untitled |
E4. Self-assessment
Answer out loud, without notes. The section number tells you where to re-read.
- Name the three layers from raw disk to a usable file, and what turns each into the next. (A1–A3)
- What does lsblk show that df doesn't, and vice versa? (A1, B1)
- df vs du — what does each measure, and name two reasons they legitimately disagree. (B1–B2)
- Walk the full-disk incident procedure, command by command. (B3)
- A huge file was deleted but the disk stayed full. Explain the mechanism and the fix (and why rm isn't it). (B2, B3)
- What is mounting, what is a mount point, and why can't you unmount a "busy" filesystem? (C1)
- What is /etc/fstab, why reference filesystems by UUID, and what one command tests an edit safely? (C2)
- Explain LVM's three layers, and the two-step process to grow a filesystem live. (D1)
- Why is low "free" memory usually fine, and which column actually matters? When is swap a problem? (D2)
- How can a half-empty disk throw "No space left on device"? What command reveals it? (D3)
- Why can't you add inodes to a live ext4 filesystem, and what are the real fixes for inode exhaustion? (D3)
- Which storage problems are invisible to ls/du/find, and how do you see them anyway? (B2, D3)
E5. Sources
GeeksforGeeks — Linux Interview Questions (70+) (updated July 2026) · Turing — 100+ Linux Interview Questions (2025) · Adaface — 96 Linux Commands interview questions (September 2024).
Corpus honesty note: storage is reasonably covered in the banks — disk usage (df/du), permanent mounting, LVM, and largest-files questions all appear verbatim (quoted above). What the printed corpus under-asks are the counter-intuitive incidents — inode exhaustion, the df/du divergence, deleted-but-open files — which are precisely what a real storage interview and a real 3 a.m. page turn on. This module teaches those to the live "the disk is full but…" format, because that scenario, not command recall, is where storage competence is actually tested.
All documentation links on this page were fetched and confirmed reachable on 2 September 2026. df, du, lsblk, free, and the deleted-open-file demonstration were generated on a live Ubuntu 24.04 system; LVM outputs are the canonical format from an LVM-managed machine, since this authoring environment's disk is not LVM.