Docker Orphan Containers: Why They Appear and How to Purge Safely

Was this helpful?

Orphan containers show up the way unclaimed luggage shows up at an airport: quietly, persistently, and always when you’re already late.
Disk fills. Ports collide. Monitoring screams about “unknown” containers. And the worst part: half the time, the “orphan” is actually doing something useful.

This is the field guide for figuring out what’s really orphaned, what’s merely unlabeled, and how to clean up without turning a routine maintenance window into a career event.

What “orphan container” actually means (and what it doesn’t)

“Orphan container” is not a Docker runtime concept. Docker doesn’t have a flag that says “this container is orphaned.”
Orphan is an operational label we slap on containers that appear disconnected from the tooling and intent that created them.

In practice, a container gets called “orphan” when one (or more) of these are true:

  • The deployment tool no longer knows about it (Compose project renamed, Swarm stack removed, CI pipeline moved, etc.).
  • No one can name an owner (no labels, no service mapping, no ticket, no runbook).
  • It’s still running, but nobody’s watching it (no metrics, no logs shipping, no alerts).
  • It’s stopped but not removed and slowly accumulating with old versions.

The dangerous misconception is that “orphan” means “safe to delete.” Sometimes it does. Sometimes it’s your only copy of a stateful sidecar
that someone hand-started at 2 a.m. during an outage and then forgot to document.

If you want a more reliable working definition for production: an orphan is a container whose lifecycle controller has vanished.
No controller means no predictable upgrades, no drift management, and no automated cleanup. That’s why they accumulate.

Why orphan containers appear in real systems

Orphans aren’t a moral failing. They’re an emergent property of teams shipping software faster than they ship operational hygiene.
Here are the main causes I see repeatedly.

1) Docker Compose project/name drift

Compose identifies “its” containers by a project name. Change the directory name, use -p sometimes but not always,
or update Compose versions with slightly different defaults, and suddenly you have multiple “projects” on the same host. Each project’s
containers look like strangers to the other.

2) CI/CD deploy strategy leaves old containers behind

Blue/green or canary can be clean. Or it can be “start new containers, forget to stop old ones.”
Especially with ad-hoc scripts that do docker run with new image tags and never remove the previous container.

3) Crash loops create stopped-container graveyards

A container that fails immediately can be restarted by a policy or supervisor. But the old instances might remain if created repeatedly
by a pipeline, a script, or Compose runs with changing names. You end up with dozens or hundreds of exited containers, each one with logs
and writable layer data consuming disk.

4) Engineers run “just one quick container” on prod

You can feel the shape of the incident: someone needs a one-off migration, a debug tool, a packet capture, a backup. They run a container.
They swear they’ll remove it. Then Slack happens. Then Q4 happens. Now it’s immortal.

5) Swarm, Kubernetes, or systemd changed, but the containers stayed

When you migrate orchestration layers, there’s a transitional period where old hosts still run legacy workloads.
Remove the orchestrator and you remove the part that cleans up.

6) Networks and volumes outlive containers (by design)

Docker intentionally treats volumes as durable. That’s good. But it also means cleanup is not “remove containers” — it’s “remove containers,
networks, volumes, and images with the correct set of constraints.” Miss one and you still bleed disk.

One joke, because you deserve one: Docker cleanup is like decluttering a garage—everything is “temporary” until it becomes a load-bearing pile.

Interesting facts and historical context (the stuff that explains today’s mess)

  1. Docker volumes were designed to outlive containers, which is why deleting containers doesn’t free your “real” disk usage.
  2. Docker Compose originally targeted local development. Production use became common because it was convenient, not because it was a perfect ops tool.
  3. Early Docker workflows used “pets” (manually managed containers) long before immutable infrastructure practices became mainstream.
  4. Compose labels became the de facto metadata system: modern Compose stamps containers with labels like com.docker.compose.project and com.docker.compose.service.
  5. “Dangling images” are a side effect of layered builds: when a tag moves, the old layers can remain referenced by nothing but still occupy space.
  6. Docker’s overlay filesystem behavior matters: writable container layers can grow even if your app writes “temporary” data, because “temporary” might be inside the layer.
  7. Restart policies can hide failures: a container that restarts forever looks “healthy” in docker ps if you only glance at “Up” time.
  8. Compose v2 shifted into the Docker CLI (as docker compose), which changed installation paths and sometimes behavior across fleets.
  9. Prune commands were added because people filled disks constantly. They’re powerful, sharp, and easy to misuse.

