Skip to main content
Settings
Color Mode
Theme Skin
Background

Appearance preferences are saved in this browser only.

Environment
Current Environment Production

Built with JEKYLL_ENV=production. Changes require deployment.

Theme & Build
Jekyll v3.10.0
Last Build Jul 09, 12:19
Page Location
Page Info
Layout article
Collection tools
Path _tools/duf-honest-review.md
URL /tools/duf-honest-review/
Date 2026-07-09

duf: the honest review

Verdict: install it for the five-second question you actually run df for — “is anything about to fill up?” — and let it draw you a boxed, colored table sorted however you like. But leave df (or duf --json) in your scripts, learn that it files your mounts into two separate tables, and don’t be surprised when it refuses to tell you which version of itself you’re running. duf is df with a picture: run it bare and you get a Unicode-boxed table of every mount, use percentages and all. We reach for it whenever a build box starts throwing “no space left on device.” We also spent a few minutes hunting for a tmpfs mount that was sitting in a second table the whole time — and that hunt is the review.

duf is free and open source (MIT). We have no relationship with the project and nothing to sell. Like its siblings dust, fd, bat, and eza, the interesting part isn’t price or telemetry — it’s a handful of defaults that ambush anyone arriving from the coreutils tool it replaces. dust was the du half of “where did my disk go?”; duf is the df half: not what’s using space inside a tree, but how full each mounted filesystem is. We’ll show each surprise with output we captured on a fresh Ubuntu 24.04 box.

Install — and this one really does apt (mostly)

Its sibling dust isn’t packaged on Ubuntu at all. duf is, under its own name, no fdfind/batcat rename tax:

$ apt-cache policy duf
duf:
  Installed: (none)
  Candidate: 0.8.1-1ubuntu0.24.04.3
  Version table:
     0.8.1-1ubuntu0.24.04.3 500
        500 mirror+file:/etc/apt/apt-mirrors.txt noble-updates/universe amd64 Packages
     0.8.1-1build1 500
        500 mirror+file:/etc/apt/apt-mirrors.txt noble/universe amd64 Packages

So sudo apt install duf and the command is duf. One name, no surprises — until you ask it who it is:

$ duf --version
duf (built from source)
$ df --version | head -1
df (GNU coreutils) 9.4

df tells you it’s coreutils 9.4. duf shrugs. The Ubuntu package builds the binary without stamping the version into it, so --version prints the literal placeholder built from source no matter which duf you have. apt knows it’s 0.8.1; the tool itself won’t say. It’s harmless until you’re filing a bug and the maintainer asks “which version?” — the honest answer is “check apt, because duf isn’t telling.”

Why you’d reach for it

Run it bare and it answers “how full is everything?” in one glance:

$ duf
╭───────────────────────────────────────────────────────────────────╮
│ 3 local devices                                                   │
├────────────┬────────┬───────┬────────┬────────┬──────┬────────────┤
│ MOUNTED ON │   SIZE │  USED │  AVAIL │  USE%  │ TYPE │ FILESYSTEM │
├────────────┼────────┼───────┼────────┼────────┼──────┼────────────┤
│ /          │ 144.3G │ 56.3G │  87.9G │  39.0% │ ext4 │ /dev/root  │
│ /boot      │ 880.4M │ 63.5M │ 755.3M │   7.2% │ ext4 │ /dev/sda16 │
│ /boot/efi  │ 104.3M │  6.1M │  98.2M │   5.8% │ vfat │ /dev/sda15 │
╰────────────┴────────┴───────┴────────┴────────┴──────┴────────────╯

On a real terminal the USE% column is a colored bar that goes green→yellow→red as a mount fills, which is the entire pitch: you spot the mount at 98% before it hits 100. Sorting and filtering are one flag away — duf --sort size, duf --only local, duf --output mountpoint,size,avail for only the columns you care about. Compare the df line everyone actually memorizes for the same job:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root       145G   57G   88G  40% /
tmpfs           7.9G   84K  7.9G   1% /dev/shm
tmpfs           3.2G 1016K  3.2G   1% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
efivarfs        128M   26K  128M   1% /sys/firmware/efi/efivars
/dev/sda16      881M   64M  756M   8% /boot
/dev/sda15      105M  6.2M   99M   6% /boot/efi
tmpfs           1.6G   12K  1.6G   1% /run/user/1001

Same machine, and now look closely, because those two outputs disagree about almost everything: how many mounts exist, which ones show, what order the columns come in, and the last digit of every size.

The surprise that hides your mounts

Here’s the one that cost us the time. Count the rows: df -h shows eight filesystems in one flat list. duf’s first table shows three. Where did the tmpfs mounts go? Not gone — filed into a second table below the first:

