Skip to main content
DevOps from zero · Lesson 15 of 15 · 12 min

Docker No Space Left on Device: Find What Fills the Disk

Fix Docker's no space left on device: read docker system df, prune images and build cache by age, keep the newest N images, cap logs and the journal.

By Matthews WongReviewed on

Key takeaway

No space left on device on a Docker host means something grows without a limit: tagged images from every deploy, build cache, container logs, the systemd journal, or volumes. Measure first with df, docker system df and journalctl --disk-usage. Then prune by age or count, cap the logs and the journal, and leave volumes alone.

You will be able to

  • Find which of images, build cache, logs, the journal or volumes is using the disk, before deleting anything.
  • Choose the right prune command, and say which ones destroy data.
  • Cap what grows by itself: keep the newest N images, rotate container logs and vacuum the journal.
Watch it work

Step through the diagram

Press Play, or step with the arrows. Every step is written out below the diagram.

What fills a Docker host's disk, and what reclaims it

Five things fill the disk: images, build cache, container logs, the journal and volumes. Each has its own command to measure it or to cap it, and volumes are the one nothing should prune automatically.

Step 1 of 7. Left, the five things that grow on a Docker host. Right, the command that measures or caps each one. Measure first, then pick the smallest cleanup that works.

Keys: ← → step · Home / End · K play or pause

  1. Left, the five things that grow on a Docker host. Right, the command that measures or caps each one. Measure first, then pick the smallest cleanup that works.

  2. docker system df summarises space used by images, containers and local volumes. Add -v for each item. Logs and the journal are measured elsewhere, with du and journalctl --disk-usage.

    docker system df -v
  3. docker image prune -a removes images no container uses. With until=72h it only takes those older than 72 hours. docker builder prune accepts the same until filter for build cache.

    docker image prune -af --filter "until=72h"Predict the next step A busy day brings 19 deploys in 40 hours. What does the 72-hour prune remove? (Every old release · Nothing: every image is under 72 hours old · Only dangling images)
  4. So the deploy also caps by count: list the app's images newest first, skip the first two, remove the rest. docker image rm refuses an image a container uses.

    docker image ls | sort -r | tail -n +3 | xargs -r docker image rm
  5. The json-file driver defaults to unlimited max-size and one file, and max-file only works with max-size. The compose file sets 10m and 3, so each log rotates.

    max-size: "10m" max-file: "3"
  6. journalctl --vacuum-size deletes the oldest archived journal files until they fit the limit. Active files stay, so --disk-usage can still read higher than the target.

    journalctl --disk-usage sudo journalctl --vacuum-size=200MPredict the next step Does docker system prune with no flags remove volumes? (Yes, every unused volume · No, volumes are not pruned by default · Only named volumes)
  7. Docker never removes volumes on its own, because that could destroy data. docker volume prune takes unused anonymous volumes; named ones need --all. The deploy never runs it.

What fills the disk of a Docker host?

Five things grow on a Docker host, and only some of them grow quietly. Images pile up because every deploy pulls a new tag, and a tagged image is not dangling: Docker defines a dangling image as one that is untagged and not referenced by any container. Build cache grows with every build. Container logs have no limit until you set one. The systemd journal and volumes hold the rest.

Read more (1)

The error that tells you is no space left on device, usually in the middle of an image pull or a build, not at a quiet moment. That timing is why this site prints df before and after its cleanup steps: the log then shows how much headroom each step actually bought.

How do you find what is using the space?

Start with df -h on the filesystem that is full, so you know how bad it is. Then docker system df, which Docker describes as the space used by the daemon, in rows for images, containers and local volumes. Add -v and each image, container and volume gets its own line, with the unique size an image alone uses and the size it shares with others.

Read more (1)

Docker's own numbers do not cover the journal. For that, journalctl --disk-usage reports the total of all archived and active journal files. Read both before deleting anything: the biggest line is usually obvious, and it tells you which section below to use.

What does each prune command remove?

The prune commands differ in scope, and the table below sets them side by side. Two flags change the answer: -a widens image prune from dangling images to every image no container uses, and --filter until=24h style filters limit any prune to items older than the age you give. Each command asks for confirmation unless you pass -f, so reading the prompt is your last chance.

How do you keep only the last N images?

An age filter assumes a steady pace of deploys. This site's deploy had one, until 19 deploys landed in 40 hours: every image was younger than 72 hours, the prune removed nothing, and the disk reached 93%. The fix is a count cap beside the age filter: list the app's images newest first, skip the first two, and remove the rest with docker image rm.

Read more (1)

Two properties make that safe. docker image rm refuses to remove the image of a running container unless you force it, so the live release survives. And removing an image from the host does not remove it from the registry, so an older release can be pulled again if you need it.

How do you cap logs and the journal?

For container logs, the fix is a setting, not a cleanup. The json-file driver's max-size defaults to -1, unlimited, and max-file defaults to 1 and only takes effect when max-size is set. This site's compose file sets 10m and 3, so a container keeps at most three log files of up to 10 MB each.

Read more (1)

The journal has two tools. journalctl --vacuum-size, --vacuum-time and --vacuum-files clean it once: they delete archived journal files by total size, by age, or down to a number of files. SystemMaxUse= in journald.conf caps it for good, and by default journald uses up to 10% of the filesystem, capped at 4G. Use the vacuum to recover now and the setting to stop it coming back.

How this site does it

The real config behind this lesson

Excerpts from the repository that builds and deploys this website, quoted as they are.