Fast diagnosis playbook

When you suspect orphan containers, you’re usually responding to one of three pains: disk pressure, port conflicts, or “what is this thing” during an incident.
This playbook gets you to the bottleneck quickly without deleting the wrong thing.

First: identify the pain category in 60 seconds

  • Disk pressure: Docker host root filesystem is filling, or /var/lib/docker is huge.
  • Port conflict: deploy fails because port is already bound, or traffic is going to the wrong container.
  • Unknown runtime: monitoring shows CPU/mem spikes from a container no one recognizes.

Second: map containers to “controller”

Your goal: determine whether each suspicious container is controlled by Compose, Swarm, Kubernetes, systemd, or “someone’s shell history.”
Labels and naming patterns tell you most of what you need.

Third: decide “stop” versus “remove” versus “leave”

Stopping is reversible-ish (until you forget why it existed). Removing is final for the writable layer, not necessarily for volumes.
Leaving is acceptable when you can’t prove it’s safe and you need time to investigate.

Fourth: clean the correct resource class

Containers aren’t the only leak. Images, build cache, volumes, and networks each have their own failure modes.
Fix the biggest culprit first, and don’t touch volumes unless you’ve verified they’re unused and not part of a restore plan.

One reliability quote that actually holds up: “Hope is not a strategy.” — paraphrased idea often attributed to engineers and operators everywhere.
The point is simple: don’t rely on “probably safe to delete”; verify.

Practical tasks: commands, outputs, and decisions (12+)

These are the commands I run on real hosts. Each task includes: command, what the output means, and the decision you make next.
Run them as a user with Docker privileges (often root or in the docker group). In production, prefer doing this in a screen/tmux session
and logging your actions.

Task 1: List running containers with high-signal columns

cr0x@server:~$ docker ps --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}'
CONTAINER ID   NAMES                      IMAGE                    STATUS          PORTS
8c1f2a9d1b33   billing-api-1              billing:2026.01.03       Up 3 days        0.0.0.0:8080->8080/tcp
2b7d9c0e6c12   tmp_migrate_2025_11        alpine:3.19              Up 90 days       0.0.0.0:9000->9000/tcp

Meaning: You’re looking for “what is still alive” and “what is binding ports.” Names like tmp_* are a smell, not proof.

Decision: For anything suspicious, move to inspection: labels, mounts, and command line.

Task 2: List stopped containers that quietly eat disk

cr0x@server:~$ docker ps -a --filter status=exited --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}'
CONTAINER ID   NAMES                   IMAGE                 STATUS
a9d0c2f8e1aa   billing-api-1_old       billing:2025.12.10    Exited (137) 2 weeks ago
f1b2a3c4d5e6   debug-shell             ubuntu:22.04          Exited (0) 4 months ago

Meaning: Exited containers keep their writable layer and logs until removed.

Decision: If they’re clearly superseded and have no volumes you care about, remove them. But first, check mounts.

Task 3: Inspect a suspicious container for labels (ownership)

cr0x@server:~$ docker inspect 2b7d9c0e6c12 --format '{{json .Config.Labels}}'
{"com.docker.compose.project":"billing","com.docker.compose.service":"migrate","com.docker.compose.oneoff":"True"}

Meaning: Compose created it as a one-off. That often means docker compose run or a migration job.

Decision: Find the Compose project on disk and see if this was meant to be temporary. One-off containers running for 90 days are rarely intentional.

