Docker & ContainersVI Β· Container LifecycleRemoval and replacement
Removing and recreating containers β what removal destroys
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
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
$ docker inspect --format '{{.LogPath}}' web/var/lib/docker/containers/25fb9c8b983b3ca3358b9bdc6902921ed5d6e8bc1d78db7eef0e2f5f2645f98a/25fb9c8b983b3ca3358b9bdc6902921ed5d6e8bc1d78db7eef0e2f5f2645f98a-json.logIf 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
$ docker rm webError response from daemon: cannot remove container "web": container is running: stop the container before removing or force removeThat 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.
$ docker volume ls --filter dangling=true --format '{{.Name}}'0e22ff337fce030ca13101cf85a6c2c5a16adaa1bab461f0f89c15214e3c456e
1c2f6c9913f64e83719fab7d85f0d8907acd93d03128f282e1eff4b7345951fc
1e4617a57468922d11e5997e605efb7e1fe5da073e7d8d78cf6f4dfa2d7d1ae2The way to avoid generating anonymous volumes in the first place is to mount something explicit over every path the image declares as a volume:
docker run -d --name db \
-v pgdata:/var/lib/postgresql/data \
--stop-timeout 120 \
postgres:16A 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:
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.0Or wait for removal to complete before reusing the name:
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
doneThe 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
Q1. Which of these survives `docker rm -v web`?
Q2. `docker rm -f` performs a graceful stop before removing the container.
Q3. A detached container was started with `--rm` and has crashed. Which post-mortem sources are gone? Select all that apply.
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.