Короткий ответ
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 dfImages большой,docker psдва контейнера;- после «почистили -a»
compose upпошёл в pull на час; - путают dangling и «старый :latest всё ещё tagged».
| Фильтр | Что попадёт |
|---|---|
| dangling=true | только без тега |
| prune (без -a) | dangling |
| prune -a | все unused tagged |
| rmi ID слоя, который у running | отказ, unless -f |
Возможные причины
- CI
buildбез-qи без удаления предыдущего digest. docker commitв руках.- Pull того же тега, digest сменился, старый остался
<none>. - Неудачный
docker image rmчастично. - Multi-stage, промежуточные не
--target/ BuildKit всё же оставляет cache (cache ≠ dangling images, но путают в du). - Редко: ручная пометка 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 не в этой операции.