Task 4: Inspect mounts to see if deletion risks data loss

cr0x@server:~$ docker inspect billing-api-1 --format '{{range .Mounts}}{{.Type}} {{.Name}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
volume billing_db_data /var/lib/docker/volumes/billing_db_data/_data -> /var/lib/postgresql/data
bind  - /srv/billing/config -> /app/config

Meaning: This container uses a named volume for database data. Removing the container is fine; removing the volume is not fine unless you’re absolutely sure.

Decision: You can remove old containers, but preserve volumes until you prove they’re unused or have backups and a restore plan.

Task 5: Check container size growth (writable layer)

cr0x@server:~$ docker ps -a --size --format 'table {{.Names}}\t{{.Status}}\t{{.Size}}'
NAMES               STATUS                  SIZE
billing-api-1       Up 3 days               12.3MB (virtual 312MB)
tmp_migrate_2025_11 Up 90 days              8.1GB (virtual 18.2GB)

Meaning: The writable layer of tmp_migrate_2025_11 is huge. That’s often logs, caches, or accidental data writes to the container FS.

Decision: Exec in and find what’s growing, or capture logs and remove the container if it’s not meant to persist.

Task 6: Identify which containers are binding specific ports

cr0x@server:~$ docker ps --format '{{.Names}} {{.Ports}}' | grep -E '0\.0\.0\.0:8080|:8080->'
billing-api-1 0.0.0.0:8080->8080/tcp

Meaning: Port 8080 is owned by billing-api-1. If your new deploy can’t start, this is why.

Decision: Verify whether this is the intended current instance. If not, stop it cleanly and redeploy correctly.

Task 7: Get the full startup command to understand intent

cr0x@server:~$ docker inspect tmp_migrate_2025_11 --format 'Entrypoint={{json .Config.Entrypoint}} Cmd={{json .Config.Cmd}}'
Entrypoint=["/bin/sh","-lc"] Cmd=["python manage.py migrate && python manage.py collectstatic --noinput && sleep 9999999"]

Meaning: Someone chained a migration and then an infinite sleep. That’s a classic “temporary container turned permanent.”

Decision: Confirm migrations are done and nothing is depending on this container (e.g., mounted volumes used elsewhere). Then remove it.

Task 8: Check restart policy (containers that come back like bad ideas)

cr0x@server:~$ docker inspect billing-api-1 --format 'RestartPolicy={{.HostConfig.RestartPolicy.Name}}'
RestartPolicy=unless-stopped

Meaning: If you reboot the host, this container comes back automatically. That’s not orchestration; it’s persistence via restart policy.

Decision: If you want it gone, you must remove it (or set restart policy to no and stop it).

Task 9: Map containers to Compose projects and spot “foreigners”

cr0x@server:~$ docker ps -a --format '{{.Names}}' | while read n; do docker inspect "$n" --format '{{.Name}} {{index .Config.Labels "com.docker.compose.project"}}' 2>/dev/null; done | sed 's#^/##'
billing-api-1 billing
billing-db-1 billing
tmp_migrate_2025_11 billing
debug-shell <no value>

Meaning: debug-shell has no Compose project label. That makes it harder to attribute and more likely to be hand-run.

Decision: For unlabeled containers, trace via image, command, mounts, and creation time. Don’t delete blind.

Task 10: Find “dangling” and unused resources with a dry-run mindset

cr0x@server:~$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          48        12        21.4GB    15.1GB (70%)
Containers      73        9         9.3GB     8.8GB (94%)
Local Volumes   19        7         120.6GB   22.4GB (18%)
Build Cache     62        0         4.1GB     4.1GB

Meaning: Containers are highly reclaimable: you likely have many stopped containers or bloated writable layers.
Volumes are large but less reclaimable; active volumes are in use.

Decision: Start with containers and images. Touch volumes only after confirming they’re unused and not part of disaster recovery.

Task 11: Show which volumes are unused (but don’t delete yet)

