When the disk of a Docker server fills up, containers fail with "no space left on device" and databases stop writing, and the usual reflex, docker system prune, deletes things without telling you whether they were the cause. This wizard first finds what holds the space: container logs, images, build cache, volumes, a container's own filesystem, the system journal, deleted files still open, or inodes.
Answer one question at a time. Each step shows a command to run on your server and what its output means; every command that deletes something says what it deletes and is marked before you run it. Replace <container>, <service>, <volume> and <mount> with your own names. The page never runs anything and sends nothing anywhere; your answers are kept in the page's link, never your output.
Find the cause, one step at a time
Which filesystem is full?
Start from the numbers the kernel reports for every mounted filesystem. A full disk shows Use% at or near 100% and almost nothing under Avail; containers then fail with no space left on device, databases stop writing and Docker itself may refuse to start containers.
Docker keeps its data under /var/lib/docker unless its data-root was moved; on most servers that is the root filesystem /. If you are unsure where it is, docker info --format "{{.DockerRootDir}}" prints it, and in every command below you replace /var/lib/docker with that path.
df -hEvery step as text
The same tree as the wizard above, all branches at once, for reading or printing.
Show every question, check and solution of the wizard
Which filesystem is full?
Start from the numbers the kernel reports for every mounted filesystem. A full disk shows Use% at or near 100% and almost nothing under Avail; containers then fail with no space left on device, databases stop writing and Docker itself may refuse to start containers.
Docker keeps its data under /var/lib/docker unless its data-root was moved; on most servers that is the root filesystem /. If you are unsure where it is, docker info --format "{{.DockerRootDir}}" prints it, and in every command below you replace /var/lib/docker with that path.
df -hAnswers
- The filesystem that holds Docker's data (often /) is at or near 100%: go to “Is the space in Docker, in the logs, or elsewhere?”
- Only another filesystem is full (for example /boot or /home): go to “The space is outside Docker”
- None is near 100%, yet writes fail with "no space left on device": go to “Are the inodes used up?”
Are the inodes used up?
A filesystem has two limits: bytes and inodes. Every file and directory uses one inode, and when they run out no new file can be created, even with gigabytes free. Millions of tiny files (image layers, build cache, a cache directory of an application, session files) exhaust inodes long before they fill the bytes.
df -i shows the same filesystems counted in inodes; look at IUse% on the filesystem where writes fail.
df -iAnswers
- IUse% is at or near 100%: go to “The filesystem ran out of inodes”
- IUse% is well below 100%: go to “No branch matched: collect what you need to dig further”
Is the space in Docker, in the logs, or elsewhere?
Before deleting anything, find who holds the space. This command sums the three usual suspects: Docker's data directory, containerd's (where the containerd image store keeps image data), and the system logs under /var/log. A directory that does not exist on your server is skipped silently.
Compare the sizes with the Used column that df -h showed for the same filesystem. du reads every file, so on a large directory it can take a minute.
sudo du -sh /var/lib/docker /var/lib/containerd /var/log 2>/dev/nullAnswers
- /var/lib/docker (or /var/lib/containerd) holds most of the used space: go to “Where inside Docker's data?”
- /var/log holds most of it: go to “Is it the system journal?”
- Together they hold far less than df shows as used: go to “Find the largest directories”
Find the largest directories
The space is outside Docker and the logs. List the largest directories of the full filesystem, two levels deep; -x keeps du on that filesystem and does not count other mounts under it.
If even the largest directories add up to much less than Used, the space is held by files that were deleted but are still open: du no longer sees them, while the kernel still counts them.
sudo du -xh --max-depth=2 / 2>/dev/null | sort -h | tail -n 15Answers
- A directory outside Docker and the logs is large: go to “The space is outside Docker”
- The directories add up to much less than df's Used: go to “Deleted files are still held open”
Where inside Docker's data?
Docker's data directory splits by kind: containers holds each container's configuration and its log file, overlay2 holds image layers, the writable layers of containers and the build cache, and volumes holds the data of named and anonymous volumes. The largest one points to the cause.
The command lists the first level of /var/lib/docker, smallest to largest. If the space is under /var/lib/containerd instead, image data and build cache are there.
sudo du -h --max-depth=1 /var/lib/docker | sort -hAnswers
- containers is the largest: go to “Are container logs the cause?”
- overlay2 is the largest, or the space is under /var/lib/containerd: go to “What does Docker itself count?”
- volumes is the largest: go to “Which volumes, and are they used?”
Are container logs the cause?
With the default json-file logging driver, Docker writes everything a container prints to a file next to its configuration, and by default it never rotates or caps that file. One chatty container can write gigabytes a day. docker system df does not count these files, which is why they are easy to miss.
The command runs as root in a shell because only root can read the containers directory; it lists the five largest log files. The directory name in each path is the container's full ID.
sudo sh -c 'du -h /var/lib/docker/containers/*/*-json.log' | sort -h | tail -n 5Answers
- One or more *-json.log files are large: go to “Container logs are not rotated”
- The log files are small, or there are none (another logging driver): go to “What does Docker itself count?”
What does Docker itself count?
docker system df sums Docker's own objects in four rows: images, containers (their writable layers), local volumes and the build cache. RECLAIMABLE is what is not used by any container right now and could be removed.
Pick the row that holds the most. A large RECLAIMABLE on images or build cache is the easy case; a large Containers row means a container writes into its own filesystem instead of a volume.
docker system dfAnswers
- Images, with a large RECLAIMABLE: go to “Old and unused images fill the disk”
- Build Cache: go to “The build cache keeps growing”
- Containers: go to “A container writes into its own filesystem”
- Local Volumes: go to “Which volumes, and are they used?”
- No row is large, or images are large but nothing is reclaimable: go to “No branch matched: collect what you need to dig further”
Which volumes, and are they used?
A volume outlives the container that created it: removing a container, or docker compose down without -v, leaves its volumes in place. Over months, test stacks and old versions leave volumes no container uses, and some of them hold real data.
The verbose form lists every volume with its size and LINKS, the number of containers that use it. LINKS 0 means no container, running or stopped, uses the volume now; it does not mean the data in it is worthless.
docker system df -vAnswers
- Large volumes have LINKS 0 (no container uses them): go to “Volumes no container uses”
- The large volume is used by a container I run: go to “The data of a running service keeps growing”
Is it the system journal?
On systemd servers the biggest tenant of /var/log is usually the journal in /var/log/journal. It keeps every service's output, and by default it may grow to 10% of the filesystem (up to 4 GB per journal), which on a small or already full disk is space you need.
The command prints how much the active and archived journal files take. It only reads.
sudo journalctl --disk-usageAnswers
- The journal takes several gigabytes: go to “The system journal is too large”
- The journal is small; other files in /var/log are large: go to “Log files in /var/log are not rotated or removed”
Container logs are not rotated
The json-file driver keeps every line a container has printed since it was created. The lasting fix is a size cap: max-size and max-file for json-file, or the local driver, which rotates and compresses by default. A cap set in daemon.json applies only to containers created after the change, so existing containers must be recreated.
Emptying a log file frees the space now, and deletes that container's log history: whatever the log held, including the lines that explain a past incident, is gone. Copy the end of it first if you may need it.
What to do
- Match the ID in the largest log's path to a container name.
docker ps -a --no-trunc --format "{{.ID}} {{.Names}}" - Read the last lines first, and copy what you may need: the next step erases them.
docker logs --tail 200 <container> - Empty that container's log file. This deletes its whole log history; the container keeps running and writing to the same file.
sudo truncate -s 0 "$(docker inspect --format '{{.LogPath}}' <container>)" - Cap the logs for good: set
log-drivertolocal, orjson-filewithlog-optsmax-size(for example10m) andmax-file(for example3), in/etc/docker/daemon.json, or per service underlogging:in the Compose file. - After changing
daemon.json, restart Docker so it reads the file. Every container stops and starts again (those with a restart policy).sudo systemctl restart docker - Recreate the containers so the new log settings apply to them. Data in volumes stays; anything a container wrote into its own filesystem is lost.
docker compose up -d --force-recreate - Check what was freed.
df -h
Old and unused images fill the disk
Every docker pull of a new version keeps the old one. A dangling image (<none> in the list) is an old version whose tag moved on; an unused image is one no container, running or stopped, was created from. Updating stacks for months leaves gigabytes of both.
docker image prune removes dangling images only. With -a it removes every image not used by an existing container, including ones you may want again: they are not lost for good, only pulled again on the next start, which needs the registry to be reachable and the tag to still exist.
What to do
- See the images and their sizes.
docker image ls - Remove dangling images. It deletes only untagged old versions.
docker image prune - If that is not enough, remove every image no container uses. Stopped containers keep their images; a stack you took down with
docker compose downwill pull its images again.docker image prune -a - Check what was freed.
df -h
The build cache keeps growing
Every docker build and docker compose build keeps its layers and downloaded files in the build cache so the next build is faster. On a server that builds images itself (a CI runner, a deploy that builds on the host), the cache grows with every change, and only its garbage collection limits it.
Removing build cache never touches images, containers or volumes: the next build is slower, because it starts without cache.
What to do
- See what the cache holds.
docker buildx du - Remove cache not used in the last week. It deletes build cache only.
docker builder prune --filter until=168h - Or remove all of it, if the disk is still full.
docker builder prune -a - Keep it in check from now on with the builder's garbage collection limits in
daemon.json(thebuilder.gcsettings). - Check what was freed.
df -h
A container writes into its own filesystem
Anything a container writes outside its volumes goes into its writable layer under overlay2: uploads, a cache, logs to a file, a database without a volume. That data grows for as long as the container exists, and it is deleted with the container, so it is both a disk problem and a data-loss risk.
The fix is to find the path the application writes to and put it on a volume. Stopped containers that are no longer needed hold their writable layers too.
What to do
- List containers with the size of their writable layer (
SIZE).docker ps -a -s --format "{{.Names}} {{.Size}}" - See which files the large one added or changed (
AandC).docker diff <container> | head -n 50 - Mount that path as a volume in the Compose file (
volumes:of the service), after copying out anything you need from it withdocker cp. - Recreate the service with the volume. Its old writable layer, and every file in it, is deleted.
docker compose up -d --force-recreate <service> - Remove stopped containers you no longer need. It deletes every stopped container and its writable layer, not volumes.
docker container prune - Check what was freed.
df -h
Volumes no container uses
An unused volume can be leftover test data, or the only copy of a database whose stack is down right now. Removing a volume deletes its data for good; no Docker command brings it back. Look inside each one and back up what you are unsure of before removing it.
docker volume prune removes unused anonymous volumes only (since Docker Engine 23); with -a it also removes every unused named volume. Prefer removing the volumes you checked, by name.
What to do
- List the volumes no container uses.
docker volume ls --filter dangling=true - See which Compose project created a volume (its labels) and when.
docker volume inspect <volume> - Look at what it holds.
sudo ls -la /var/lib/docker/volumes/<volume>/_data - Back up any volume whose data you may need, before removing it.
- Remove a volume you checked. Its data is deleted for good.
docker volume rm <volume> - Or remove every unused anonymous volume at once. Containers started without a named volume (a database image with a
VOLUME) keep their data in exactly such volumes: check the list above first.docker volume prune - Check what was freed.
df -h
The data of a running service keeps growing
The space belongs to an application you run: its database, uploads, media, or backups it writes into its own volume. Nothing in Docker should be removed here; the cleanup happens in the application (retention, old backups, a vacuum of the database) or by giving the data a larger disk.
Find which directory inside the volume grows, then use the application's own documented way to clean it. Deleting files from under a running database corrupts it.
What to do
- See which directory inside the volume is large.
sudo du -h --max-depth=2 /var/lib/docker/volumes/<volume>/_data | sort -h | tail -n 10 - If it is backups or exports the application writes, move them off the server and set how many it keeps.
- If it is the database itself, use its documented cleanup (for example retention settings or a vacuum), never
rmon its files. - If the data is simply larger than the disk, plan a larger disk or move Docker's
data-rootto one; this wizard does not cover growing a disk at a provider.
The system journal is too large
The journal deletes old entries only when it reaches its limit, and the default limit is a share of the filesystem, not a fixed size. Vacuuming removes the oldest archived journal files down to the size you give; the active file is kept, so recent entries stay.
Then set a fixed cap, so the journal cannot fill the disk again: SystemMaxUse= in /etc/systemd/journald.conf, applied after journald restarts.
What to do
- Delete the oldest archived journal files until the journal takes at most 500 MB. Older system logs are gone after this.
sudo journalctl --vacuum-size=500M - Cap it: set
SystemMaxUse=500Min the[Journal]section of/etc/systemd/journald.conf. - Restart journald so it reads the new limit. Logging pauses for a moment.
sudo systemctl restart systemd-journald - Confirm the size.
sudo journalctl --disk-usage
Log files in /var/log are not rotated or removed
Most services that log to files rely on logrotate to rename, compress and eventually delete old files. A service without a logrotate rule, or one that ignores it, grows a single file without end; rotated copies (.1, .gz) pile up when the number kept is high.
Find the large files first. Removing old compressed copies frees space without touching what services write now.
What to do
- List the largest files and directories under /var/log.
sudo du -ah /var/log 2>/dev/null | sort -h | tail -n 15 - Delete compressed rotated logs older than 30 days. They are gone for good.
sudo find /var/log -type f -name "*.gz" -mtime +30 -delete - See what logrotate would do, without doing it, to find a file no rule covers.
sudo logrotate --debug /etc/logrotate.conf - Add a rule for that file in
/etc/logrotate.d/(withsize,rotateandcompress), or make the service log to the journal instead. - Check what was freed.
df -h
Deleted files are still held open
Deleting a file removes its name, but the kernel frees its space only when the last process that has it open closes it. A log file deleted by hand while a service still writes to it keeps growing out of sight: df counts it, du cannot find it.
lsof +L1 lists open files that no longer have a name, with the process holding each. Restarting that process, or the container it runs in, releases the space.
What to do
- List deleted files still open, with their size and process (install
lsofif it is missing).sudo lsof -nP +L1 - If the process is a system service, restart it. The service stops for a moment.
sudo systemctl restart <service> - If it runs in a container, restart that container.
docker restart <container> - Next time, empty a log a process still writes to instead of deleting it, or let logrotate handle it.
- Check what was freed.
df -h
The filesystem ran out of inodes
Inodes are freed only by deleting files, so the fix is to find the directory with the most files. On a Docker server it is often overlay2 (many images, or a build cache with many small layers); elsewhere it is a cache, a mail queue or session files of an application.
If the count is in Docker's directory, removing unused images and build cache frees inodes the same way it frees bytes.
What to do
- Count files per directory, three levels deep, on the full filesystem.
sudo du --inodes -x --max-depth=3 / 2>/dev/null | sort -n | tail -n 10 - If it is under /var/lib/docker: remove dangling images (old untagged versions only).
docker image prune - And build cache older than a week (build cache only).
docker builder prune --filter until=168h - If it is elsewhere, clean that directory with the tool of the application that owns it.
- Check the inodes again.
df -i
The space is outside Docker
Docker is not what fills this filesystem: it is a directory of the system or of a user, such as old backups, downloads, a home directory, or old kernels on a small /boot. Pruning Docker objects would delete things without freeing this space.
List the largest directories of that filesystem and remove only what you know is no longer needed; for packages and kernels, use the package manager of your distribution, not rm.
What to do
- List the largest directories of the full filesystem; replace
<mount>with its mount point fromdf -h.sudo du -xh --max-depth=2 <mount> 2>/dev/null | sort -h | tail -n 15 - Move old backups and archives off the server before you delete them here.
- For old kernels and package caches, use your distribution's package manager.
No branch matched: collect what you need to dig further
The answers so far do not point at one place. Collect the full picture before deleting anything: the space and inodes of every filesystem, what Docker counts, where Docker keeps its data, and what the kernel said when writes failed. That is also what a forum or an issue tracker will ask for.
All the commands below only read. docker info prints the server's configuration but no secrets; still look over any output before you share it.
What to do
- Space and inodes of every filesystem.
df -h && df -i - Docker's own count, per image, container and volume.
docker system df -v - Where Docker keeps its data, and its storage and logging drivers.
docker info --format "{{.DockerRootDir}} {{.Driver}} {{.LoggingDriver}}" - The size of Docker's and the system's directories.
sudo du -sh /var/lib/docker /var/lib/containerd /var/log 2>/dev/null - Kernel messages of the last hour (filesystem errors, a read-only remount).
journalctl -k --since "1 hour ago"
What this does not cover
The wizard covers a single Linux host with Docker Engine and its default overlay2 storage. Docker Desktop keeps its data inside a virtual disk, and Kubernetes nodes clean images and logs through the kubelet; both work differently. Growing a disk or a volume at a provider is not covered.
Removing a volume deletes its data for good, and emptying a log deletes its history: read each marked command before you run it, and back up what you are unsure of. When no branch matches, the last step lists what to collect; it does not guess.
Related pages
Sources
- Docker Docs: JSON File logging driver
- Docker Docs: Local file logging driver
- Docker Docs: Configure logging drivers
- Docker Docs: Configure the daemon (daemon.json)
- Compose file reference: logging
- Docker Docs: docker image prune
- Docker Docs: Prune unused Docker objects
- Docker Docs: docker system df
- Docker Docs: docker builder prune
- Docker Docs: Build garbage collection
- Docker Docs: Storage
- Docker Docs: docker container diff
- Docker Docs: docker container ls
- Docker Docs: docker container prune
- Docker Docs: Volumes
- Docker Docs: docker volume rm
- Docker Docs: docker volume prune
- du(1), Linux manual page
- journalctl(1), systemd manual
- journald.conf(5), systemd manual
- logrotate (project and manual)
- unlink(2), Linux manual page
- df(1), Linux manual page
- Docker Docs: docker system info
How ANAROQ sources the facts on its pages is described in the methodology. Read the methodology