Покинуті контейнери з’являються так само, як безхазяйна багаж на аеропорту: тихо, наполегливо і завжди в найневідповідніший момент.
Диск заповнюється. Порти конфліктують. Моніторинг кричить про «невідомі» контейнери. І найгірше: у половині випадків «сирота» насправді щось робить корисне.
Це польовий посібник, як зрозуміти, що справді покинуте, що просто не марковане, і як прибирати без перетворення рутинного вікна технічного обслуговування на кар’єрну подію.
Що насправді означає «покинутий контейнер» (і чого це не означає)
«Покинутий контейнер» — це не концепт Docker runtime. Docker не має прапорця «цей контейнер покинутий».
«Покинутий» — це операційна мітка, яку ми вештаємо на контейнери, що здаються відрваними від інструментів і намірів, які їх створили.
На практиці контейнер називають «покинутим», коли істинне (або декілька) з наступного:
- Інструмент розгортання більше про нього не знає (проект Compose перейменували, стек Swarm видалено, pipeline CI перенесено тощо).
- Ніхто не може назвати власника (немає лейблів, немає відображення на сервіс, немає задачі в трекері, немає рукбуку).
- Він ще працює, але ніхто не слідкує (немає метрик, немає відправки логів, немає оповіщень).
- Він зупинений, але не видалений і поступово накопичується в старих версіях.
Небезпечне хибне уявлення — що «покинутий» означає «безпечно видаляти». Іноді так. Іноді це єдина копія станового сайдкара,
який хтось вручну запустив о 2:00 під час інциденту й забув задокументувати.
Якщо вам потрібне надійніше робоче визначення для production: покинутий — це контейнер, у якого зник контролер життєвого циклу.
Немає контролера — нема прогнозованих оновлень, нема управління дрейфом і нема автоматичного прибирання. Ось чому вони накопичуються.
Чому покинуті контейнери з’являються в реальних системах
Покинуті — це не моральна вада. Це властивість систем, у яких команди доставляють софт швидше, ніж забезпечують операційну гігієну.
Ось основні причини, які я бачу знову і знову.
1) Дрфт імен/проєктів Docker Compose
Compose ідентифікує «свої» контейнери за іменем проєкту. Змінити назву директорії, інколи використовувати -p, або оновити версії Compose з трохи іншими значеннями за замовчуванням — і раптом на хості з’являються кілька «проєктів». Контейнери одного проєкту здаються чужими для іншого.
2) Стратегія CI/CD залишає старі контейнери
Blue/green або canary можуть бути чистими. А можуть бути «запустити нові контейнери, забути зупинити старі».
Особливо з ad-hoc скриптами, які виконують docker run з новими тегами образів і ніколи не видаляють попередній контейнер.
3) Цикли падіння створюють цвинтар зупинених контейнерів
Контейнер, що падає одразу, може перезапускатися політикою або супервізором. Але старі інстанси можуть залишатися, якщо їх багаторазово створював pipeline, скрипт або Compose з різними іменами. В результаті у вас десятки чи сотні exited-контейнерів, кожен з логами і writable layer, що споживає диск.
4) Інженери запускають «швидкий контейнер» в проді
Сценарій інциденту відчувається знайомо: комусь треба разова міграція, інструмент для дебагу, захоплення пакетів, бекап. Вони запускають контейнер.
Обіцяють видалити. Потім Slack стається. Потім Q4. І він стає безсмертним.
5) Swarm, Kubernetes або systemd змінилися, а контейнери лишилися
При міграції шарів оркестрації буває транзитний період, коли старі хости ще виконують легасі навантаження.
Видаливши оркестратор, ви видаляєте частину, яка виконувала прибирання.
6) Мережі та томи переживають контейнери (за дизайном)
Docker навмисно трактує томи як довготривалі. Це добре. Але це також означає, що прибирання — це не «видалити контейнери», а «видалити контейнери, мережі, томи й образи з правильною сукупністю обмежень». Пропустіть один елемент — і диск все ще буде текти.
Жарт, бо ви його заслужили: прибирання Docker — як приведення гаража до ладу — все «тимчасове», поки не стане несучою купою.
Цікаві факти та історичний контекст (що пояснює сучасний безлад)
- Docker томи були спроєктовані переживати контейнери, тому видалення контейнерів не звільняє ваш реальний простір на диску.
- Docker Compose спочатку орієнтувався на локальну розробку. Використання в production стало звичним через зручність, а не через ідеальність для опсів.
- Ранні робочі процеси Docker використовували «петі» (контейнери, якими керували вручну) задовго до того, як практики невразливої інфраструктури стали мейнстримом.
- Лейбли Compose стали де-факто системою метаданих: сучасний Compose маркирує контейнери лейблами на кшталт
com.docker.compose.projectтаcom.docker.compose.service. - «Висячі» образи — побічний ефект шарових збірок: коли тег рухається, старі шари можуть залишатися ніким не прив’язаними, але займати місце.
- Поведінка overlay-файлової системи Docker має значення: записувані шари контейнера можуть рости, навіть якщо ваш додаток пише «тимчасові» дані, бо «тимчасові» можуть бути всередині шару.
- Політики перезапуску можуть ховати збої: контейнер, що перезапускається нескінченно, виглядає «здоровим» у
docker ps, якщо лише глянути на «Up» час. - Compose v2 перемістився в CLI Docker (як
docker compose), що змінило шляхи встановлення і інколи поведінку по флоту хостів. - Команди prune додали, бо люди постійно заповнювали диски. Вони потужні, гострі і легкі для зловживання.
Швидкий план діагностики
Коли підозрюєте покинуті контейнери, зазвичай ви реагуєте на одну з трьох болючих проблем: тиск на диск, конфлікти портів або «що це таке» під час інциденту.
Цей план дозволяє швидко дістатися до вузького місця, не видаляючи невірне.
Перше: ідентифікуйте категорію болю за 60 секунд
- Тиск на диск: коренева файловa система Docker-хоста заповнюється або
/var/lib/dockerдуже велика. - Конфлікт портів: розгортання не вдається, бо порт вже зайнятий, або трафік потрапляє до неправильного контейнера.
- Невідомий рантайм: моніторинг показує стрибки CPU/пам’яті від контейнера, якого ніхто не впізнає.
Друге: зіставте контейнери з «контролером»
Ваша мета: визначити, чи кожен підозрілий контейнер контролює Compose, Swarm, Kubernetes, systemd або «чиєсь shell-history».
Лейбли й шаблони імен дають вам більшість потрібної інформації.
Третє: вирішіть «stop» проти «remove» проти «залишити»
Зупинка — відносно відкатна (поки ви не забудете, навіщо воно було). Видалення — фінальне для writable layer, але не обов’язково для томів.
Залишити — прийнятно, якщо ви не можете довести безпеку і потрібен час на розслідування.
Четверте: чистіть правильний клас ресурсів
Контейнери — не єдина теча. Образи, build cache, томи та мережі мають власні режими відмов.
Виправте найбільшого винуватця спочатку і не чіпайте томи, поки не перевірите, що вони не використовуються і не потрібні для відновлення.
Одна цитата про надійність, яка справді працює: «Надія — це не стратегія.» — парафраз ідеї, яку часто приписують інженерам і операторам.
Суть проста: не покладайтеся на «ймовірно безпечно видалити»; перевіряйте.
Практичні завдання: команди, виводи та рішення (12+)
Ось команди, які я запускаю на реальних хостах. Кожне завдання включає: команду, що означає вивід, і рішення, яке ви приймаєте далі.
Виконуйте їх з користувача з привілеями Docker (зазвичай root або в групі docker). У production надавайте перевагу виконанню в screen/tmux сесії
й логуванню своїх дій.
Завдання 1: Перелік запущених контейнерів з корисними колонками
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
Значення: Ви шукаєте «що ще живе» і «що прив’язує порти». Імена на кшталт tmp_* — запах, а не доказ.
Рішення: Для усього підозрілого переходьте до інспекції: лейбли, монтування і командна стрічка.
Завдання 2: Перелік зупинених контейнерів, що тихо їдять диск
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
Значення: Exited-контейнери зберігають свій writable layer і логи, поки їх не видалять.
Рішення: Якщо вони явно застарілі і не мають томів, які вас цікавлять, видаліть їх. Але спочатку перевірте монтування.
Завдання 3: Інспект контейнера на предмет лейблів (власність)
cr0x@server:~$ docker inspect 2b7d9c0e6c12 --format '{{json .Config.Labels}}'
{"com.docker.compose.project":"billing","com.docker.compose.service":"migrate","com.docker.compose.oneoff":"True"}
Значення: Compose створив його як one-off. Це часто означає docker compose run або міграційну задачу.
Рішення: Знайдіть проєкт Compose на диску і подивіться, чи це було тимчасово. One-off, що працює 90 днів — рідко свідомо.
Завдання 4: Інспект монтувань, щоб зрозуміти ризик втрати даних
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
Значення: Цей контейнер використовує іменований том для даних бази. Видалити контейнер — ок; видаляти том — не ок, якщо ви не впевнені.
Рішення: Можна видалити старі контейнери, але зберігайте томи, доки не доведете, що вони не використовуються або маєте бекап та план відновлення.
Завдання 5: Перевірити зростання розміру контейнера (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)
Значення: Writable layer tmp_migrate_2025_11 величезний. Зазвичай це логи, кеші або випадкові записи в файлову систему контейнера.
Рішення: Виконайте exec і знайдіть, що росте, або захопіть логи і видаліть контейнер, якщо він не повинен зберігатися.
Завдання 6: Визначити, які контейнери прив’язують конкретні порти
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
Значення: Порт 8080 зайнятий billing-api-1. Якщо ваше нове розгортання не стартує, ось чому.
Рішення: Перевірте, чи це нинішній потрібний інстанс. Якщо ні — зупиніть його коректно і задеплойте правильно.
Завдання 7: Отримати повну команду запуску, щоб зрозуміти намір
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"]
Значення: Хтось сполучив міграцію і потім нескінченний sleep. Класичний випадок «тимчасовий контейнер став постійним».
Рішення: Підтвердіть, що міграції завершено і нічого не залежить від цього контейнера (наприклад, монтувані томи), потім видаліть.
Завдання 8: Перевірити політику перезапуску (контейнери, що повертаються як погані ідеї)
cr0x@server:~$ docker inspect billing-api-1 --format 'RestartPolicy={{.HostConfig.RestartPolicy.Name}}'
RestartPolicy=unless-stopped
Значення: Після ребуту хосту цей контейнер знову підніметься автоматично. Це не оркестрація; це стійкість через restart policy.
Рішення: Якщо ви хочете його прибрати, треба видалити контейнер (або встановити restart policy у no і зупинити).
Завдання 9: Зіставити контейнери з проєктами Compose і помітити «чужаків»
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>
Значення: debug-shell не має лейбла Compose. Це ускладнює атрибуцію і робить ймовірнішим запуск вручну.
Рішення: Для немаркованих контейнерів простежте за образом, командою, монтуваннями і часом створення. Не видаляйте наосліп.
Завдання 10: Знайти «висячі» та невикористані ресурси з dry-run підходом
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
Значення: Контейнери дуже підлягають звільненню: ймовірно, у вас багато зупинених контейнерів або роздуті writable layers.
Томи великі, але менш підлягають звільненню; активні томи використовуються.
Рішення: Почніть з контейнерів і образів. Чіпайте томи лише після підтвердження, що вони не використовуються і не потрібні для аварійного відновлення.
Завдання 11: Показати, які томи не використовуються (але поки не видаляти)
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
Значення: Перелік томів не показує, чи вони приєднані. Інспекція показує, де вони знаходяться на диску.
Рішення: Перевірте посилання з контейнерів перед видаленням томів.
Завдання 12: Визначити, чи якийсь контейнер ще посилається на том
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
Значення: Відсутній вивід означає, що жоден контейнер наразі не монтує old_tmp_data.
Рішення: Кандидат на видалення, після підтвердження, що він не потрібен для майбутніх відновлень або ручних робіт.
Завдання 13: Безпечно зупинити контейнер (спостерігати вплив)
cr0x@server:~$ docker stop --time 30 tmp_migrate_2025_11
tmp_migrate_2025_11
Значення: Docker послав SIGTERM і чекав до 30 секунд перед SIGKILL. Зупинка майже відкатна: можна перезапустити у разі потреби.
Рішення: Слідкуйте за здоров’ям додатку (запити, помилки, черги). Якщо нічого не змінюється, переходьте до видалення.
Завдання 14: Видалити контейнер і підтвердити відсутність
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
Значення: Контейнер видалено. Іменовані томи залишаються, якщо їх явно не видалити.
Рішення: Заново виконайте docker system df, щоб перевірити звільнений простір і переконатися, що ви не видалили щось активне.
Завдання 15: Правильне видалення orphan-контейнерів проєкту Compose
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
Значення: Compose приводить стан проєкту у відповідність і видаляє контейнери того ж проєкту, які не оголошені в поточному Compose-файлі.
Рішення: Використовуйте це, коли ви довіряєте Compose-файлу як джерелу істини. Не застосовуйте у директорії, яка можливо не відповідає production.
Завдання 16: Контрольоване prune (менше зло)
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
Значення: image prune видаляє невикористані образи (які не посилаються з жодного контейнера). container prune видаляє зупинені контейнери.
Рішення: Зазвичай це безпечно на однохостових налаштуваннях, якщо ви розумієте, що означає «невикористане». Небезпечно, якщо ви покладаєтеся на зупинені контейнери як судові артефакти.
Підозрілі контейнері Docker Compose: звичайний підозрюваний
Більшість розмов про «покинуті контейнери» врешті-решт переходять на Compose, бо Compose відмінно створює контейнери і лише помірковано наполягає на їх прибиранні.
Compose відслідковує ресурси через лейбли і ім’я проєкту. Це ім’я проєкту може походити від:
- імені директорії, з якої ви його запустили,
- флагу
-p, - поля
name:в новіших специфікаціях Compose (залежно від версій і інструментів).
Зміна будь-якого з цих елементів може створити другу всесвіт контейнерів, що виглядають несумісними. З погляду Docker вони не «покинуті».
Вони покинуті з погляду вашої ментальної моделі.
Compose також створює «one-off» контейнери (лейбл com.docker.compose.oneoff=True), коли ви робите такі речі:
docker compose runдля міграцій,- ad-hoc адміністративні завдання,
- дебаг-команди, що закінчуються довгим sleep, бо хтось хотів «зберегти його».
Безпечний підхід — трактувати Compose як декларативну інфраструктуру: файл — це контракт. Узгоджуйте стан за допомогою docker compose up -d --remove-orphans.
Потім перестаньте робити one-off в проді без плану прибирання.
Стратегія безпечного очищення (що я насправді роблю)
Безпечне очищення — це про послідовність і докази. Ви хочете видалити сміття, не видаливши те, що мовчки підтримує легасі інтеграцію.
Ось стратегія, що масштабується від одного хоста до невеликого флоту.
Крок 1: Класифікуйте контейнери у три кошики
- Оголошені: створені відомими інструментами (проєкт Compose, який можна знайти, сервіс Swarm тощо).
- Ймовірно оголошені: мають лейбли або шаблони імен, що натякають на власність, але контролер знайти не одразу.
- Неоголошені: немає лейблів, немає очевидного репозиторію, ніхто не може пояснити.
Оголошені контейнери прості: виправляйте контролер, а не симптом. Ймовірно оголошені вимагають трохи археології. Неоголошені вимагають обережності.
Крок 2: Віддавайте перевагу зупинці перед видаленням
Зупинка дає важіль для відкату. Якщо хтось почне кричати, ви можете перезапустити. Якщо після розумного вікна спостереження нічого не сталося, видаляйте.
«Розумне» залежить від навантаження: хвилини для безстатусних API за балансувальником; години або дні для cron-подібних задач, що виконуються щотижня.
Крок 3: Розділіть прибирання контейнерів і томів
Контейнери — витратні. Томи — там, де ховаються тіла. Безпечна кампанія прибирання зазвичай:
- видаляє exited-контейнери,
- видаляє невикористані образи і build cache,
- тільки потім оцінює томи по одному з доказами.
Крок 4: Встановіть запобіжники проти майбутнього створення покинутих контейнерів
Якщо ви просто приберете раз — вони повернуться. Запобіжники, що працюють:
- Стандартизувати імена проєктів Compose (
-pабо явна назва) для кожного середовища. - Вимагати лейбли для власності та посилань на задачу при запуску ad-hoc контейнерів у production.
- Планові звіти: «контейнери без лейблів compose/swarm» — дешевий аудит.
- Оповіщення по диску для
/var/lib/dockerз достатнім запасом, щоб встигнути відреагувати до відмов.
Другий жарт, бо всесвіт несправедливий: Нічого не є більш постійним, ніж тимчасовий контейнер, запущений з коментарем «Видалю після обіду».
Три міні-історії з корпоративного життя
Міні-історія 1: Інцидент, спричинений хибним припущенням
Середня SaaS-компанія мала кілька «простих» Docker-хостів для внутрішніх сервісів — експортів білінгу, кількох ETL-пайплайнів і дашборду.
Ніякого оркестратора. Лише Compose і багато впевненості.
Під час рутинного деплою on-call інженер виконав docker system prune -a -f, щоб звільнити місце на диску.
Це спрацювало. Диск відновився. Деплой пройшов. Потім клієнтські експорти почали падати з помилками аутентифікації, які не мали сенсу.
Хибне припущення: «невикористані образи безпечні для видалення». У їхньому робочому процесі зупинений контейнер тримали як гарячу резервну копію для легасі джоба,
що виконується лише в кінці місяця. Цей контейнер посилався на образ, який на момент prune не використовувався жодним запущеним контейнером.
Коли настав кінець місяця, їхня автоматизація спробувала миттєво запустити джоб. Завантаження образу вимагало доступу до реєстру, який час від часу блокувався
корпоративним фаєрволом. Джоб не запустився. Фінансова команда помітила. Це швидко ескалувало.
Виправлення було не в «ніколи не prune». Виправлення — зробити місячні джоби задокументованими, передбачуваними та тестованими, і кешувати або дзеркалити потрібні образи.
Вони також навчилися трактувати «зупинений, але важливий» як реальний стан, який потребує документації і моніторингу, а не забобонів.
Міні-історія 2: Оптимізація, яка обернулася проти
Інша організація хотіла пришвидшити деплойти. Хтось помітив, що Compose пересоздає контейнери при зміні конфігурації, і що старі контейнери накопичуються.
Тож вони «оптимізували» це скриптом, що робив docker run з унікальними іменами контейнерів для кожного білду, лишаючи старі зупиненими «на всякий випадок».
Спочатку це здавалося чудово. Rollback був «запустити попередній контейнер». Ніяких pull з реєстру, ніякого очікування. Команда тішилась.
Потім хост відчув тиск на диск. Не через образи, а через exited-контейнери та їхні writable layers. Кожен білд записував кілька сотень мегабайт кешів
у файлову систему контейнера. Помножте на тижні деплоїв — і от повільний фейл.
Наслідком став операційний хаос: механізм відкату був ручний і схильний до помилок, а історія прибирання стала «хтось час від часу має prune».
Коли диск заповнився, Docker почав відмовляти запускати контейнери, потім логи перестали писатися, а згодом навіть SSH-сесії стали дивними, бо коренева файловка майже повна.
Довгострокове рішення було нудне: використовувати реальний патерн деплою з явним збереженням (зберігати N попередніх образів), зберігати кеші в томах або зовнішніх сховищах,
і забезпечити прибирання як частину pipeline. Вони замінили «ад-хок відкат» на відтворювані відкати.
Міні-історія 3: Нудна, але правильна практика, яка врятувала день
Компанія, близька до платежів, керувала кількома Docker-хостами зі строгим контролем змін. Не гламурно, але ефективно. Вони мали щотижневу задачу, що збирала:
docker ps -a, docker system df і список контейнерів без лейблів власності. Вивід йшов у внутрішній тикет автоматично.
Одного тижня звіт позначив новий запущений контейнер без лейблів Compose, який прив’язав високий порт і постійно споживав CPU.
Він не був достатньо важким, щоб викликати оповіщення, але виглядав дивно.
On-call інженер простежив за ним через docker inspect: він монтував директорію хоста з секретами додатку і був запущений з базового образу.
Це скидалось на дебаг-скорочення або щось гірше. Вони зупинили його в контрольоване вікно і спостерігали метрики. Нічого не впало.
Після внутрішнього розслідування виявилось, що це був «тимчасовий» діагностичний контейнер, запущений під час дзвінка з вендором. Його залишили запущеним і забули.
Нудна практика — щотижневий звіт — впіймала його до того, як він став питанням відповідності.
Вони не карали інженера, який його запустив. Змінили процес: ad-hoc контейнери вимагали лейблів і примітки про термін, а звіт став щоденною перевіркою на критичних хостах.
Ніхто не писав блог-пост про це. Усе продовжувало працювати. Оце і є перемога.
Типові помилки: симптом → корінь проблеми → виправлення
Це секція, яку ви читаєте, коли втомлені і хост вас пейджить. Кожний пункт конкретний, бо загальна порада — це шлях до несподіваного даунтайму.
1) «Я видалив контейнер, але використання диску не змінилося»
- Симптом:
docker rmвиконується, але/var/lib/dockerзалишається гігантським. - Корінь проблеми: Томи і образи все ще споживають місце; шар контейнера не був головним винуватцем.
- Виправлення: Запустіть
docker system df; prune зупинені контейнери; prune невикористані образи; перегляньте томи індивідуально перед видаленням.
2) «docker compose down не видалив дивний контейнер»
- Симптом: Проєкт Compose зупинено, але деякі контейнери залишилися.
- Корінь проблеми: Контейнер належить іншому імені проєкту Compose (дріфт директорій або невідповідність
-p), або він не був створений Compose. - Виправлення: Інспектуйте лейбли
com.docker.compose.project. Запустіть Compose з правильної директорії з правильним іменем проєкту, або видаліть вручну після перевірки використання.
3) «Контейнер продовжує повертатися після зупинки»
- Симптом: Ви зупиняєте контейнер, і він перезапускається.
- Корінь проблеми: Політика перезапуску (
always/unless-stopped) або зовнішній контролер (systemd, Swarm, Kubernetes) його відтворює. - Виправлення: Визначте контролер через лейбли. Вимкніть контролер або видаліть сервісну дефініцію. Для політики перезапуску — видаліть контейнер або пересоздайте з
--restart=no.
4) «Після pruning додаток не стартує, бо образ відсутній»
- Симптом: Старт не вдається і Docker намагається тягнути образ несподівано.
- Корінь проблеми: Ви запустили prune образи, які не були посилені запущеними контейнерами, але були потрібні для швидкого старту або запланованих задач.
- Виправлення: Зробіть задачі оголошеними і протестованими; забезпечте доступ до реєстру; тримайте контрольований кеш образів; робіть prune за політикою, а не емоціями.
5) «Після очищення додаток стартує, але дані зникли»
- Симптом: Сервіс працює, але база/контент скинуто.
- Корінь проблеми: Ви видалили іменований том або випадково перейшли на анонімний том.
- Виправлення: Не видаляйте томи поспішно. Перевіряйте монтування через
docker inspect. Використовуйте іменовані томи з явними іменами. Резервуйте та тестуйте відновлення томів.
6) «Є десятки подібно названих контейнерів»
- Симптом: Контейнери типу
api_1,api_1_old,api_1_202512тощо. - Корінь проблеми: Скрипти розгортання створюють нові контейнери замість пересоздати; дріфт проєктів Compose; ручні відкати.
- Виправлення: Стандартизуйте імена і контролери. Використовуйте примирення Compose або оркестратор. Визначте політику збереження явно (N попередніх образів, а не N контейнерів).
7) «Покинуті контейнери мають величезні логи»
- Симптом: Диск заповнюється; логи Docker ростуть; контейнери показують великий розмір.
- Корінь проблеми: Драйвер логування
json-fileбез ротації; чатливі додатки; цикли падіння. - Виправлення: Налаштуйте ротацію логів на daemon або на контейнер; розгляньте централізоване логування; видаляйте контейнери з величезними застарілими логами після збереження необхідного вмісту.
Контрольні списки / покроковий план
Контрольний список A: «Мені сьогодні треба безпечно звільнити диск»
- Отримайте базову картину:
docker system dfі перевірка файлової системи на/var/lib/docker. - Спочатку видаліть зупинені контейнери:
docker container prune(або ручне видалення після перегляду). - Prune build cache, якщо ви збираєте локально:
docker builder prune(перевірте, чи ви не залежите від нього під час інциденту). - Prune невикористані образи:
docker image prune(уникайте-a, якщо ви не розумієте заплановані джоби і холодні старти). - Лише потім розглядайте томи: ідентифікуйте невикористані томи; перевіряйте, що вони не прив’язані; перевіряйте політику бекап/відновлення; видаляйте вибірково.
- Перевірте знову:
docker system df. Підтвердіть, що оповіщення зникає і Docker може запускати нові контейнери.
Контрольний список B: «Я знайшов невідомий запущений контейнер»
- Поки не видаляйте. Ідентифікуйте:
docker ps, потімdocker inspectдля лейблів і монтувань. - Перевірте порти: якщо він прив’язує публічні порти — вважайте це терміновим.
- Перевірте команду: шукайте дебаг-шелли, sleep або тунельні процеси.
- Перевірте монтування: шляхи хоста і секрети — високий ризик.
- Зупиніть його з вікном спостереження. Якщо нічого не ламалось, видаліть і відкрийте задачу, щоб запобігти повторенню.
Контрольний список C: «Запобігти поверненню покинутих контейнерів»
- Стандартизуйте імена проєктів Compose (
-pфіксовано для середовища) і зберігайте Compose-файли в відомих шляхах. - Примусьте лейбли на
docker runу production (власник, ticket, термін дії). - Впровадьте плановий аудит: контейнери без лейблів власності; томи без посилань; загальний reclaimable простір.
- Встановіть ротацію логів і моніторинг диску з достатнім запасом часу для реагування.
- Перетворіть one-off задачі на реальні задачі (оголошені, повторювані, під моніторингом).
FAQ
1) Що таке «покинутий контейнер» в термінах Docker?
Docker не визначає його формально. Оператори вживають «покинутий», маючи на увазі «контейнер, що все ще присутній на хості без очевидного власника/контролера».
2) Чи завжди безпечно видаляти покинуті контейнери?
Ні. «Покинутий» часто означає «ніхто не пам’ятає, навіщо він існує», що не те саме, що «невикористаний». Перевіряйте монтування, порти і трафік перед видаленням.
3) Чому я отримую попередження про orphans у Docker Compose?
Compose попереджає, коли в проєкті є контейнери, які не визначені в поточному Compose-файлі. Це відбувається після перейменувань, видалень сервісів або дріфту імен проєктів.
4) Що насправді видаляє docker compose up -d --remove-orphans?
Контейнери в тому ж проєкті Compose, які не оголошені в поточному Compose-файлі. Воно не видаляє томи, якщо ви явним чином не попросите Compose це зробити.
5) Яка найбезпечніша команда «prune»?
docker container prune зазвичай найбезпечніша, бо орієнтується лише на зупинені контейнери. Далі — docker image prune для невикористаних образів.
Будьте обережні з docker system prune -a, особливо на хостах зі запланованими джобами або де pull-операції ризиковані.
6) Як знайти, хто створив контейнер?
Почніть з docker inspect і дивіться лейбли. Compose, Swarm та багато CI-систем додають корисні лейбли.
Якщо лейблів нема, інспектуйте команду, монтування, ім’я образу і час створення і корелюйте з логами розгортання.
7) Якщо я видалю контейнер, чи втрачу я дані?
Ви втрачаєте writable layer контейнера. Дані в іменованих томах зберігаються. Дані, записані всередині файлової системи контейнера (не в томі) — зникають.
Завжди перевіряйте монтування перед видаленням.
8) Чому exited-контейнери займають стільки місця?
Exited-контейнери зберігають свій файловий шар і логи. Чатливий додаток з дефолтним JSON-логуванням може генерувати великі файли навіть коли контейнер більше не працює.
9) Чому контейнери з’являються знову після перезавантаження?
Політики перезапуску на кшталт unless-stopped можуть їх піднімати, і зовнішні контролери можуть їх відтворювати. Перед тим як припустити «Docker одержимий», визначте, який механізм застосовується.
10) Як запобігти покинутим контейнерам під час одноразового обслуговування?
Віддавайте перевагу виконанню one-off як оголошених задач (Compose-сервіс, запланована задача або оркестратор job). Якщо мусите використовувати docker run, ставте лейбли і план прибирання,
і уникайте довготривалих sleep.
Наступні кроки, які ви можете зробити сьогодні
Якщо ви зробите лише три речі, зробіть ці:
- Запустіть
docker system dfі визначте, чи реальними хуліганами є контейнери, образи, томи чи build cache. - Аудит власності: перелікуйте контейнери без лейблів Compose/stack і інспектуйте їх монтування й порти перед тим, як щось чіпати.
- Впровадьте регулярну рутинну очистку і звітування, потім стандартизуйте імена проєктів Compose, щоб припинити породжувати паралельні всесвіти.
Покинуті контейнери — не загадка Docker. Це провал управління життєвим циклом. Виправте життєвий цикл, і «сироти» здебільшого зникнуть — разом із оповіщеннями про диск о 2:00 ночі.