cr0x@server:~$ docker volume ls --format 'table {{.Name}}\t{{.Driver}}'
NAME               DRIVER
billing_db_data    local
billing_cache      local
old_tmp_data       local

cr0x@server:~$ docker volume inspect old_tmp_data --format 'Name={{.Name}} Mountpoint={{.Mountpoint}}'
Name=old_tmp_data Mountpoint=/var/lib/docker/volumes/old_tmp_data/_data

Meaning: Listing volumes doesn’t tell you if they’re attached. Inspection shows where they live on disk.

Decision: Cross-check references from containers before deleting volumes.

Task 12: Determine if any container still references a volume

cr0x@server:~$ docker ps -a --format '{{.ID}} {{.Names}}' | while read id name; do docker inspect "$id" --format '{{.Name}} {{range .Mounts}}{{.Name}} {{end}}' | sed 's#^/##'; done | grep -w old_tmp_data || true

Meaning: No output means no container currently mounts old_tmp_data.

Decision: It’s a candidate for removal, after you confirm it’s not meant for future restores or manual workflows.

Task 13: Safely stop a container (observe impact)

cr0x@server:~$ docker stop --time 30 tmp_migrate_2025_11
tmp_migrate_2025_11

Meaning: Docker sent SIGTERM and waited up to 30 seconds before SIGKILL. Stopping is reversible: you can restart if needed.

Decision: Watch app health (requests, errors, queues). If nothing changes, proceed to remove.

Task 14: Remove container and confirm it’s gone

cr0x@server:~$ docker rm tmp_migrate_2025_11
tmp_migrate_2025_11

cr0x@server:~$ docker ps -a --format '{{.Names}}' | grep -w tmp_migrate_2025_11 || echo "not found"
not found

Meaning: The container is removed. Any named volumes remain unless explicitly deleted.

Decision: Re-run docker system df to verify reclaimed space and ensure you didn’t just delete something that was actively used.

Task 15: Remove a Compose project’s orphans the correct way

cr0x@server:~$ cd /srv/billing
cr0x@server:~$ docker compose ps
NAME            IMAGE                 SERVICE     STATUS
billing-api-1   billing:2026.01.03    api         running
billing-db-1    postgres:15           db          running

cr0x@server:~$ docker compose up -d --remove-orphans
[+] Running 2/2
 ✔ Container billing-db-1   Running
 ✔ Container billing-api-1  Running

Meaning: Compose reconciles the project state and removes containers in the same project that are not declared in the current Compose file.

Decision: Use this when you trust the Compose file as the source of truth. Don’t use it in a directory you’re not sure matches production.

Task 16: Controlled pruning (the least-worst way)

cr0x@server:~$ docker image prune -f
Deleted Images:
deleted: sha256:4f9c1a...
Total reclaimed space: 2.3GB

cr0x@server:~$ docker container prune -f
Deleted Containers:
a9d0c2f8e1aa
f1b2a3c4d5e6
Total reclaimed space: 6.7GB

Meaning: Image prune removes unused images (not referenced by any container). Container prune removes stopped containers.

Decision: This is usually safe on single-host setups if you understand what “unused” means. It’s not safe if you rely on stopped containers as forensic artifacts.

Docker Compose orphans: the usual suspect

Most “orphan container” conversations eventually turn into a Compose conversation, because Compose is excellent at creating containers and only moderately opinionated about cleaning them.
Compose tracks resources via labels and a project name. That project name can come from:

  • the directory name you ran it from,
  • the -p flag,
  • the name: field in newer Compose specs (depending on versions and tooling).

Change any of these and you can create a second universe of containers that look unrelated. They’re not “orphaned” from Docker’s perspective.
They’re orphaned from your mental model.

Compose also creates “one-off” containers (label com.docker.compose.oneoff=True) when you do things like:

  • docker compose run for migrations,
  • ad-hoc admin tasks,
  • debug commands that end with a long sleep because someone wanted to “keep it around.”

