Skip to main content
RunBook Academy

Docker & ContainersVI Β· Container LifecycleRemoval and replacement

Removing and recreating containers β€” what removal destroys

Intermediate⏱ ~22 mindocker

What you'll learn

  • List exactly what `docker rm` deletes and what it leaves behind
  • Avoid the two removal flags that silently destroy evidence
  • Recognise anonymous volume accumulation from repeated recreation
  • Write a replacement sequence that does not race itself

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-11

Not yet marked complete on this device.

Replacing a container is the normal way to change one, so removal is not an occasional cleanup task β€” it is part of every deploy. That makes it worth knowing precisely what gets destroyed, because the list includes the two things you will want most during an incident: the writable layer and the logs.

What removal deletes

Read-only / SafeLogPath
$ docker inspect --format '{{.LogPath}}' web
/var/lib/docker/containers/25fb9c8b983b3ca3358b9bdc6902921ed5d6e8bc1d78db7eef0e2f5f2645f98a/25fb9c8b983b3ca3358b9bdc6902921ed5d6e8bc1d78db7eef0e2f5f2645f98a-json.log

If your containers ship their logs somewhere central, removal costs you nothing. If they do not, removal is the end of the evidence β€” which is the argument for central log collection stated in operational rather than architectural terms.

Removal refuses running containers, on purpose

Destructiverm
$ docker rm web
Error response from daemon: cannot remove container "web": container is running: stop the container before removing or force remove

That refusal is a safety feature, and -f removes it. docker rm -f sends SIGKILL β€” not the stop signal, no grace period, no draining. It is equivalent to docker kill followed by docker rm.

--rm is the flag that costs you the post-mortem

docker run --rm removes the container as soon as it exits. For an interactive one-shot β€” docker run --rm -it alpine sh β€” that is exactly right, and it is the reason --rm exists.

Combined with -d, it is a trap:

Note also that --rm and a restart policy other than no are mutually exclusive β€” the daemon rejects the combination, because β€œrestart it when it dies” and β€œdelete it when it dies” cannot both be true.

Anonymous volumes accumulate

Any image with a VOLUME instruction creates an anonymous volume for every container built from it, unless you mount something over that path. postgres, mysql, mongo, redis and many others do this.

docker rm without -v leaves those volumes behind. Each one has a 64-character hex name and no indication of what created it. Recreate the container fifty times over a year of deploys and you have fifty of them, each holding a full copy of whatever the application had written.

Read-only / Safedangling
$ docker volume ls --filter dangling=true --format '{{.Name}}'
0e22ff337fce030ca13101cf85a6c2c5a16adaa1bab461f0f89c15214e3c456e
1c2f6c9913f64e83719fab7d85f0d8907acd93d03128f282e1eff4b7345951fc
1e4617a57468922d11e5997e605efb7e1fe5da073e7d8d78cf6f4dfa2d7d1ae2

The way to avoid generating anonymous volumes in the first place is to mount something explicit over every path the image declares as a volume:

Configuration changenamed volume
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
--stop-timeout 120 \
postgres:16

A replacement sequence that does not race

The obvious script has a race in it:

docker rm -f web
docker run -d --name web myapp:2.0.0

Removal is not fully synchronous β€” the container passes through the removing state, and the daemon releases the name at the end of that. On a busy host the docker run can arrive first:

docker: Error response from daemon: Conflict. The container name "/web" is
already in use by container "3f9c0a1e5b7d". You have to remove (or rename)
that container to be able to reuse that name.

Two robust patterns. Rename out of the way first, which releases the name immediately and leaves the old container available for post-mortem:

Service impact possiblerename-first
if docker ps -a --format '{{.Names}}' | grep -qx web; then
  docker stop --timeout 60 web
  docker rename web "web-old-$(date +%Y%m%dT%H%M%S)"
fi
docker run -d --name web myapp:2.0.0

Or wait for removal to complete before reusing the name:

Service impact possiblewait-for-removal
docker stop --timeout 60 web || true
docker rm web || true
for _ in $(seq 1 30); do
  docker ps -a --format '{{.Names}}' | grep -qx web || break
  sleep 1
done

The grep -qx matters: grep -q web also matches webhook-worker and web-old-20260811T091402. Anchored matching is the difference between a cleanup script and an outage.

Knowledge check

Knowledge check Β· 4 questions

  1. Q1. Which of these survives `docker rm -v web`?

  2. Q2. `docker rm -f` performs a graceful stop before removing the container.

  3. Q3. A detached container was started with `--rm` and has crashed. Which post-mortem sources are gone? Select all that apply.

  4. Q4. Why can `docker rm -f web` immediately followed by `docker run --name web` fail with a name conflict?

Passing score: 75%. Answers are checked in this browser.