Docker Filling Up Your VPS Disk? Here's the Real Fix
Your VPS says the disk is full, df -h shows the root partition pinned at 100%, and you haven't touched a config file in weeks. If you're running Docker on that box, there's a good chance the containers themselves are the reason — not your app, not MySQL, not your logs. Docker is quietly one of the worst offenders for eating disk space on a VPS, and it does it in ways that don't show up if you're only checking the usual suspects.
Symptom: Disk Full, But You Can't Find the Files
This one trips people up because the usual disk-full checklist — /var/log, MySQL binlogs, old backups — comes back clean, and the disk is still full. A few tells that Docker is the culprit:
df -hshows/or/varnear 100%, butdu -h /var/log /homedoesn't add up to anywhere close to that number- Containers keep restarting or crashing with no application-level error
docker: no space left on devicewhen you try to pull an image or build one- The box has been running the same handful of containers for months with regular redeploys (CI/CD pipelines are especially bad about this)
Run this first to confirm Docker is actually the problem before you go further:
docker system df -v
That gives you a breakdown of images, containers, local volumes, and build cache — with sizes. If that output is in the tens of gigabytes and your app code is a few hundred megabytes, you've found it.
Cause: Where Docker Actually Hides the Bytes
Docker doesn't put everything in one obvious place, which is why manual du scans miss it. Here's what's usually responsible, in order of how often we see it:
1. Container logs with no size limit
By default, Docker's json-file logging driver does not rotate or cap log size. A chatty app container (verbose access logs, a crash-looping process printing stack traces every few seconds) can silently grow a single log file to tens of gigabytes. Check the worst offenders:
docker inspect --format='{{.LogPath}}' $(docker ps -q) | xargs du -h 2>/dev/null | sort -rh | head -10
2. Dangling and unused images
Every docker build or CI redeploy that doesn't clean up after itself leaves behind old, untagged image layers. Over months of deploys these pile up fast, especially with large base images (Node, Python with ML libraries, anything with a big node_modules or site-packages baked in).
3. The build cache
Multi-stage builds and BuildKit cache layers aggressively — that's the point, it speeds up rebuilds — but on a VPS with limited disk, that cache can quietly become the single largest consumer on the machine.
4. Orphaned volumes
docker-compose down without -v leaves named and anonymous volumes behind even after the containers are gone. Database containers (Postgres, MySQL-in-Docker, Redis with persistence) are the usual culprits since their volumes grow continuously.
5. The overlay2 storage driver's own overhead
Each image layer under /var/lib/docker/overlay2 is stored separately, so a stack of similar images (say, five versions of the same app image) doesn't dedupe as cleanly as you'd expect, especially across different base image versions.
Fix: Clean It Up Without Breaking Anything Running
Step 1 — See what you'd actually reclaim
Before deleting anything, dry-run it:
docker system df -v
docker image ls -a
docker volume ls
Step 2 — Remove unused images, containers, networks and build cache
This is safe on a running host — it only removes stopped containers and images/cache not referenced by anything currently running:
docker system prune -a -f
Important: the -a flag also removes images that aren't attached to any container, even if you were planning to reuse them for a redeploy later. If you have a CI pipeline that pulls a base image once and reuses the cache, skip -a and just run docker system prune -f instead.
Step 3 — Clear volumes separately, and carefully
Volumes hold data, so don't run this blindly on a production box. List orphaned ones first:
docker volume ls -f dangling=true
Inspect anything that looks like it belongs to a database before removing it. Once you're sure it's safe:
docker volume prune -f
Step 4 — Truncate runaway container logs immediately
If a specific container's log file is the emergency, you can truncate it live without restarting the container — the process still has the file handle open, so this is safe:
truncate -s 0 $(docker inspect --format='{{.LogPath}}' )
Prevention: Stop It From Filling Up Again
Cap log size at the daemon level
This is the single biggest fix. Set a default log rotation policy in /etc/docker/daemon.json so no container can grow an unbounded log file again:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
Restart Docker for it to take effect:
systemctl restart docker
Note this only applies to new containers created after the change — existing ones keep their original logging config until recreated.
Schedule a weekly prune
A simple cron entry keeps things from creeping back up between manual checks:
0 3 * * 0 docker system prune -af --filter "until=168h" >> /var/log/docker-prune.log 2>&1
The until=168h filter only removes images and cache older than a week, so you don't nuke something your CI pipeline pulled yesterday and still needs.
Always tear down with volumes when you mean it
If a compose stack is genuinely being retired, use docker-compose down -v instead of a bare down so orphaned volumes don't get left behind.
Watch disk usage, not just uptime
Set up basic disk-usage monitoring — even a cron job that emails you past 80% usage — so this shows up as a warning instead of a 3am outage. On a Getwebup VPS you can also keep an eye on usage from the control panel dashboard rather than relying on SSH checks alone.
Quick Reference
| Command | What it does | Safe on a live production box? |
|---|---|---|
docker system df -v | Shows what's using space, broken down by type | Yes — read-only |
docker system prune -f | Removes stopped containers, dangling images, unused networks and cache | Yes |
docker system prune -af | Same as above, plus all unused (not just dangling) images | Caution — removes images not tied to a running container |
docker volume prune -f | Removes volumes not attached to any container | Caution — check for database volumes first |
docker builder prune -af | Clears the BuildKit build cache | Yes, just slows the next build |
None of this replaces keeping an eye on actual disk headroom — if you're consistently running Docker workloads close to the edge of your current plan, it's usually cheaper to resize the VPS than to babysit prune jobs every week.
Frequently asked questions
Is it safe to run docker system prune -a on a production server?
It's safe in the sense that it won't touch anything currently running — stopped containers, dangling images, and unused build cache only. The risk is removing images you were planning to reuse for a fast redeploy. If your CI/CD relies on cached base images, use plain docker system prune (without -a) or add an age filter like --filter "until=168h" so recent images survive.
Why doesn't df -h match what du shows when Docker is involved?
Docker stores data under /var/lib/docker using the overlay2 driver, and deleted-but-still-open log files (a container writing to a log file that got rotated but the process still holds the old file handle) can hold disk space that du won't show as belonging to any visible file. docker system df -v is the accurate way to see Docker's real footprint.
Will limiting log size in daemon.json affect containers that are already running?
No. The max-size and max-file settings in /etc/docker/daemon.json only apply to containers created after you restart the Docker daemon. Existing containers keep whatever logging config they were started with until you recreate them (docker-compose up -d --force-recreate works for this).
I ran docker volume prune and now a container won't start. What happened?
You likely removed a volume that was mounted by a stopped container Docker didn't recognize as 'in use' at prune time — dangling volume detection only tracks currently running or created containers, not ones referenced in a compose file that hasn't been started yet. If you have a backup or the volume was for a database, restore from your last backup; otherwise check docker-compose.yml for the expected volume name and recreate it before restarting the stack.
How much disk should I reserve for Docker on a VPS?
As a rough floor, keep at least 20-30% of your total disk free at all times for image pulls, build layers, and log growth between prune cycles. If you're running more than a handful of containers or doing frequent CI builds on the same box, budget more — or move build/CI to a separate runner and keep the VPS for running the final images only.