The safe approach is to treat Compose as declarative infrastructure: the file is the contract. Reconcile to it with docker compose up -d --remove-orphans.
Then stop doing one-offs on prod without a cleanup plan.

A safe purge strategy (what I actually do)

Purging safely is about sequencing and proof. You want to remove the junk without removing the thing that is silently keeping a legacy integration alive.
Here’s the strategy that scales from a single host to a small fleet.

Step 1: Classify containers into three buckets

  • Declared: created by known tooling (Compose project you can find, Swarm service, etc.).
  • Probably declared: has labels or naming patterns suggesting ownership, but you can’t find the controller immediately.
  • Undeclared: no labels, no obvious repo, no one can explain it.

Declared containers are easy: fix the controller, not the symptom. Probably declared containers require a bit of archaeology. Undeclared containers require caution.

Step 2: Prefer stopping before removing

Stopping gives you a rollback lever. If someone yells, you can restart. If nothing breaks after a reasonable observation window, remove.
“Reasonable” depends on workload: minutes for stateless APIs behind a load balancer; hours or days for cron-like jobs that run weekly.

Step 3: Separate container cleanup from volume cleanup

Containers are disposable. Volumes are where the bodies are buried. A safe cleanup campaign typically:

  • removes exited containers,
  • removes unused images and build cache,
  • only then evaluates volumes, one by one, with evidence.

Step 4: Put guardrails on future orphan creation

If you just clean up once, you’ll be back. Guardrails that work:

  • Standardize Compose project names (-p or explicit name) per environment.
  • Require labels for ownership and ticket references on ad-hoc containers.
  • Scheduled reporting: “containers without compose/swarm labels” is a cheap audit.
  • Disk alerts on /var/lib/docker with enough headroom to act before outages.

Second joke, because the universe is unfair: Nothing is more permanent than a temporary container started with “I’ll delete it after lunch.”

Three corporate-world mini-stories

Mini-story 1: The incident caused by a wrong assumption

A mid-sized SaaS company ran a few “simple” Docker hosts for internal services—billing exports, a couple of ETL pipelines, and a dashboard.
No orchestrator. Just Compose and a lot of confidence.

During a routine deploy, the on-call engineer ran docker system prune -a -f to clear disk space.
It worked. Disk recovered. Deploy proceeded. Then customer-facing exports started failing with authentication errors that didn’t make sense.

The wrong assumption: “Unused images are safe to delete.” In their workflow, a stopped container was kept around as a hot spare for a legacy job
that ran only at month-end. That container referenced an image that wasn’t used by any running container at the time of the prune.

When month-end arrived, their automation tried to start the job instantly. Pulling the image required network access to a registry that was intermittently blocked
by corporate firewall changes. The job didn’t run. Finance noticed. That escalated quickly.

The fix was not “never prune.” The fix was to make month-end jobs declared, predictable, and tested, and to cache or mirror required images properly.
They also learned to treat “stopped but important” as a real state that needs documentation and monitoring, not superstition.

Mini-story 2: The optimization that backfired

Another organization wanted faster deploys. Someone noticed that Compose recreates containers when configuration changes, and that old containers pile up.
So they “optimized” by switching to a script that did docker run with unique container names per build, leaving the old ones stopped “just in case.”

At first it felt great. Rollback was “start the previous container.” No registry pulls, no waiting. The team congratulated itself.

Then the host hit disk pressure. Not from images, but from exited containers and their writable layers. Each build wrote a few hundred megabytes of caches
into the container filesystem. Multiply by weeks of deploys and you get a slow-motion failure.

The backfire was operational complexity: the rollback mechanism was manual and error-prone, and the cleanup story became “someone should prune sometimes.”
When the disk filled, Docker started failing to start containers, then logging broke, and finally even SSH sessions got weird because the root filesystem was nearly full.

