When a deploy fails with “no space left on device,” or monitoring shows a mount climbing toward 100%, you need two answers in order: which filesystem is full? and where inside that mount is the growth? That is what df and du are for. df reports free space per mounted filesystem. du walks directories and shows how much each path uses.

You do not need every flag on day one. Start with a human-readable df, then drill with du into the fullest mount. The sections below walk through the commands you will reach for most often — with enough context that each one feels intentional, not magical.

Warm-up: playground plus a df glance

Build a small tree you can measure repeatedly. It mirrors a messy project: logs that grew, a chunky artifact, and a dependency folder you often want to skip later.

Create it once, then reuse it for the du examples:

mkdir -p demo-disk/{logs,src,cache,node_modules/pkg}
echo 'app' > demo-disk/src/main.js
echo 'tiny' > demo-disk/logs/app.log
dd if=/dev/zero of=demo-disk/logs/huge.log bs=1k count=200 status=none
dd if=/dev/zero of=demo-disk/cache/blob.bin bs=1k count=80 status=none
echo 'dep' > demo-disk/node_modules/pkg/index.js

Before hunting directories, ask which filesystem you are standing on. -h prints sizes in powers of 1024 (K, M, G) that humans can scan quickly:

df -h .

Typical columns look like this (sizes and mount points will differ on your machine):

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        50G   38G   10G  80% /

Read Use% and Avail first — that is the “am I about to fail writes?” signal. Mounted on tells you the path whose children you should measure with du next.

A fuller picture of every mounted filesystem:

df -h

Note: Always run df before a deep du /. Confirming the full mount saves you from sorting an entire root tree when only /var is critical.

Human sizes and inodes

-h (human, 1024-based) is the everyday default. Some people prefer -H (1000-based, SI-style). Pick one and stay consistent when comparing runs; mixing them makes “did it grow?” harder than it needs to be.

“No space left” is not always about bytes. Filesystems also run out of inodes when millions of tiny files exhaust the index while df -h still shows free gigabytes.

Check inode usage:

df -i
df -ih .

If IUse% is at or near 100% while Use% for bytes looks fine, the fix is deleting or relocating many small files — not looking for one giant log.

Note: Reach for df -i whenever byte usage looks healthy but creates, extracts, or package installs still fail with “no space.”

One mount or path

Point df at a path to see the filesystem that path lives on — useful when /, /var, and /home are separate mounts.

df -h /var

Add -T when you care about the filesystem type (ext4, xfs, tmpfs, overlay):

df -hT

Sample shape:

Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/sda1      ext4   50G   38G   10G  80% /
tmpfs          tmpfs  1.6G  2.1M  1.6G   1% /run

Note: tmpfs mounts sit in RAM. A full /run or /tmp on tmpfs is a memory-pressure problem dressed as disk — do not waste time du-ing a spinning disk for it.

Measure a directory with du

Once df names the mount, du finds the hog. Summarize one tree with a single human-readable total:

du -sh demo-disk

You should see one line — total size of the playground (exact value depends on block size, but it will be a few hundred kilobytes):

284K	demo-disk

To see each top-level child without listing every nested file, limit depth:

du -h --max-depth=1 demo-disk

On many systems the short form is equivalent:

du -hd1 demo-disk

Typical ranking:

84K	demo-disk/cache
4.0K	demo-disk/node_modules
4.0K	demo-disk/src
204K	demo-disk/logs
284K	demo-disk

Note: The last line is the total for the starting path. Compare siblings above it to decide where to drill next.

Sort the hogs

Raw du order follows directory walk order, not size. Pipe through sort -h so the largest paths rise to the bottom (or top with -r).

du -h --max-depth=1 demo-disk | sort -h

Drill into the winner — here, logs:

du -h --max-depth=1 demo-disk/logs | sort -h

On a real host, the same pattern against a full mount is the everyday triage loop:

sudo du -hd1 /var | sort -h

Then repeat on the largest child (/var/log, /var/lib, and so on) until the offender is obvious.

Note: Prefer depth-1 passes over du -ah / | sort -h. Shallow, repeated drills are faster to read and kinder to busy disks.

Apparent size vs disk usage

By default, du reports space allocated on disk (blocks used). Sparse files and some compressed or unusual layouts can make “apparent” byte length differ from allocated size.

Compare allocated usage to apparent size when a file looks huge in ls -l but barely moves the needle in du:

du -sh demo-disk/logs/huge.log
du -sh --apparent-size demo-disk/logs/huge.log

Note: Trust default du for “will deleting this free space on this filesystem?” Trust apparent size when you are reasoning about logical file length or transfer size.

Exclude noise

Project trees bury signal under node_modules, build caches, and virtualenvs. Exclude patterns keep the first pass focused on your growth.

Skip node_modules while measuring the playground:

du -h --max-depth=1 --exclude='node_modules' demo-disk

On a home directory, exclusions often look like:

du -hd1 --exclude='.cache' --exclude='node_modules' "$HOME" | sort -h

Note: Excludes are for triage readability. When you must account for every byte on a mount, drop them and accept the noise — or measure from a cleaner starting path.

When df and du disagree

Two common traps make the numbers fight each other.

Deleted but still open files: rm removes the directory entry, but blocks stay allocated until every process closes the file. df stays high; du under the mount looks fine. Find the holder with lsof (deleted-open patterns), then restart or truncate via the process FD.

Other filesystems under the tree: By default, du can cross into nested mounts and confuse “usage on this disk.” Stay on one filesystem with -x:

du -xhd1 /var | sort -h

Note: When monitoring says the disk is full but a careful du -x on that mount cannot explain the used bytes, check inodes (df -i) next, then deleted-open files with lsof — not another unsorted root scan.

Quick reference card

Keep this nearby until the flags become muscle memory:

GoalCommand
Free space (human)df -h
This path’s filesystemdf -h . or df -h /var
Include FS typedf -hT
Inode usagedf -i / df -ih
Directory totaldu -sh path
Top-level childrendu -hd1 path
Largest first (sort)du -hd1 path | sort -h
One filesystem onlydu -xhd1 /var
Skip noisy dirsdu -hd1 --exclude='node_modules' path
Apparent sizedu -sh --apparent-size file

Practice drills

Use demo-disk plus your live mounts and try these without peeking. The point is to choose df vs du with intent, not to memorize syntax under pressure.

  1. Show human-readable free space for the filesystem that contains your current directory.
  2. Check whether that filesystem is low on inodes.
  3. Print each top-level child of demo-disk with sizes, sorted smallest to largest.
  4. Re-run the same summary while excluding node_modules.
  5. Explain (in one sentence) what you would do next if df -h /var shows 100% but sudo du -xhd1 /var cannot account for the used space.

When you are ready to compare, here are solid answers — not the only ones, but clear and portable:

df -h .
df -ih .
du -hd1 demo-disk | sort -h
du -hd1 --exclude='node_modules' demo-disk | sort -h
# Next: df -i /var, then lsof for deleted-open files on that mount

If you can work through those five comfortably, you already cover most real disk triage: confirm the full filesystem, rule out inode exhaustion, rank directory hogs, cut noise with excludes, and know when to leave du for lsof. Start with df, then tighten with shallow du passes only where the mount is actually full.