✦ For everyone, free.

Practical knowledge for real and everyday life

Home

16.3.2.2 Stopped Container Bloat

A focused guide to Stopped Container Bloat, connecting core concepts with practical Docker and container operations.

Stopped container bloat is the accumulation of exited but not-yet-removed containers on a host, each retaining its full writable layer on disk indefinitely until explicitly removed, a particularly easy category of disk consumption to overlook since a stopped container does not appear in default process-oriented checks the way a running one does, but still occupies exactly the same disk space it did while running.

Why stopped containers are easy to lose track of

The default docker ps command shows only running containers, which means a host can have a large number of stopped containers quietly accumulating disk space while appearing, at a glance, to have very little actually running:

docker ps
CONTAINER ID   STATUS
3f29a8c1d8e2   Up 2 hours
docker ps -a
CONTAINER ID   STATUS
3f29a8c1d8e2   Up 2 hours
a8b9c2d3e4f5   Exited (0) 3 days ago
f1e2d3c4b5a6   Exited (1) 1 week ago
...

The -a flag is necessary to see the full picture, and on a host where this flag is not part of routine checking, stopped containers can accumulate unnoticed for an extended period, each one continuing to occupy whatever disk space its writable layer consumed during its runtime.

Common patterns that produce accumulation

Containers run for one-off tasks, a manual debugging session, a migration script, a test run, without the --rm flag, remain on disk after exiting unless explicitly removed afterward:

docker run my-api npm run migrate
docker run --rm my-api npm run migrate

The second invocation, using --rm, automatically removes the container the moment it exits, which is generally the more appropriate default for any genuinely one-off, non-persistent container run, removing the need to remember a separate cleanup step afterward.

Quantifying how much space stopped containers actually consume

Checking the size of stopped containers specifically, rather than assuming they are negligible, often reveals a meaningful contribution to overall disk usage, particularly for containers that wrote substantial logs or temporary data during their runtime before exiting:

docker ps -as --filter "status=exited"
CONTAINER ID   STATUS                   SIZE
a8b9c2d3e4f5   Exited (0) 3 days ago    1.2GB (virtual 1.8GB)

A stopped container with a writable layer this large, sitting unused and unremoved for days, is a direct, quantifiable contributor to disk pressure that a quick docker ps -a review without the size flag would not have made apparent.

Removing stopped containers safely

Removing a stopped container is generally low-risk once any needed forensic information, logs, specific files of interest, has already been extracted, since the underlying image and any properly configured volumes are entirely unaffected by container removal:

docker container prune
docker rm $(docker ps -aq --filter "status=exited")

docker container prune removes every stopped container on the host, which is appropriate for routine cleanup but worth being deliberate about during an active investigation, since removing a stopped container before extracting needed diagnostic information from it discards that information permanently.

Filtering pruning by age for safer routine cleanup

Rather than removing every stopped container indiscriminately, filtering by how long ago a container exited provides a reasonable balance between reclaiming space and retaining recently stopped containers that might still be relevant to an active or recent investigation:

docker container prune --filter "until=24h"

This removes only containers that have been stopped for at least 24 hours, which is generally a safe assumption that anything genuinely needing investigation would have already been examined within that window, while still reclaiming space from older, clearly no-longer-relevant stopped containers.

Restart policies and their interaction with stopped container accumulation

A restart policy of no (the default when none is specified) leaves a crashed or intentionally stopped container in the stopped state indefinitely, contributing to accumulation over time if not cleaned up; a policy like on-failure with a maximum retry count eventually gives up and leaves the container stopped as well, once that limit is reached, which is a related, slower-building source of the same accumulation pattern:

docker inspect my-api --format '{{.HostConfig.RestartPolicy}}'

Reviewing restart policy configuration across a fleet of containers, alongside routine stopped-container cleanup, addresses both the immediate accumulation and the ongoing pattern that continues producing more of it over time if left unaddressed.

Scheduled cleanup as routine maintenance

Establishing a scheduled, routine cleanup specifically targeting stopped containers, separate from or combined with broader image and build cache pruning, prevents this particular category of bloat from silently accumulating between less frequent, more comprehensive cleanup passes:

0 3 * * * docker container prune -f --filter "until=24h"

A daily scheduled prune targeting containers stopped for at least 24 hours keeps this specific category of disk usage consistently low without requiring anyone to remember to check for it manually.

CI and automated testing environments as a particular risk

CI pipelines and automated testing environments that frequently start and stop containers as part of their normal operation are particularly prone to rapid stopped container accumulation if cleanup is not built into the pipeline itself, since each test run can produce several stopped containers in quick succession:

test:
  script:
    - docker run --rm my-api npm test

Ensuring every container run as part of an automated pipeline includes --rm, or that the pipeline includes an explicit cleanup step at its conclusion, prevents this specific environment from becoming a disproportionately fast contributor to overall stopped container accumulation compared to more typical, manual usage patterns.

Common mistakes

  • Checking only docker ps rather than docker ps -a when assessing what is actually consuming resources on a host, missing the full population of stopped containers entirely.
  • Not using --rm for one-off, non-persistent container runs, leaving each one to accumulate on disk after exiting rather than being automatically cleaned up.
  • Removing stopped containers in bulk without first checking whether any contain forensic information still needed for an active or recent investigation.
  • Not establishing a scheduled, routine cleanup specifically for stopped containers, allowing them to accumulate silently between less frequent, broader cleanup passes.
  • Running CI or automated testing pipelines without --rm or an explicit cleanup step, producing rapid stopped container accumulation specific to that environment.

Stopped container bloat is easy to overlook precisely because stopped containers do not appear in default, running-process-oriented views, and addressing it requires both routine, scheduled cleanup with sensible age-based filtering and the habitual use of --rm for any container run that is genuinely intended to be one-off and non-persistent from the start.