The long-term solution was boring: use a real deployment pattern with explicit retention (keep N previous images), store caches in volumes or external stores,
and enforce cleanup as part of the deployment pipeline. They replaced “ad-hoc rollback” with repeatable rollbacks.

Mini-story 3: The boring but correct practice that saved the day

A payments-adjacent company ran several Docker hosts with strict change control. Not glamorous, but effective. They had a scheduled weekly job that collected:
docker ps -a, docker system df, and a list of containers missing ownership labels. The output went into an internal ticket automatically.

One week, the report flagged a new running container with no Compose labels, binding a high port and consuming steady CPU.
It wasn’t heavy enough to trigger alerts, but it was odd.

The on-call engineer traced it via docker inspect: it mounted a host directory containing application secrets, and it was started from a generic base image.
That looked like either a debugging shortcut or something worse. They stopped it in a controlled window and watched metrics. Nothing broke.

After some internal investigation, it turned out to be a “temporary” diagnostics container started during a vendor call. It was left running and forgotten.
The boring practice—the weekly report—caught it before it became a compliance issue.

They didn’t punish the engineer who started it. They changed the process: ad-hoc containers required labels and an expiration note, and the report became a daily check on critical hosts.
Nobody wrote a blog post about it. Everything kept working. That’s the win.

Common mistakes: symptom → root cause → fix

This is the section you read when you’re tired and the host is paging you. Each item is specific because generic advice is how you get surprise downtime.

1) “I removed the container but disk usage didn’t change”

  • Symptom: docker rm runs, but /var/lib/docker remains huge.
  • Root cause: Volumes and images still consume space; container layer wasn’t the main culprit.
  • Fix: Run docker system df; prune stopped containers; prune unused images; review volumes individually before deleting.

2) “docker compose down didn’t remove the weird container”

  • Symptom: Compose project is down, but some containers remain.
  • Root cause: Container belongs to a different Compose project name (directory drift or -p mismatch), or it wasn’t created by Compose.
  • Fix: Inspect labels com.docker.compose.project. Run Compose from the correct directory with the correct project name, or remove manually after verifying usage.

3) “A container keeps coming back after I stop it”

  • Symptom: You stop a container, and it restarts.
  • Root cause: Restart policy (always / unless-stopped) or an external controller (systemd, Swarm, Kubernetes) is recreating it.
  • Fix: Identify controller via labels. Disable the controller or remove the service definition. For restart policy, remove the container or recreate with --restart=no.

4) “After pruning, an app can’t start because the image is missing”

  • Symptom: Start fails and Docker tries to pull unexpectedly.
  • Root cause: You pruned images that weren’t referenced by running containers, but were relied on for quick starts or scheduled jobs.
  • Fix: Make jobs declared and tested; ensure registry access; keep a controlled image cache; prune with policy, not emotion.

5) “After cleanup, the app starts but data is gone”

  • Symptom: Service runs but database/content resets.
  • Root cause: You deleted a named volume or switched to an anonymous volume unknowingly.
  • Fix: Stop deleting volumes casually. Verify mounts with docker inspect. Use named volumes with explicit names. Back up volumes and test restore.

6) “There are dozens of similarly named containers”

  • Symptom: Containers like api_1, api_1_old, api_1_202512, etc.
  • Root cause: Deploy scripts create new containers instead of recreating; Compose project drift; manual rollbacks.
  • Fix: Standardize naming and controllers. Use Compose reconciliation or an orchestrator. Define retention explicitly (N previous images, not N previous containers).

7) “Orphan containers have huge logs”

  • Symptom: Disk fills; logs under Docker grow; containers show large size.
  • Root cause: Logging driver is json-file with no rotation; chatty apps; crashed loops.
  • Fix: Configure log rotation at Docker daemon or per-container; consider centralized logging; remove containers with enormous stale logs after capturing what you need.

Checklists / step-by-step plan