.github/workflows/deploy.yml:262-275
            # The 72h window assumes a few deploys a day. On 2026-10-13/14 there
            # were 19 in 40 hours, every image was younger than 72h, so the
            # prune above removed nothing and the disk reached 93%. So the
            # portfolio's own images are also capped by count: the newest two
            # are the running release and the one a rollback needs. Older
            # releases stay in GHCR. `docker image rm` refuses an image a
            # container still uses, so a rolled-back release is never removed.
            KEEP_PORTFOLIO_IMAGES=2
            PORTFOLIO_REPO="${{ needs.build-and-push.outputs.image }}"
            PORTFOLIO_REPO="${PORTFOLIO_REPO%:*}"
            docker image ls "$PORTFOLIO_REPO" --format '{{.CreatedAt}}\t{{.Repository}}:{{.Tag}}' \
              | sort -r | tail -n +$((KEEP_PORTFOLIO_IMAGES + 1)) | cut -f2 \
              | xargs -r docker image rm || true
            df -h / | tail -1

The count cap that sits after the age filter. docker image ls prints newest-first once sorted in reverse, tail -n +3 skips the two to keep, and xargs -r docker image rm removes the rest. The trailing || true means a refused removal never fails the deploy, and the last line prints the disk so the log shows what it bought.

docker-compose.yml:43-47
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

The cap on container logs. Without max-size the json-file driver never rotates, because its default is unlimited. With it, Docker rolls the file at 10 MB and keeps three, so a noisy container can no longer be the thing that fills the disk.

What breaks

Mistakes that cost real time

  • Destructive: volume prune and system prune --volumes delete data

    docker volume prune removes unused anonymous volumes, docker volume prune --all also removes unused named ones, and docker system prune --volumes adds the anonymous ones to a system prune. Once nothing references a volume, it is fair game. Docker never removes volumes by itself because of this, so do not put them in a cleanup script.

  • Destructive: prune -a with no filter removes your rollback image

    docker image prune -a removes every image no container uses, tagged or not, and docker system prune -a does the same plus the rest. The previous release is not in use, so it goes too, and a rollback has to pull it again. Add an until filter or a count cap, as the deploy does.

  • A time filter alone fails on a busy day

    until=72h removes only images older than 72 hours. When many deploys land inside that window, nothing qualifies and the prune frees nothing while the log still reads as success. Cap by count as well, and print df after the cleanup so a no-op shows up.

  • Logs with no max-size, or max-file on its own

    A container without max-size keeps one log file that grows until the disk is full, and no prune command touches it. Setting only max-file does nothing, because it is only effective when max-size is also set. Set both, and remember the journal is capped separately.

  • The vacuum ran, but the journal is still over the limit

    --vacuum-size only deletes archived journal files, while --disk-usage counts archived and active ones. The active file can keep the total above your target. That is expected, not a failed command; a SystemMaxUse= limit is what keeps the journal bounded over time.

What each Docker cleanup command removes. Defaults, as Docker documents them.
docker image pruneDangling images: untagged and unusedNo, images can be pulled again
docker image prune -aEvery image no container uses, tagged tooNo, but loses local rollback images
docker container pruneAll stopped containersYes, stopped containers are deleted
docker builder pruneDangling build cache; -a removes all unused cacheNo, only slower next build
docker system pruneStopped containers, unused networks, dangling images, unused build cacheYes, stopped containers go
docker volume pruneUnused anonymous volumes; --all adds named onesYes, this is stored data
Try it yourself

Do it on your own machine

Use a practice machine, not a production host. You need Docker and a Linux box with systemd. Look first, delete second.

  1. Check how full the disk is and what Docker thinks it uses.

    df -h /
    docker system df
  2. Get the per-item list. Note which images show 0 containers: those are the ones a prune can reach.

    docker system df -v
  3. Check the journal, which docker system df does not show.

    journalctl --disk-usage
  4. Prune images older than 72 hours. Read the confirmation prompt before you answer, and do not add --volumes anywhere.

    docker image prune -a --filter "until=72h"
  5. Vacuum the journal to a size, then run the usage command again and compare.

    sudo journalctl --vacuum-size=200M
    journalctl --disk-usage
Quick quiz

Three questions before you move on

Pick an answer to see why it is right or wrong. Nothing is saved or graded, so guess freely.

  1. 1.A deploy runs docker image prune -af --filter until=72h, yet the disk fills after 19 deploys in 40 hours. Why did the prune free nothing?
  2. 2.You run journalctl --vacuum-size=200M, but --disk-usage still shows more than 200M. What explains it?
  3. 3.Which command removes unused named volumes, and so can destroy data?
Think it through

Answer in your head, then check

  1. 1.Why is a tagged image from last week not removed by a plain docker image prune?Show the answer

    Answer:A plain image prune removes only dangling images, meaning untagged and unreferenced. Every deploy pulls a new tag, so old releases are tagged and survive until you add -a or remove them by count.

  2. 2.What two limits does the compose file set on a container's log, and what happens without the first?Show the answer

    Answer:max-size 10m and max-file 3. Without max-size the json-file driver is unlimited, and max-file is ignored, so the log grows until the disk is full.

  3. 3.Why is it safe for the cleanup to run docker image rm on the older releases?Show the answer

    Answer:The command refuses an image a running container uses, and removing it from the host leaves the registry copy alone. The newest two images are kept, and any other release can be pulled again.

Go deeper

Related deep dives on this site