Clean Up Stopped Docker Containers with docker container prune

docker container prune deletes every stopped container that matches your filter, and there is no undo. You will finish with a checked list of stopped containers and a prune command that removes only the ones inside a boundary you understand. The installed command is Docker 29.8.1 from docker-ce-cli package version 5:29.8.1-1~ubuntu.24.04~noble. Allow about ten minutes, longer if the host contains containers owned by several projects.

This command deletes container records and their writable container layers. It does not remove running containers, images, networks or named volumes, but deletion is still destructive. A deleted container cannot be started again. Recreating it normally means rerunning the original Compose or deployment configuration, so find that configuration before you prune.

1. Check access and list every container

First confirm the client and daemon you are about to use. This is an ordinary read-only check:

$ docker --version
Docker version 29.8.1, build 4a63305
$ docker ps -a --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
CONTAINER ID   NAMES       STATUS
...            old-worker  Exited (0) 3 days ago

The -a option matters because plain docker ps shows running containers by default. Record the IDs or names of stopped containers that you recognise. Check the status column carefully: a container shown as Up is not a prune candidate.

Checkpoint: do not continue until you can explain why each stopped container is disposable, or why your filter below excludes it. If Docker reports a permission error for its daemon socket, rerun the read-only command with the privilege your host normally requires:

$ sudo docker ps -a --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'

Use sudo only when access requires it. Docker group membership also grants broad control over the host, so do not add yourself to that group as a quick fix without checking your system's access policy.

2. Inspect the candidates before deletion

For a small list, inspect a candidate by ID. This does not change it:

$ docker inspect CONTAINER_ID

Replace CONTAINER_ID with an ID from your own listing. Look for the image, command, labels and mounts that identify the project. If the container belongs to a Compose application, locate its Compose file and confirm that the service can be recreated before removing the old record.

There is no separate dry-run mode in docker container prune. The docker ps -a listing is your review step; it is not an exact preview of a filtered prune when container state changes between commands. If another operator or automation is creating containers, pause that work or accept that the review is only a point-in-time check.

3. Choose a narrow filter

With no filter, prune considers every stopped container. The safer first boundary is usually age. The until filter keeps recently stopped containers and selects containers created before the supplied time:

$ docker container prune --filter 'until=168h'

The command will still ask for confirmation. The duration is interpreted relative to the Docker daemon's clock, so a remote daemon or a host with an incorrect clock can produce a surprising boundary. Use a date or timestamp only when you have checked the daemon's time and timezone.

Labels are useful when a project marks its disposable containers. For example, this selects stopped containers carrying the exact label key cleanup=temporary:

$ docker container prune --filter 'label=cleanup=temporary'

Docker also supports a label exclusion form, such as label!=keep, and multiple filters. Different filter keys are combined with AND logic; repeated values for the same key are combined with OR logic. Treat a label filter as a policy boundary only if you know that every relevant container was labelled consistently.

Checkpoint: write down the exact filter you chose. If you cannot state the age or label rule in one sentence, stop and inspect the containers again.

4. Run the prune and read its result

Run the command without --force first. It will warn that stopped containers will be removed and ask you to enter y to continue:

$ docker container prune --filter 'until=168h'
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
CONTAINER_ID
Total reclaimed space: 212B

The displayed IDs and reclaimed space depend on the daemon. An empty deletion list with a zero reclaimed-space result means that no stopped container matched the selected boundary, not that the command failed.

Destructive action: Answer y only after checking the warning and filter. The short option -f and long option --force suppress the prompt:

$ docker container prune --force --filter 'label=cleanup=temporary'

Use --force for reviewed automation, not as a way to skip the review. Put the filter in the script and log the command so a later operator can see the intended boundary.

5. Verify what remains

List all containers again and compare the result with your recorded candidates:

$ docker ps -a --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'

Running containers should still be present. A stopped container that was newer than the until boundary, lacked the selected label, or failed another filter should remain. If a container you expected to remove is still listed, inspect its creation time and labels before changing the filter.

There is no Docker undo command for a pruned container. If you removed the wrong one, recover its service from the original image and deployment definition. Named volumes are separate objects and are not removed by this command, so application data in a named volume may still be available; verify the volume and application backup before attempting a recreation. An anonymous volume or a container's writable layer should not be treated as a guaranteed recovery copy.

Common traps

Done means