Checklist A: “I need to free disk safely today”

  1. Get a baseline: docker system df and a filesystem check on /var/lib/docker.
  2. Remove stopped containers first: docker container prune (or manual remove after review).
  3. Prune build cache if you build locally: docker builder prune (verify you’re not relying on it for performance during the incident).
  4. Prune unused images: docker image prune (avoid -a unless you understand scheduled jobs and cold-start needs).
  5. Only then consider volumes: identify unused volumes; verify not referenced; verify backup/restore posture; delete selectively.
  6. Recheck: docker system df. Confirm alert clears and Docker can start new containers.

Checklist B: “I found an unknown running container”

  1. Don’t delete it yet. Identify it: docker ps, then docker inspect for labels and mounts.
  2. Check ports: if it binds public ports, treat as urgent.
  3. Check command: look for debug shells, sleeps, or tunnel processes.
  4. Check mounts: host paths and secrets are high risk.
  5. Stop it with an observation window. If nothing breaks, remove it and open a ticket to prevent recurrence.

Checklist C: “Prevent orphans from coming back”

  1. Standardize Compose project naming (-p fixed per env) and keep the Compose files in known paths.
  2. Enforce labels on docker run in production (owner, ticket, expiry).
  3. Implement a scheduled audit: containers missing ownership labels; volumes not referenced; total reclaimable space.
  4. Set log rotation and monitor disk usage with enough lead time.
  5. Make one-off jobs real jobs (declared, repeatable, monitored).

FAQ

1) What is an “orphan container” in Docker terms?

Docker doesn’t define it formally. Operators use “orphan” to mean “a container still present on the host with no clear owner/controller.”

2) Are orphan containers always safe to delete?

No. “Orphan” often means “nobody remembers why it exists,” which is not the same as “unused.” Verify mounts, ports, and traffic before removing.

3) Why do I get orphan warnings with Docker Compose?

Compose warns when containers exist in the same project but are not defined in the current Compose file. That happens after renames, service removals, or project name drift.

4) What does docker compose up -d --remove-orphans actually remove?

Containers in the same Compose project that are not declared in the current Compose file. It does not delete volumes unless you explicitly ask Compose to remove them.

5) What’s the safest “prune” command?

docker container prune is usually the safest because it targets stopped containers only. Next is docker image prune for unused images.
Be cautious with docker system prune -a, especially on hosts running scheduled jobs or where cold-start pulls are risky.

6) How do I find who created a container?

Start with docker inspect and look at labels. Compose, Swarm, and many CI systems stamp helpful labels.
If labels are absent, inspect the command, mounts, image name, and creation time and correlate with deployment logs.

7) If I remove a container, do I lose data?

You lose the container’s writable layer. Data in named volumes persists. Data written inside the container filesystem (not a volume) is gone.
Always check mounts before deleting.

8) Why do exited containers take so much space?

Exited containers keep their filesystem layer and logs. A chatty app using default JSON logs can generate large files even when the container is no longer running.

9) Why do containers reappear after reboot?

Restart policies like unless-stopped can bring them back, and external controllers can recreate them. Identify which mechanism applies before you assume “Docker is haunted.”

10) How do I prevent orphan containers during one-off maintenance?

Prefer running one-offs as declared jobs (Compose service, scheduled task, or orchestrator job). If you must use docker run, set labels and a cleanup plan,
and avoid long-running sleeps.

Next steps you can do today

If you only do three things, do these:

  1. Run docker system df and identify whether containers, images, volumes, or build cache are the real disk hogs.
  2. Audit ownership: list containers missing Compose/stack labels and inspect their mounts and ports before you touch them.
  3. Put a recurring cleanup and reporting routine in place, then standardize Compose project naming so you stop breeding parallel universes.

Orphan containers aren’t a Docker mystery. They’re a lifecycle management failure. Fix the lifecycle, and the “orphans” mostly disappear—along with the 2 a.m. disk alerts.

← Previous
ZFS refquota: The Only Quota That Stops “Used Space Lies”
Next →
Debian 13 “Start request repeated too quickly”: systemd fixes that actually stick

Leave a comment