Короткий ответ

Dangling-образ — слой без тега (<none>:<none>), обычно после docker compose build/docker tag на тот же репозиторий: старый digest потерял имя. Его можно удалять. Неиспользуемый, но тегированный registry.example:5000/app/web:1.2.3 dangling не является: его снесёт только docker image prune -a или ручной rmi. На host.example сначала docker images -f dangling=true, потом docker image prune без -a. Не трогайте app_data.

Обновление стека с сохранением volume: обновление без потери данных.

Симптомы и как отличить

  • docker images пестрит <none>;
  • docker system df Images большой, docker ps два контейнера;
  • после «почистили -a» compose up пошёл в pull на час;
  • путают dangling и «старый :latest всё ещё tagged».
ФильтрЧто попадёт
dangling=trueтолько без тега
prune (без -a)dangling
prune -aвсе unused tagged
rmi ID слоя, который у runningотказ, unless -f

Возможные причины

  1. CI build без -q и без удаления предыдущего digest.
  2. docker commit в руках.
  3. Pull того же тега, digest сменился, старый остался <none>.
  4. Неудачный docker image rm частично.
  5. Multi-stage, промежуточные не --target / BuildKit всё же оставляет cache (cache ≠ dangling images, но путают в du).
  6. Редко: ручная пометка untag.

Диагностика

docker images -f dangling=true
docker images --digests
docker system df -v | sed -n '1,40p'
docker ps -a --format '{{.Image}} {{.Names}} {{.Status}}'

Сверьте, какие теги нужны для отката app:

cd /opt/app
grep -n image: compose.yaml
docker compose images

Список «не удалять»: текущий и N предыдущих тегов registry.example:5000/app/web:....

Почему диск ещё большой после чистки dangling — карта диска (логи, volumes, build cache).

Решение

Сценарий A. Только dangling

docker image prune

Интерактивно подтвердите. Эквивалент:

docker images -f dangling=true -q | xargs -r docker image rm

Если image is being used by stopped container — это не dangling или контейнер держит ID. Решите, нужен ли контейнер:

docker ps -a --filter ancestor=<id>

Не -f на rmi, пока не посмотрите имена.

Сценарий B. Старые теги, но не все unused

Удаляйте явно:

docker image rm registry.example:5000/app/web:1.1.0

Не -a. Сохраните :1.2.3 и :1.2.2 для отката.

Сценарий C. Уже сделали prune -a

Восстановление:

cd /opt/app
docker compose pull
docker compose up -d

Если registry недоступен — private registry. Volume app_data должен остаться, контейнеры пересоздадутся.

Сценарий D. Build cache, не images

docker builder du
docker builder prune --filter until=240h

Это не image prune. Не путайте в одной команде с -a.

Тег registry.example:5000/app/web:1.2.3 занимает слои, общие с :1.2.4, если build был cache-friendly. docker image rm :1.2.3 может освободить мало, пока жив :1.2.4. Это нормально, не повод для -a. Смотрите docker system df -v раздел Shared Size / Unique Size, если ваш клиент его печатает, либо смиритесь, что overlay дедуплицирует.

На хосте с несколькими проектами dangling появляются от чужого CI, который собирает здесь же. Политика: prod-хост host.example не является builder. Иначе prune без -a станет ежедневной работой, а случайный -a снесёт откат app. Вынесите docker compose build на runner, на прод только pull.

Контейнер Exited держит Image ID: docker ps -a --filter ancestor=sha256:.... Пока он есть, ID не dangling. Решите, нужен ли контейнер для логов/диффа, затем docker rm без -v. Анонимный volume при rm -v умрёт — для app_data named этого флага не используйте.

docker image prune --filter until=72h режет dangling по времени создания, не по «безопасности тега». Если вы сняли тег час назад, чтобы переименовать digest в rollback, фильтр может съесть откат. Сначала docker tag <id> registry.example:5000/app/web:rollback, потом prune.

Как проверить, что проблема устранена

docker images -f dangling=true
docker images | grep 'app/web'
docker compose -f /opt/app/compose.yaml ps
df -h /var/lib/docker

Список dangling пуст или стабильно мал. Теги прода на месте. Стек Running. df снизился на ожидаемые гигабайты слоёв, не на размер app_data.

Если не помогло

  • <none> сразу появляются снова: CI каждую минуту. Чистите в job после push, не только на проде.
  • rmi требует -f: контейнер Exited держит образ. compose rm остановленного старого контейнера без -v.
  • Место не освободилось: общие слои с живым тегом / логи.
  • Digest в compose images не тот, что в registry — после pull пересоздайте.

Профилактика

  • CI: docker image prune (без -a) на сборщике после job.
  • Политика: хранить N тегов на хосте.
  • Не latest как единственный тег отката.
  • Алерт на число dangling и на df.
  • Документ «не запускать prune -a на prod».

На registry garbage collect и локальный dangling — разные вселенные. Удаление <none> на host.example не чистит registry.example:5000. Наоборот, GC registry не убирает локальные слои. Планируйте оба контура отдельно. Не запускайте GC registry в ту же ночь, что prune -a на всех хостах: потеряете и локальный откат, и blob.

FAQ

Dangling — это всегда мусор?

Почти всегда. Исключение: вы сняли тег, ещё не поставили новый, и собирались откатиться по ID. Тогда не prune, а docker tag <id> registry.example:5000/app/web:rollback.

Почему prune без -a мало места дал?

Большие tagged unused. Их удаляют точечно, не -a вслепую.

image prune --filter until=24h

Режет по времени dangling. Для tagged не заменяет политику тегов.

Можно ли cron prune -a?

На prod — нет. На эфемерном CI runner — да, если образы всегда pull/build с нуля.

Связь с обновлением контейнера

Новый тег, recreate, старый тег держите, dangling от промежуточных build — чистите. Volume не в этой операции.