$ duf
...
╭─────────────────────────────────────────────────────────────────────────╮
│ 6 special devices                                                       │
├──────────────┬────────┬─────────┬────────┬────────┬────────┬────────────┤
│ MOUNTED ON   │   SIZE │    USED │  AVAIL │  USE%  │ TYPE   │ FILESYSTEM │
├──────────────┼────────┼─────────┼────────┼────────┼────────┼────────────┤
│ /dev         │   7.8G │      0B │   7.8G │        │ devtmp │ devtmpfs   │
│ /dev/shm     │   7.8G │   84.0K │   7.8G │   0.0% │ tmpfs  │ tmpfs      │
│ /run         │   3.1G │ 1016.0K │   3.1G │   0.0% │ tmpfs  │ tmpfs      │
...

duf splits every mount into local and special (plus network and fuse when you have them) and prints each group as its own boxed table. So when you run duf, scan the top table, and don’t see /run or /dev/shm, they haven’t vanished — they’re special, in a table you have to scroll to. The reverse bites too: duf shows /dev (a devtmpfs) that df -h omits entirely.

$ df -h | grep -c devtmpfs
0
$ duf | grep -c devtmpfs
1

The knobs that put you back in control are --only and --hide:

$ duf --only local          # only the real disks, one table
$ duf --hide special        # everything except the tmpfs/devtmpfs group
$ duf --only-fs ext4,vfat   # filter by filesystem type instead

duf --hide special collapses it back to the three-mount view. Until you know those flags exist, the two-table split reads like duf is hiding half your system — it’s only being tidy about it.

The columns don’t match df — so your muscle memory lies

Every df user has df | awk '{print $4}' burned into their fingers, because $4 is Avail. Point that instinct at duf and it quietly returns the wrong number:

$ df -h / | awk 'NR==2{print $4}'
88G
$ duf --only local / | awk 'NR==6{print $4}'
144.3G

88G is the available space. 144.3G is the total size. The boxed table means field $1 is the border, $2 is the mount point, $3 is another , and $4 lands on SIZE, not AVAIL. duf never collapses that box, even in a pipe — redirect it and the ANSI color drops, but the / box-drawing characters stay, so awk/cut see a wall of borders, not columns. There’s exactly one correct way to feed duf to a script, and it’s --json:

$ duf --json | head -12
[
 {
  "device": "/dev/root",
  "device_type": "local",
  "mount_point": "/",
  "fs_type": "ext4",
  "type": "ext2/ext3",
  "opts": "rw,relatime",
  "total": 154894188544,
  "free": 94431916032,
  "used": 60445495296,
  "inodes": 19529728,

Raw bytes, mount options, inode counts — everything a script wants, and nothing a box-drawing parser has to fight. If you’re piping duf anywhere that isn’t a human’s eyeballs, use --json; if you’re not, use df.

The numbers round the other way, too

While you’re comparing, note that duf and df -h disagree on the last digit of every size: duf says / is 144.3G, df -h says 145G; duf says /boot is 880.4M, df -h says 881M. duf prints one decimal place and truncates; df -h rounds to whole units. Same bytes (--json shows / is exactly 154,894,188,544 bytes — that’s 144.26 GiB), different rounding. As with dust vs du, never diff a duf figure against a df figure and conclude space appeared or vanished. It’s the display, not the disk.

Where plain df still wins

duf is a dashboard. df is a number source, and three jobs stay with it:

  • Scripts. df -B1 --output=avail /data | tail -1 gives one stable integer with no box art. If you insist on duf in automation, it’s duf --json piped through jq, never the table.
  • Ubiquity. df is on every Unix box on Earth right now, no install. duf is a thing you apt install first — fine on your laptop, one more dependency in a container.
  • -i for inodes at a glance. df -i is muscle memory when you’re out of inodes but not space. duf can show it (duf --output mountpoint,inodes_avail), but the short flag isn’t shorter than df -i.

What it costs and the free alternative

It costs nothing — MIT, no account, no telemetry. The free, zero-install alternative is the df you already have: df -h for the flat view, df -hT if you also want the TYPE column duf gives you, df -i for inodes. duf buys you the color bar, the sorting, and the at-a-glance grouping; df buys you being everywhere and parseable. If you want duf’s glanceability and a way to act on what you find, that’s a different tool class — a TUI like ncdu drills into a filesystem to delete hogs, where duf only reports.

What made us close the tab

Nothing closed it — duf earns a spot for the “which mount is about to fill?” moment, and the color bar answers it faster than reading df percentages. The honest caveats, in the order they’ll bite:

  • It splits your mounts into two tables. local on top, special (tmpfs, devtmpfs) below. A mount “missing” from the first table is almost always in the second. --only/--hide control it.
  • The boxed columns aren’t df’s columns. awk '{print $4}' gets SIZE, not AVAIL, and the box characters survive a pipe. For scripts, --json is the only sane interface.
  • --version won’t tell you the version. The Ubuntu build prints built from source; ask apt instead.
  • Its sizes won’t match df’s to the last digit — one decimal, truncated, vs whole-unit rounding. Same bytes.

When it goes wrong: if you run duf and a filesystem you know is mounted isn’t in the output, don’t assume it’s unmounted — scroll down for the special table, or run duf --all to include the ones it filters by default. And if a script that used to parse df starts doing nothing sensible after someone swapped in duf, the fix isn’t a cleverer awk — it’s duf --json | jq, or putting df back.