docker ps lists what is running, and after a fortnight of experiments it looks reassuringly short. The container that exited is not gone — its writable layer, its logs and its configuration are all still under /var/lib/docker. The only thing that changed is that it stopped appearing in the default listing.
docker ps # running only
docker ps -a # everything ever created, Exited (0) included
docker rm -v 3f2b9c1a4e7d # -v also removes the volumes it created
# the sweep: rm refuses containers that are still running, so this is
# noisier than it is dangerous
docker rm $(docker ps -a -q)
# untagged images left behind by rebuilds that reused a tag
docker images | grep '^<none>'
du -sh /var/lib/docker
Each stopped container keeps its own copy-on-write layer, holding everything written since it started — a container that ran composer install and exited is carrying a vendor directory around. The logs are worse over time: the JSON log file grows without a limit and is only deleted with the container. Anonymous volumes are the part that leaks silently, because docker rm without -v leaves a directory under /var/lib/docker with nothing referring to it and no command to find it again. Getting into the habit of docker run --rm for anything interactive removes most of the problem at source. The other half is images: every rebuild that reuses a tag leaves the previous one behind as <none>, still holding its layers on disk.