Skip to content

Docker Resource Diagnostics and Cleanup

Use this workflow when Docker builds, worktrees, agents, dev stacks, or services have been running for a while and the machine needs a resource check. It is useful on small EC2 or VPS servers with limited disk and RAM, and also on local Linux development machines where Docker cache and unused containers can grow over time.

  • Before moving a service such as MySQL from managed hosting to local Docker.
  • After many Docker builds, worktrees, agents, or dev stacks.
  • When an EC2 or VPS machine feels slow or has a small root disk.
  • When local Docker development starts consuming unexpected disk or CPU.
  • When htop and Docker show high CPU but a whole-system monitor shows a lower total percentage.
Terminal window
# memory and disk
free -h
df -h
# live Docker usage
docker stats --no-stream
docker system df
# find large root, var, Docker, and containerd paths
sudo du -x -h --max-depth=1 / | sort -h
sudo du -h --max-depth=1 /var | sort -h
sudo du -h --max-depth=1 /var/lib | sort -h
sudo du -h --max-depth=1 /var/lib/docker | sort -h
sudo du -h --max-depth=1 /var/lib/containerd | sort -h
# conservative Docker cleanup
docker builder prune
docker container prune
docker image prune
docker system prune
# re-check
df -h
docker system df

For memory, the important value is available. Linux uses unused memory for buff/cache, which is usually normal. On a small server, available memory comfortably above 1 GB is a good sign.

Terminal window
free -h

Check mounted filesystems next. On small EC2 or VPS disks, disk can become the bottleneck before RAM. Keeping the root filesystem below about 70 to 80 percent leaves room for logs, Docker layers, package cache, and temporary files.

Terminal window
df -h

docker stats shows live CPU and memory usage per container. Docker CPU percentage is per logical CPU core, not always the percentage of the whole machine.

Docker CPU Meaning
50% Half of one logical CPU core
100% One full logical CPU core
200% Two full logical CPU cores
100% on 16 threads About 1/16 of total CPU capacity
Terminal window
docker stats --no-stream

docker system df summarizes images, containers, volumes, and build cache. SIZE is current usage. RECLAIMABLE is what Docker thinks can be removed. Build cache is often safe to clean. Volumes can contain databases and should be treated carefully.

Terminal window
docker system df

Start from the root filesystem, then inspect /var and /var/lib. Use -x when scanning / so du stays on the same filesystem and avoids mounted pseudo-filesystems. Some /proc or /run warnings are normal if / is scanned without -x.

Terminal window
sudo du -x -h --max-depth=1 / | sort -h
sudo du -h --max-depth=1 /var | sort -h
sudo du -h --max-depth=1 /var/lib | sort -h
  • /var/lib/docker contains Docker images, containers, overlay layers, volumes, and build-related data.
  • /var/lib/containerd can also grow, especially from downloaded image blobs and unpacked filesystem layers.
  • /var/log, /var/cache, /var/lib/mysql, /var/lib/postgresql, and /var/lib/snapd can also be large depending on the machine.
Terminal window
sudo du -h --max-depth=1 /var/lib/docker | sort -h
sudo du -h --max-depth=1 /var/lib/containerd | sort -h
containerd path What it usually holds
io.containerd.content.v1.content Downloaded image blobs and layers
io.containerd.snapshotter.v1.overlayfs Unpacked container and image filesystem layers

Start with the safest cleanup. Build cache is usually the first thing to remove because it does not delete running containers, active images, Docker volumes, databases, env files, or source code. The tradeoff is that the next Docker build may be slower.

Terminal window
docker builder prune
# non-interactive
docker builder prune -f
Command What it removes
docker container prune Stopped containers only
docker image prune Dangling images only
docker system prune Stopped containers, unused networks, dangling images, and build cache
docker image prune -a All unused images, not images used by running containers
Terminal window
docker container prune
docker image prune
docker system prune
# more aggressive
docker image prune -a

Avoid volume cleanup by default. Unused Docker volumes may contain old local databases, Qdrant data, Redis data, or experiment state.

Terminal window
# Do not run this unless the volumes are definitely disposable.
# docker system prune -a --volumes
Terminal window
df -h
docker system df
sudo du -h --max-depth=1 /var/lib/containerd | sort -h
sudo du -h --max-depth=1 /var/lib/docker | sort -h

htop process CPU is usually per logical CPU core. Whole-system monitors such as Mission Center usually show total CPU across all CPU threads. Both views can be correct at the same time.

Process CPU Meaning
70% 0.7 of one logical CPU
100% One full logical CPU
200% Two full logical CPUs
Two processes at 70% on 16 threads About 1.4 logical CPUs, or about 8.75 percent of total CPU capacity
Terminal window
# Ubuntu/Debian
sudo apt install htop
# Arch
sudo pacman -S htop
htop

When htop shows a busy process, inspect the PID and check whether it belongs to a container. Replace PID_HERE with the process ID from htop.

Terminal window
ps -fp PID_HERE
cat /proc/PID_HERE/cgroup
Terminal window
for c in $(docker ps --format '{{.Names}}')
do
echo "=== $c ==="
docker top "$c" | grep -E 'PID_HERE' || true
done

Then check the suspected container logs.

Terminal window
docker logs --tail=100 CONTAINER_NAME
docker logs --tail=100 project-wt1-backend
docker logs --tail=100 project-wt2-backend
docker logs --tail=100 automation-service

uvicorn –reload can cause steady CPU usage because it watches files. Inside Docker this can be heavier if the watched directory includes mounted source code, .git, .venv, node_modules, generated files, cache folders, or large worktrees.

  • Use –reload only for the backend you are actively editing.
  • Run other worktree or backend containers without reload.
  • Never use –reload in production.
Terminal window
docker inspect CONTAINER_NAME --format '{{.Config.Cmd}}'
Terminal window
# production-style FastAPI command
gunicorn -k uvicorn.workers.UvicornWorker -w 2 -b 0.0.0.0:8000 main:app
# plain non-reload Uvicorn
uvicorn main:app --host 0.0.0.0 --port 8000
# dev reload with common exclusions
uvicorn main:app \
--host 0.0.0.0 \
--port 8000 \
--reload \
--reload-exclude .git \
--reload-exclude .venv \
--reload-exclude node_modules \
--reload-exclude __pycache__ \
--reload-exclude .pytest_cache

Stop containers that are not actively needed. For local development, this is often better than trying to optimize every idle worktree service.

Terminal window
docker stop CONTAINER_NAME
docker stop project-wt1-backend project-wt1-frontend
docker stop project-wt2-backend project-wt2-frontend

Or stop a Compose stack from the relevant project directory.

Terminal window
docker compose down

If htop shows a long-running node process, a workflow service or other Node-based container may be the active CPU user. A process at 100 percent means about one logical CPU core. On a 16-thread machine that is about 6.25 percent of total CPU capacity.

Terminal window
docker stats --no-stream | grep -i service-name
docker logs --tail=100 automation-service
Terminal window
# if not needed
docker stop automation-service
# if it should be running but seems busy
docker restart automation-service
docker logs --tail=100 automation-service