cheat-sheets

Docker Cleanup

How to see what Docker is using and remove it safely: containers, images, networks, volumes and build cache, plus log rotation so the disk does not fill again. The order matters. Volumes hold data, so they get their own warnings.

Documentation: docker system prune · Prune unused objects · JSON File logging driver

Verification: the container prune --filter until=, builder prune (--keep-storage, --filter until=, -a) options and the daemon.json log options were checked against the Docker docs. The rest is from the CLI reference. No command was executed. Review what a prune would delete before you run it, and never run --volumes on a host whose data lives in anonymous volumes you still need.

See also: Docker · Docker Troubleshooting · Docker Registry · Docker-Compose · Storage and Filesystems


See What Takes Space #

COMMANDDESCRIPTION
docker system dfSummary: images, containers, volumes, build cache, with “reclaimable” amounts
docker system df -vPer-image, container and volume sizes
docker ps -asContainers with the size of their writable layer
docker images --format 'table {{.Repository}}:{{.Tag}}\t{{.Size}}' | sort -k2 -hImages by size
docker volume ls -f dangling=trueVolumes no container uses
docker builder duBuild cache usage
sudo du -xh --max-depth=1 /var/lib/docker | sort -hRaw directories (overlay2, volumes, containers)
sudo du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tailBiggest container logs

Safe Order #

Work from least to most risky. Run the matching ls first.

STEPCOMMANDREMOVES
1docker container pruneStopped containers
2docker image pruneDangling images (untagged <none>)
3docker network pruneNetworks with no containers
4docker builder pruneDangling build cache
5docker image prune -aEvery image not used by a container (re-pull needed later)
6docker volume pruneData. Volumes not used by any container (see below)

docker system prune runs steps 1 to 4 together (containers, networks, dangling images, dangling cache). It asks for confirmation; -f skips that.


Prune With Filters #

COMMANDDESCRIPTION
docker container prune --filter "until=24h"Stopped containers older than a day
docker image prune -a --filter "until=720h"Unused images created more than 30 days ago
docker image prune -a --filter "label!=keep"Skip images labelled keep
docker system prune --filter "until=168h"System prune of objects older than a week
docker builder prune --filter "until=72h"Build cache not used for 3 days
docker builder prune --keep-storage 5GBTrim the cache to 5 GB
docker builder prune -aAll build cache, including used
docker buildx prune --filter "until=72h"Same for buildx builders
docker network prune --filter "until=24h"Old unused networks

until takes a duration (24h), a timestamp or a Unix time. Add -f to skip the prompt in scripts.


Containers #

COMMANDDESCRIPTION
docker ps -a --filter status=exitedList stopped containers
docker rm CONTAINERRemove one (-f for a running one, -v also removes its anonymous volumes)
docker rm $(docker ps -aq --filter status=exited)Remove all exited
docker run --rm ...Never leave a throwaway container behind

Images #

COMMANDDESCRIPTION
docker images -f dangling=trueUntagged leftovers from rebuilds
docker image pruneRemove those
docker rmi IMAGE:TAGRemove one
docker images --filter "reference=REPO/*"Everything from one repo
docker rmi $(docker images -q REPO/NAME | tail -n +4)Keep the 3 newest of one repository (check the list first)
docker image ls --digestsShow digests, to spot duplicates

An image used by a stopped container is not removed by image prune -a until that container is removed.

Volumes (Careful) #

Volumes hold databases and app data. Deleting them cannot be undone.

COMMANDDESCRIPTION
docker volume lsAll volumes
docker volume ls -f dangling=trueVolumes with no container attached
docker volume inspect VOLUMEMountpoint, labels, driver
docker ps -a --filter volume=VOLUMEWhich containers use a volume (including stopped)
docker volume rm VOLUMERemove one
docker volume pruneRemove anonymous unused volumes only (since Docker 23)
docker volume prune -aRemove all unused volumes, named ones too
docker volume prune --filter "label!=keep"Keep labelled volumes
docker system prune --volumesSystem prune plus anonymous unused volumes
docker compose down -vRemoves the project’s volumes too

Rules of thumb:

  • A volume counts as “unused” if no container (running or stopped) references it. Stopped containers protect their volumes. If you removed the stack with compose down, its volumes become prunable.
  • Back up first: docker run --rm -v VOLUME:/data -v "$PWD":/backup busybox tar czf /backup/VOLUME.tgz -C /data . (see Docker).
  • Name important volumes and label them (labels: keep: "true" in Compose) so filters can protect them.

Build Cache #

COMMANDDESCRIPTION
docker builder duShow cache entries
docker builder pruneRemove dangling cache
docker builder prune --filter until=24hOnly older entries
docker builder prune --keep-storage 10GBKeep a 10 GB budget
docker buildx du / docker buildx pruneSame for the buildx builder you use

In CI, run docker builder prune --filter until=168h -f weekly instead of wiping the cache every run.


Log Rotation #

The default json-file driver keeps logs forever. One chatty container can fill the disk.

Global default, in /etc/docker/daemon.json (merge with existing keys, then sudo systemctl restart docker). It only applies to new containers, so recreate existing ones:

json
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

local stores compact, compressed logs (defaults 20 MB x 5). json-file with the same log-opts also works, and keeps docker logs plus plain JSON on disk.

Per container or per Compose service:

bash
docker run -d --log-opt max-size=10m --log-opt max-file=3 IMAGE
yaml
services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
COMMANDDESCRIPTION
docker inspect -f '{{.HostConfig.LogConfig}}' CONTAINERLog config in use
docker inspect -f '{{.LogPath}}' CONTAINERLog file path
sudo truncate -s 0 $(docker inspect -f '{{.LogPath}}' CONTAINER)Empty a runaway log now without restarting
sudo journalctl --vacuum-size=500MIf you use the journald driver, trim the journal (systemd)

Do not rm the log file of a running container: the daemon keeps it open and the space stays used. Truncate it instead.


Automate It #

A weekly systemd timer (systemd) running a conservative prune:

/etc/systemd/system/docker-prune.service:

ini
[Unit]
Description=Prune unused Docker objects

[Service]
Type=oneshot
ExecStart=/usr/bin/docker system prune -f --filter "until=168h"
ExecStart=/usr/bin/docker builder prune -f --filter "until=168h"

/etc/systemd/system/docker-prune.timer:

ini
[Unit]
Description=Weekly Docker prune

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=timers.target
bash
sudo systemctl daemon-reload
sudo systemctl enable --now docker-prune.timer

This leaves volumes and in-use images alone. Pair it with image update tools such as Watchtower or Renovate, which leave old image versions behind.

Moving Docker’s Data Directory #

If /var/lib/docker sits on a small disk, move it:

bash
sudo systemctl stop docker docker.socket
sudo rsync -aHAX /var/lib/docker/ /mnt/bigdisk/docker/
# in /etc/docker/daemon.json: {"data-root": "/mnt/bigdisk/docker"}
sudo systemctl start docker
docker info | grep 'Docker Root Dir'

Keep the old directory until you have checked that containers and volumes work, then delete it.

Search all sheets

Tip: / or Ctrl/Cmd+K to open, Esc to close.