Containers are rarely what fills the disk
On this machine docker system df shows 44 MB in containers and 5.7 GB reclaimable in images. If you are cleaning up for disk space, Docker Prune Cleanup covers images, build cache and volumes, and Find Large Files on Linux checks whether Docker is the problem at all.
docker rm $(docker ps -aq) is the answer everyone copies, and it has three problems. It fails when there is nothing to remove, which breaks the script that runs it. It refuses running containers, so half the list survives and the exit code says failure. And it says nothing about the named volumes, images and networks it left behind. The fourth problem is the one that hurts: -a means every container on the host, including the database for the project you were not thinking about.
What Happens When You Run the Usual Command?
Reproduced here on 2026-10-03 (Kali, Docker 28.5.2, busybox 1.37). This machine runs real containers, so every demo container carries a bsdemo=1 label and every command filters on it. Drop the filter and the same commands act on everything. Four containers: two running, two exited, one of them with a named volume.
With nothing matched, the command substitution expands to nothing and docker rm gets no arguments:
With the four demo containers, it removes the stopped two and refuses the running two:
demo-job is gone. Its named volume demo-data, and the data in it, is not.
xargs -r (GNU's --no-run-if-empty) is the empty-list fix: it runs nothing when the input is empty.
Why Does docker stop Take 10 Seconds?
docker stop sends SIGTERM, waits 10 seconds, then sends SIGKILL. sleep running as PID 1 has no SIGTERM handler, so it waits out the full timeout and exits with 137 (128 + 9, killed). Plenty of real images behave the same way. docker rm -f skips the wait and kills at once, fine for throwaway containers and wrong for a database you want flushed. The real fix is an entrypoint that handles SIGTERM, or docker run --init. Exit codes like 137 are decoded in the Bash Exit Code Lookup.
The Script
Save as docker-remove-containers.sh. Stopped containers only by default, a dry run until you pass --yes, filters passed straight to docker ps, and a line at the end saying what it did not remove.
Prerequisites
Docker (any current version) and permission to talk to the daemon: membership of the docker group or sudo. Membership of the docker group is root-equivalent on that host, so grant it deliberately.
How Does Each Part Work?
- The status filters keep running containers out unless you pass
--running. Threestatus=filters are OR'ed by Docker, and any--filter label=…you add is AND'ed with them. --format '{{.ID}}\t{{.Names}}…'gives the script a parseable list and you a readable one: names and status, not bare IDs.- The empty check is what
docker rm $(…)lacks. Nothing to do exits 0, so the script can run from cron. - The dry run exits 1 and prints the list. Removing containers cannot be undone, so the default is to show, not do.
docker rm -valso deletes each container's anonymous volumes. Named volumes are never removed byrm, by design.- The last line counts dangling volumes host-wide, so the "what did I leave behind" question has an answer every time.
What Does the Script Print?
Two fresh stopped containers and the two running ones from before, all labelled:
The 4 volumes are host-wide: demo-data plus three this machine already had. That count is the point. Nothing in docker rm's own output would have told you.
What Should I Check Before Deleting?
docker system df shows what each kind of object costs and what is reclaimable. On this machine, the same day:
Containers are 44 MB; unused images are 5.7 GB. Removing every container here would free almost nothing and destroy state. If the goal is disk, docker image prune -a and Docker Prune Cleanup are the right tools. If containers are managed by Compose, docker compose down (with -v only if you mean to delete the project's volumes) removes them with their network, and is what the next docker compose up expects.
Dry run by default and an exit code cron can read are the habits every cleanup script should start with; The Production Bash Toolkit ships a ShellCheck-clean template built around them, and the loop over containers is the bash for loop pattern for lists you must not split.
Frequently Asked Questions
How do I remove all Docker containers at once?
Run docker rm -f $(docker ps -aq). docker ps -aq lists the ID of every container, running or stopped, and docker rm -f removes each one, killing running containers first. If nothing exists the command errors with requires at least 1 argument, so in a script use docker ps -aq | xargs -r docker rm -f, which does nothing on an empty list. Check docker ps -a first: this includes every project on the host.
What is the difference between docker rm and docker container prune?
docker rm removes the containers you name, running ones too with -f. docker container prune removes every stopped container and nothing else, asks for confirmation unless you pass -f, and accepts --filter label= or until= to narrow it. Use prune for routine cleanup, because it can never take down a running service; use docker rm when you mean specific containers.
Does removing a container delete its volume?
Not a named volume. docker rm deletes the container's writable layer, and with -v also its anonymous volumes, but a volume created with -v name:/path or by Compose survives and keeps its data. List leftovers with docker volume ls --filter dangling=true and remove them with docker volume prune (anonymous only by default since Docker 23) or docker volume rm NAME. Back up anything you need first: a removed volume does not come back.
Why does docker stop take 10 seconds?
docker stop sends SIGTERM and waits 10 seconds for the main process to exit before sending SIGKILL. A process that runs as PID 1 and has no SIGTERM handler, like a plain sleep or many shell entrypoints, ignores the signal, so every stop takes the full timeout and the container exits with code 137. docker stop -t 2 shortens the wait, docker rm -f kills immediately, and the real fix is an entrypoint that handles SIGTERM or docker run --init.
How do I remove Docker containers but keep some?
Filter instead of listing everything. docker ps -aq --filter status=exited selects stopped containers, --filter label=env=dev selects containers you labelled, and --filter name=test- matches names. Combine several: filters with the same key are OR'ed, different keys are AND'ed. Preview with docker ps -a and the same filters, then pipe the -q version to xargs -r docker rm.
Part of the bash snippets collection
Related Scripts
- Docker Prune Cleanup — images, build cache and volumes, where the disk space actually is
- Find Large Files on Linux — check whether Docker is what filled the disk
- lsof Command Examples — when df and du disagree after a cleanup
- Bash For Loop Examples — looping over lists of IDs and names safely