Disk Cleanup β Reclaiming Space on Linux Servers
Last reviewed: 2026-09-17
Purpose: A practical workflow for finding and reclaiming disk space on Linux hosts, with emphasis on Docker artefacts, container logs, qcow2 virtual machine images, and system logs. See also the step-by-step walkthrough in UbuntuDiskCleanUp.md.
1. Diagnose Before Deleting
Find the filesystem that is actually full, then walk down the directory tree towards the culprit. Deleting without measuring wastes time and risks removing something in use.
df -h # which filesystem is full
df -i # inode exhaustion looks identical to "disk full"
du -xh / --max-depth=1 | sort -h # top-level consumers (stay on one filesystem)
du -xh /var --max-depth=2 | sort -h | tail -20
du -xh /var/lib --max-depth=1 | sort -h
du -x stays on one filesystem, so it does not descend into mounted volumes. Alternatively use ncdu /var, which gives an interactive, navigable view β much faster for hunting space than repeated du calls.
Two common traps: a deleted-but-open file still holds its blocks until the process exits (sudo lsof +L1 lists them), and inode exhaustion on filesystems with millions of small files reports free space while refusing new writes.
2. Docker
Docker typically accounts for the largest recoverable amount on an application host: stopped containers, superseded image layers, orphaned volumes, and build cache.
docker system df # space used vs. reclaimable, by category
docker system df -v # per-image, per-container, per-volume breakdown
docker container prune # stopped containers
docker image prune # dangling (<none>) images
docker image prune -a # all images not referenced by a container
docker builder prune # build cache
docker volume prune # volumes not used by any container
docker network prune # unused networks
# Everything at once β read the confirmation prompt
docker system prune -a --volumes
Reclaim build cache selectively rather than wholesale when builds are frequent:
docker builder prune --filter until=168h # drop cache older than 7 days
docker builder prune --keep-storage 10GB # keep the most recent 10 GB of cache
Container logs
The json-file log driver writes to /var/lib/docker/containers/<id>/<id>-json.log and, unconfigured, grows without limit. Truncate a log that is already filling the disk:
Then prevent recurrence by capping log size globally in /etc/docker/daemon.json and restarting the daemon:
Existing containers keep their old logging settings; recreate them for the cap to apply.
3. KVM / libvirt Virtual Machines
Virtual machine disk images are usually the single largest consumer on a virtualisation host. Two separate things must be kept under control: images belonging to deleted VMs, and qcow2 files that have grown beyond the space actually used inside the guest.
virsh list --all # defined VMs, running or not
virsh domblklist <vm> # disks attached to a VM
qemu-img info /var/lib/libvirt/images/<vm>.qcow2 # virtual vs. actual size
Remove a VM together with its storage:
Find orphaned images no longer referenced by any VM definition, then delete them deliberately:
for f in /var/lib/libvirt/images/*.qcow2; do
virsh domblklist --all $(virsh list --all --name) 2>/dev/null | grep -q "$(basename "$f")" \
|| echo "unreferenced: $f"
done
Compacting a qcow2 image (internal snapshots permitting) frees space the guest once wrote but no longer uses:
# 1. Inside the guest: fill free space with zeros so they can be discarded
sudo dd if=/dev/zero of=/zero.fill bs=1M status=progress; sudo rm -f /zero.fill
# 2. On the host: rewrite a sparse copy that only stores allocated zeroes
sudo qemu-img convert -O qcow2 -c /var/lib/libvirt/images/vm.qcow2 \
/var/lib/libvirt/images/vm-compact.qcow2
sudo mv /var/lib/libvirt/images/vm-compact.qcow2 /var/lib/libvirt/images/vm.qcow2
Interpretation of qemu-img info: when the disk size is close to the virtual size, the guest really is using the space and only guest-side cleanup will help. When the disk size is far below the virtual size, the image is sparse and compaction reclaims the difference.
4. System Logs, Caches and Packages
Journal logs are usually capped already, but a log storm can push them far beyond the default.
journalctl --disk-usage # how much the journal currently holds
sudo journalctl --vacuum-time=7d # keep only the last 7 days
sudo journalctl --vacuum-size=500M # or cap by total size
# /etc/systemd/journald.conf
# SystemMaxUse=500M
Other routine targets:
sudo apt clean # clear downloaded .deb archives in /var/cache/apt
sudo apt autoremove # remove packages no longer required
snap list --all | awk '/disabled/{print $1, $3}' # old snap revisions
sudo du -xh /var/log --max-depth=1 | sort -h
sudo find /var/log -type f -name '*.gz' -delete # rotated compressed logs no longer needed
Old kernels, crash dumps in /var/crash, and package caches in /var/cache/ are frequent offenders on hosts that have been running for years.
5. Where the Space Actually Is
| Target | Typical gain | Risk |
|---|---|---|
| Unused VMs and their images | Very large | Low if the VM is genuinely retired |
Inside the guests (old logs, package caches, docker system prune) |
Large | Low |
| qcow2 compaction | Large, but slow | Low; do not use -c on images with active internal snapshots |
| Docker volumes | Variable | High β --volumes deletes data with no container referencing it |
| Journal and rotated logs | Usually modest | Low |
| apt / snap / package caches | Modest | Low |
On multi-terabyte hosts, logs are rarely the problem: the wins come from retiring virtual machines, cleaning inside running guests, and pruning container artefacts. Measure first, delete second, and never run a destructive prune on a host whose backups have not been verified.