Docker “volume is in use”: видалити або замінити без втрати даних

Було корисно?

Ось момент: ви запускаєте docker volume rm, очікуючи чистого видалення, а Docker відповідає з насмішкою помилкою: volume is in use. Продакшн чекає. Ваш пейджер нагрівається. І назва тому, який ви хочете видалити, — саме та, про яку команда додатку обіцяла: «він зовсім неиспользується зараз».

Фішка в тому, що Docker зазвичай каже правду — просто не всю правду. Том може бути зайнятий зупиненими контейнерами, покинутими проєктами Compose, завданнями Swarm, драйверами плагінів або застарілим визначенням, про яке ви забули. Цей посібник пояснює, як видалити або замінити Docker том без втрати даних і без класичного «rm -rf і молимося».

Що Docker має на увазі під «volume is in use»

Повідомлення Docker «volume is in use» не поетичне. Це конкретна відмова: Docker не видалить том, якщо будь-який контейнер — працюючий або зупинений — все ще посилається на нього в конфігурації контейнера. Docker відстежує це на рівні метаданих (конфіг контейнера + посилання на точки монтування), а не через перевірку того, чи має якийсь процес файли відкритими прямо зараз.

Ця відмінність важлива. Тому том може бути «зайнятий», навіть коли:

  • Контейнер завершився, але все ще існує.
  • Контейнер був створений Compose місяці тому, і ніхто його не прибрав.
  • Завдання сервісу Swarm все ще зареєстровано, навіть якщо воно перезапускається на іншому вузлі.
  • Контейнер був замінений, але старий залишився як привид з осмисленою назвою на кшталт myapp_web_1.

Також: Docker чітко розділяє томи (керовані Docker, зазвичай під /var/lib/docker/volumes) і bind mounts (шляхами хоста). Bind mount-и не мають того ж механізму «volume is in use»; натомість ви ризикуєте знищити свої дані самостійно.

Ще одна тонкість: помилка зазвичай викликається тим, що docker volume rm відмовляється продовжити. Якщо ви намагаєтеся примусово робити системну очистку, можна видалити більше, ніж планували. Мета тут — цілеспрямоване видалення або заміна з планом міграції даних, який ви зможете пояснити аудитору без потовиділення.

Швидкий план діагностики

Якщо у вас є лише п’ять хвилин до питання «можемо ми просто перезавантажити хост?», зробіть це. У порядку. Не імпровізуйте.

Перший крок: визначте том і його драйвер

  1. Проінспектуйте том (назва, драйвер, мітки, mountpoint).
  2. Якщо драйвер не local, зупиніться і перечитайте двічі: зовнішні драйвери мають іншу семантику й можуть задіяти віддалене зберігання.

Другий крок: знайдіть кожний контейнер, який посилається на нього (працюючі або зупинені)

  1. Перелічіть контейнери, прикріплені до тому через фільтр.
  2. Проінспектуйте підозрілі контейнери і підтвердіть, що список монтувань включає ваш том.

Третій крок: оберіть одну з трьох дій

  • Розблокувати видалення: видаліть контейнер(и), які посилаються на том, а потім видаліть том.
  • Замінити без втрати даних: скопіюйте дані в новий том, потім перепризначте контейнери/Compose на нього.
  • Зберегти, але відв’язати: зупиніть контейнери, оновіть конфігурацію, щоб вони більше не використовували том, і збережіть його як бекап.

Четвертий крок: перевірте безпеку даних

  1. Зробіть знімкоподібний бекап (tar-архів) вмісту тому.
  2. Зробіть базову перевірку цілісності: очікувані файли існують, розміри адекватні, і додаток читає їх після перемонтування.

Тут немає нічого гламурного. І це головне. Гламурні швидкі виправлення — саме ті, про які потім шкодують.

Декілька фактів і історичний контекст (бо це змінює підхід до налагодження)

  • Томи Docker існували рано, але дозріли пізніше. Ранні користувачі Docker часто робили ставку на bind mount-и; іменовані томи стали типовим «безпечним місцем» з розвитком операційних практик.
  • Перевірка «том зайнятий» у Docker керується метаданими. Docker не видалить том, на який посилається будь-яке визначення контейнера, навіть якщо контейнер зупинено й ніхто не має файли відкритими.
  • Мітки Compose змінили спосіб, як томи стають покинутими. Docker Compose додає мітки на кшталт project і service; ви можете використовувати ці мітки, щоб відстежити походження тому, але також й знайти покинуті стекі.
  • Оверлейні файлові системи — це не ваш том. Читально-записний шар контейнера (overlay2) відрізняється від тому. Видалення контейнера видаляє записуваний шар, але не іменовані томи.
  • Swarm ввів розподілене розміщення, а не розподілене зберігання. Люди плутають ці речі. Swarm може пересаджувати завдання; ваш локальний том не телепортується на інший вузол.
  • Драйвери томів мають значення. local поводиться як локальні директорії. Плагінні драйвери (NFS, хмарні диски, інтеграції типу CSI) можуть мати свої правила приєднання/від’єднання та режими помилок.
  • «Анонімні томи» — відома пастка. Вони створюються автоматично (особливо в старих шаблонах Compose) й можуть накопичуватися. Їх важче відстежувати, бо назва виглядає як хеш.
  • Команди prune стали популярними після інцидентів з диском. Тиск на дисковий простір штовхав команди до docker system prune. Це допомагає — поки не видалить щось, що ви вважали тимчасовим.

Золоті правила: як не втратити дані

  1. Вважайте, що том містить важливі дані, поки не доведете протилежне. «Це просто кеш» знищив більше баз даних, ніж шкідливе ПЗ.
  2. Ніколи не видаляйте том, поки не зможете назвати, хто його використовує. Якщо не можете — ви не видаляєте його; ви граєтеся в азартну гру.
  3. Створіть бекап перед міграцією. «Але ми копіюємо» — це не бекап. Копіювання може провалитися по-різному: права доступу, спеціальні файли, зламані симліки, часткові передачі або приховано обрізані файли, якщо файловій системі не вистачає місця.
  4. Надавайте перевагу створенню нового тому і міграції в нього. Заміна на місці спокушає, але позбавляє плану відкату.
  5. Будьте точними з командами очищення. Якщо ви тягнете docker system prune --volumes у продакшні, принаймні напишіть попередній план розслідування. Це зекономить час пізніше.

Жарт #1: Якщо ви коли-небудь відчуваєте себе непотрібним, згадайте, що в продакшні є кнопка «force delete». Вона в основному існує, щоб перевіряти вашу стратегію бекапів.

Одна цитата, бо операції — це дисципліна, а не тільки настрій. У SRE-кругах часто перефразовують ідею Томa Лімончеллі: якщо це не задокументовано і не відпрацьовано, це не процедура; це бажання. — Tom Limoncelli (перефразовано).

Практичні завдання: команди, виводи та рішення

Ось практичні кроки. Кожне завдання дає команду, реалістичний приклад виводу і рішення, яке потрібно прийняти. Виконуйте їх у порядку, коли ви під тиском; вибирайте вибірково під час планового обслуговування.

Завдання 1: Підтвердіть помилку й точну назву тому

cr0x@server:~$ docker volume rm pgdata
Error response from daemon: remove pgdata: volume is in use - [2d8c1f8b6a1d5e2f6f4e62b2d64a0d1b8a5b0a7c7d0f1a2b3c4d5e6f7a8b9c0d]

Що це означає: Docker знайшов принаймні один контейнер, що посилається на том. Довгий hex — це ID контейнера.

Рішення: Не намагайтеся знову. Знайдіть контейнер(и), що посилаються, і вирішіть, видаляти чи мігрувати.

Завдання 2: Інспект тому (драйвер, мітки, mountpoint)

cr0x@server:~$ docker volume inspect pgdata
[
  {
    "CreatedAt": "2025-11-20T09:12:13Z",
    "Driver": "local",
    "Labels": {
      "com.docker.compose.project": "billing",
      "com.docker.compose.volume": "pgdata"
    },
    "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
    "Name": "pgdata",
    "Options": null,
    "Scope": "local"
  }
]

Що це означає: Це локальний іменований том, створений проєктом Compose billing. Це ваша перша зачіпка.

Рішення: Якщо Driver не local, призупиніть дію й ідентифікуйте бекенд зберігання й семантику від’єднання. Якщо local — продовжуйте пошук контейнерів і розгляньте варіанти міграції.

Завдання 3: Знайти контейнери, що посилаються на том (швидкий фільтр)

cr0x@server:~$ docker ps -a --filter volume=pgdata --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
CONTAINER ID   NAMES                 STATUS
2d8c1f8b6a1d   billing-postgres-1    Up 3 days
c91a44dd210e   billing-postgres-old  Exited (0) 2 weeks ago

Що це означає: Два контейнери посилаються на том. Один працює, інший — зупинений, але все ще блокує видалення.

Рішення: Якщо ви хочете видалити том, потрібно видалити обидва контейнери (або принаймні прибрати їхні посилання шляхом видалення контейнерів). Якщо ви замінюєте том, мігруйте дані та відтворіть контейнери з новим томом.

Завдання 4: Підтвердити використання mount всередині конфігурації контейнера

cr0x@server:~$ docker inspect 2d8c1f8b6a1d --format '{{json .Mounts}}'
[{"Type":"volume","Name":"pgdata","Source":"/var/lib/docker/volumes/pgdata/_data","Destination":"/var/lib/postgresql/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]

Що це означає: Контейнер справді використовує pgdata і монтує його у директорію даних PostgreSQL.

Рішення: Розглядайте це як зберігання бази даних. Плануйте контрольовану зупинку, консистентний бекап та міграцію. Не робіть «ковбойських» копій під час запису бази даних.

Завдання 5: Ідентифікувати власника через мітки Compose

cr0x@server:~$ docker inspect 2d8c1f8b6a1d --format '{{json .Config.Labels}}'
{"com.docker.compose.project":"billing","com.docker.compose.service":"postgres","com.docker.compose.version":"2.24.6"}

Що це означає: Це керований Compose, а не випадковий ручний docker run.

Рішення: Віддавайте перевагу змінам через Compose (або принаймні зберігайте стан Compose консистентним), інакше наступний docker compose up може відтворити старі речі й здивувати вас.

Завдання 6: Подивитися, що в томі (швидка перевірка)

cr0x@server:~$ sudo ls -lah /var/lib/docker/volumes/pgdata/_data | head
total 132K
drwx------  19 999 999 4.0K Jan  3 10:11 .
drwx-----x   3 root root 4.0K Nov 20 09:12 ..
-rw-------   1 999 999    3 Jan  3 10:11 PG_VERSION
drwx------   6 999 999 4.0K Jan  3 10:11 base
drwx------   2 999 999 4.0K Jan  3 10:11 global
drwx------   2 999 999 4.0K Jan  3 10:11 pg_wal

Що це означає: Це реальна директорія даних Postgres. Права доступу відповідають типовому UID контейнера. Видалення цього — означає «оновіть своє резюме».

Рішення: Якщо потрібно замінити том, робіть це через контрольований дамп/відновлення бази даних або копіювання на рівні файлової системи під час повної зупинки, залежно від RPO/RTO і типу БД.

Завдання 7: Коректно зупинити потрібний(-ні) контейнер(и)

cr0x@server:~$ docker stop billing-postgres-1
billing-postgres-1

Що це означає: Postgres зупиняється. Якщо контейнер має належний сигнал зупинки, він має контрольовано завершити роботу.

Рішення: Якщо stop зависає, не кидайтеся до docker kill одразу. Спочатку перевірте логи й стан здоров’я. Примусові вбивства підвищують шанс неконсистентного копіювання даних.

Завдання 8: Перевірити, чи немає контейнерів, що ще посилаються на том (включно з exited)

cr0x@server:~$ docker ps -a --filter volume=pgdata --format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
CONTAINER ID   NAMES                 STATUS
2d8c1f8b6a1d   billing-postgres-1    Exited (0) 3 seconds ago
c91a44dd210e   billing-postgres-old  Exited (0) 2 weeks ago

Що це означає: Обидва контейнери все ще існують і посилаються на том.

Рішення: Якщо ви намагаєтеся видалити том, ці контейнери потрібно видалити. Якщо мігруєте, збережіть їх, доки не завершите бекап, потім видаліть або відтворіть з новим томом.

Завдання 9: Створити tar-бекап тому (дешевий страховий захід)

cr0x@server:~$ docker run --rm -v pgdata:/data -v $PWD:/backup alpine sh -c 'cd /data && tar -cpf /backup/pgdata-backup.tar .'

Що це означає: Ви використали тимчасовий контейнер, щоб прочитати том і записати tar у поточну директорію хоста.

Рішення: Перевірте, що tar існує і має правдоподібний розмір. Якщо мало місця, записуйте на змонтований бекап-файловий простір, а не на кореневий диск.

Завдання 10: Перевірити розмір бекапу й базовий вміст

cr0x@server:~$ ls -lh pgdata-backup.tar
-rw-r--r-- 1 cr0x cr0x 1.3G Jan  3 10:19 pgdata-backup.tar

cr0x@server:~$ tar -tf pgdata-backup.tar | head
./
./PG_VERSION
./base/
./global/
./pg_wal/

Що це означає: Tar існує і містить очікувану структуру.

Рішення: Тепер у вас є артефакт відкату. Ще не ідеальний бекап бази даних, але достатній, щоб відновити файли, якщо щось піде не так під час заміни тому.

Завдання 11: Створити новий замісний том

cr0x@server:~$ docker volume create pgdata_v2
pgdata_v2

Що це означає: Новий порожній іменований том створено.

Рішення: Використовуйте зрозуміле версіоноване ім’я. Уникайте імені «new», якщо вам подобається археологія пізніше.

Завдання 12: Скопіювати дані зі старого тому в новий (офлайн-копія)

cr0x@server:~$ docker run --rm -v pgdata:/from -v pgdata_v2:/to alpine sh -c 'cd /from && cp -a . /to'

Що це означає: Копіювання на рівні файлів з cp -a для збереження прав та часових міток.

Рішення: Робіть це тільки для даних, які безпечно копіювати офлайн (контейнер зупинено). Для баз даних віддавайте перевагу дамп/відновленню, якщо вам потрібна гарантія консистентності; копіювання файлів підходить, якщо БД повністю зупинена й не відновлюється після аварії під час копіювання.

Завдання 13: Порівняти швидко структури директорій (шукати очевидні помилки копіювання)

cr0x@server:~$ docker run --rm -v pgdata:/from -v pgdata_v2:/to alpine sh -c 'cd /from && find . -maxdepth 2 -type d | sort | tail -n 5 && echo "---" && cd /to && find . -maxdepth 2 -type d | sort | tail -n 5'
./pg_commit_ts
./pg_dynshmem
./pg_logical
./pg_notify
./pg_stat_tmp
---
./pg_commit_ts
./pg_dynshmem
./pg_logical
./pg_notify
./pg_stat_tmp

Що це означає: Швидко поглянувши, макет директорій відповідає.

Рішення: Продовжуйте, але не плутайте «виглядає схоже» з «бітом у біт консистентно». Для критичних систем робіть перевірки на рівні додатку після перезапуску.

Завдання 14: Видалити старі контейнери, що тримали старий том «використовуваним»

cr0x@server:~$ docker rm billing-postgres-old
billing-postgres-old
cr0x@server:~$ docker rm billing-postgres-1
billing-postgres-1

Що це означає: Контейнери видалені; їхні посилання на том зникли.

Рішення: Тепер ви можете видалити pgdata, якщо дійсно хочете, або зберегти його доти, доки новий налаштування не доведе стабільність.

Завдання 15: Відтворити контейнер (або стек Compose) з новим томом

cr0x@server:~$ docker run -d --name billing-postgres-1 \
  -e POSTGRES_PASSWORD=redacted \
  -v pgdata_v2:/var/lib/postgresql/data \
  postgres:16
8b2fa1d22b2c4b8f8f0e6e7f3a2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d

Що це означає: Новий контейнер запущено з замісним томом.

Рішення: Перевірте здоров’я додатку. Якщо це керується Compose, внесіть зміни до compose.yaml і запускайте через Compose замість ad-hoc docker run для постійності.

Завдання 16: Підтвердити, що новий контейнер використовує новий том

cr0x@server:~$ docker inspect billing-postgres-1 --format '{{range .Mounts}}{{.Name}} -> {{.Destination}}{{"\n"}}{{end}}'
pgdata_v2 -> /var/lib/postgresql/data

Що це означає: Монтування нового контейнера правильне.

Рішення: Лише тепер розгляньте видалення старого тому.

Завдання 17: Видалити старий том (опціонально, після валідації)

cr0x@server:~$ docker volume rm pgdata
pgdata

Що це означає: Docker дозволив видалення, бо жоден контейнер більше не посилається на том.

Рішення: Якщо хочете додаткового захисту — не видаляйте одразу: перейменуйте або тримайте його кілька днів, залежно від політики змін і бюджету дискового простору.

Завдання 18: Пошук анонімних/покинутих томів і аналіз перед prune

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

Що це означає: Та довга хеш-подібна назва часто — анонімний том. Іноді безпечний, іноді це єдина копія директорії завантажень якоїсь служби, бо «тимчасово».

Рішення: Інспектуйте перед prune. Ви не оплачені за сміливість; ви оплачені за правильні рішення.

Безпечна заміна тому (три шаблони)

Шаблон A: «Lift-and-shift» копіювання файлів (добре для статичних даних, обережно з базами)

Це те, що ми робили вище з cp -a через тимчасовий Alpine-контейнер. Це швидко, просто і правильно, коли додаток повністю зупинений і формат даних консистентний в офлайні.

Працює добре для:

  • директорій з веб-контентом
  • статичних бандлів конфігурацій
  • кешів артефактів, які можна відбудувати (але все одно не видаляйте сліпо)
  • баз даних лише коли вони повністю зупинені і ви приймаєте відновлення через crash-recovery

Уникайте для:

  • живих баз даних (копіювання під час роботи) — якщо ви любите пошкоджені WAL-сегменти
  • додатків, що підтримують множинні файли з крос-файловою консистентністю під час роботи

Шаблон B: Міграція на рівні додатку (dump/restore) для сильної консистентності

Якщо вам важливіше коректність, ніж швидкість, робіть експорт/імпорт на рівні додатку. Для PostgreSQL зазвичай це pg_dump / pg_restore або фізичні інструменти бекапу. Для MySQL — mysqldump або фізичні інструменти. Для Elasticsearch — snapshot/restore. Ідея в тому, що ви переносите дані підтримуваним способом, а не копіюєте внутрішні файли двигуна на ходу.

Операційно цей підхід повільніший, але передбачуваний. Це також єдиний спосіб, що дає історію, яку можна захистити під час розбору інциденту: «ми використали підтримуваний метод бекапу».

Шаблон C: Зберегти том, замінити контейнери (коли том не проблема)

Іноді вам взагалі не потрібно замінювати том. Потрібно просто видалити контейнери, які його посилаються, щоб видалити інший том, або перебудувати контейнер додатку, зберігши дані.

У цьому випадку:

  • зупиніть контейнер;
  • видаліть контейнер (не том);
  • відтворіть контейнер, вказавши той же іменований том.

Це найбезпечніший крок, коли дані цінні, а проблема лише в конфігурації контейнера.

Compose і Swarm: особливі пастки

Compose: томи зберігаються, навіть якщо ви вважаєте, що «витягли стек»

docker compose down за замовчуванням видаляє контейнери й мережі, але не видаляє іменовані томи без прапорця --volumes. Це зазвичай добре. Воно також означає, що том лишається й може накопичувати дрейф: оновлення схеми, старі міграції, файли, про які ніхто не пам’ятає.

Compose також допомагає ідентифікувати власників через мітки. Використовуйте їх. Вони існують не для краси.

Swarm: завдання і планування посилюють плутанину

У Swarm завдання сервісу може бути «зупинено», але все ще існувати в історії. Тим часом ваш іменований том існує лише на одному вузлі. Якщо ви думаєте «Swarm перенесе його», ви плутаєте оркестрацію зі зберіганням.

Swarm + локальні томи працює, якщо ви закріплюєте сервіси на вузлах і розумієте failover. Якщо вам потрібна мобільність зберігання — потрібен драйвер/бекенд, який це підтримує (і потребує тестування під відмовами, а не в презентації).

Жарт #2: Swarm охоче пересадить ваш контейнер на вузол, де немає даних. Це не злочин — просто оптимістично.

Поширені помилки (симптом → причина → виправлення)

1) «docker volume rm каже in use, але docker ps нічого не показує»

Симптом: Немає працюючих контейнерів, але том все ще «зайнятий».

Причина: Існують зупинені контейнери, які все ще посилаються на том.

Виправлення: Перелічіть усі контейнери, включно з exited, відфільтровані за томом. Видаліть контейнери (або відтворіть їх з іншим томом).

cr0x@server:~$ docker ps -a --filter volume=pgdata
CONTAINER ID   IMAGE        COMMAND                  CREATED       STATUS                     PORTS     NAMES
c91a44dd210e   postgres:16  "docker-entrypoint.s…"   2 weeks ago   Exited (0) 2 weeks ago               billing-postgres-old

2) «Я видалив контейнер, а Docker все ще не видаляє том»

Симптом: Видалили один контейнер, але блокування лишається.

Причина: Більше ніж один контейнер посилається на том (часто старі/перейменовані/провалені деплоя).

Виправлення: Використайте фільтр за томом і видаліть усі контейнери, що посилаються.

3) «Ми замінили том і додаток стартував… з порожньою базою»

Симптом: Додаток стартує як новий, стан відсутній.

Причина: Новий том змонтовано, але дані так і не мігрували (або були скопійовані в неправильний шлях/том).

Виправлення: Підтвердіть імена mount-ів і місця призначення через docker inspect. Скопіюйте дані офлайн або відновіть з tar-бекапу. Потім перезапустіть.

4) «Копіювання пройшло, але права зламані»

Симптом: Додаток падає з помилками permission denied після міграції.

Причина: Метод копіювання не зберіг власника/права (наприклад, використовували cp без -a, або розпаковували tar як root з неправильними прапорцями, або копіювали через Windows-шлях).

Виправлення: Використовуйте cp -a або tar з збереженням власності. Перевірте очікування UID/GID всередині контейнера.

5) «docker system prune –volumes видалив щось критичне»

Симптом: Втрата даних. Раптове ініціалізування додатку. Злі люди.

Причина: Prune видалив томи, на які зараз ніхто не посилається, навіть якщо вони містили цінний стан або були задумом як бекап.

Виправлення: Не використовуйте prune як скальпель. Перед prune інвентаризуйте томи і промітьте ті, що важливі. Зберігайте явні бекапи поза керуванням Docker томами.

6) «Том локальний, але диск хоста повний»

Симптом: Записи падають, контейнери у цикл перезапуску, міграції провалюються посередині.

Причина: Docker томи розміщені на файловій системі хоста. Якщо root заповнений, усе починає дивно поводитись.

Виправлення: Перевірте використання диска до й після міграції. Перенесіть Docker data root або збільшіть ємність. Не намагайтесь робити великий tarball на майже повному кореневому диску.

Чеклісти / покроковий план

Чекліст A: Безпечно видалити том, який вам точно не потрібен

  1. Інспектуйте том; підтвердіть, що драйвер очікуваний.
  2. Знайдіть усі контейнери, що посилаються на нього (працюючі й зупинені).
  3. Підтвердіть, що вміст не критичний або вже заархівований.
  4. Видаліть контейнери, що посилаються.
  5. Видаліть том.
  6. Перевірте, що додаток працює (бо іноді «не використовується» означає «не використовується зараз»).

Чекліст B: Замінити том без втрати даних

  1. Інспектуйте том і визначте власника стеку (мітки Compose допомагають).
  2. Заплануйте вікно обслуговування, якщо навантаження станне.
  3. Коректно зупиніть додаток/контейнер.
  4. Зробіть tar-бекап старого тому (збережіть десь безпечно).
  5. Створіть новий том з версіонованим ім’ям.
  6. Мігруйте дані (офлайн-копія файлів або відновлення на рівні додатку).
  7. Відтворіть контейнери/стек із новим томом.
  8. Запустіть валідацію: логи, health checks, запити на цілісність даних.
  9. Тримайте старий том протягом grace period, потім видаліть.

Чекліст C: Режим аварійного «треба негайно видалити»

  1. Зупиніть контейнер, що використовує том.
  2. Негайно експортуйте tar-бекап.
  3. Видаліть контейнери, що посилаються.
  4. Тільки потім видаліть том.
  5. Задокументуйте, що ви зробили, поки пам’ять свіжа.

Три корпоративні історії з практики

Інцидент через хибне припущення: «Exited означає не використовується»

Середня SaaS-компанія мала стек білінгу, керований Compose. Команда хотіла звільнити диск на завантаженому Docker-хості. Хтось швидко перевірив: docker ps виглядав чистим. Вони не бачили працюючих контейнерів, що використовують стару назву тому, тож спробували його видалити. Docker сказав «volume is in use». Роздратування, але принаймні він їх зупинив.

Далі пішло гірше: вони вирішили, що Docker помиляється, і виконали ширшу очистку. Видалили відомі контейнери, потім перезапустили prune, щоб «завершити роботу». Том зник — не той, який вони цілеспрямовано ціліли, а інший «не використовуваний», що містив стан побічного сервісу, який виявився єдиною копією кешу звірки платежів.

Аутеж не був драматичним; він був гіршим. Платежі не впали миттєво. Вони покручувалися. Завдання звірки почали видавати невідповідні звіти, що спровокувало аудит з комплаєнсу. Інженерне виправлення зайняло день; відновлення довіри — тижні.

Постмортем був нудно-продуктивний: зупинений контейнер все ще може посилатися на том, і «не використовується працюючими контейнерами» не те саме, що «безпечний для видалення». Вони додали правило: будь-яке видалення тому вимагає переліку посилаючихся контейнерів через docker ps -a --filter volume=... і tar-бекапу перед дією. Ніхто не полюбив зайві кроки. Всі полюбили, що інцидент не повторився.

Оптимізація, що повернулась бумерангом: «Щоденний prune томів»

Інша організація вирішила діяти проактивно щодо тиску на диск. Вони додали нічну роботу з docker system prune -af --volumes. Це працювало тижнями. Графіки диска стали спокійніші. Команда почувалася дорослою.

Потім інженер деплоїв нову версію сервісу через blue/green swap. Старий контейнер був вилучений, новий стартував. Але том з файлами користувачів тимчасово від’єднався під час хореографії деплоя — був короткий проміжок, коли жоден контейнер не посилався на том.

Нічний prune потрапив у це вікно і видалив том із завантаженнями. Наступного ранку сервіс «був здоровий», але тихо віддавав відсутні картинки і відновлював індекси по порожній директорії. Відновлення не було «від бекапу», бо його не було. Відновлення було «сказати клієнтам, що їхні дані зникли», що є чудовим способом дізнатися, як звучить юридичний відділ у конференц-колі.

Урок: prune — це не оптимізація; це політичне рішення. Якщо ви хочете автоматизованого прибирання, маркуйте томи й очищайте за явними критеріями, а не за відсутністю поточних посилань. І ще: якщо ви зберігаєте дані користувачів у Docker-томі — обробляйте їх як базу даних: бекапи, збереження й відпрацювання відновлення.

Нудна, але правильна практика, що врятувала день: «Версійовані томи + вікно відкату»

Компанія в фінансовій сфері тримала Postgres у Docker на кількох виділених хостах. Не трендово, але стабільно. Вони робили оновлення, створюючи новий том для кожної зміни: pgdata_2025q4, pgdata_2025q4_patch1 і так далі. Кожна зміна супроводжувалась tar-бекапом поза хостом і коротким валідаційним скриптом з базовими SQL-перевірками.

Під час одного оновлення контейнер стартував, але запити стали повільніші. Не катастрофічно, але достатньо, щоб спрацювали алерти латентності. Команда запідозрила регресію з боку сховища, можливо опції монтування або тонкий баг ядра. Вони не дебатували годинами. Вони відкотилися.

Відкат був простим: зупинили контейнер, вказали назад на старий том, перезапустили. Старий том все ще існував, бо їхня політика завжди тримала попередній том у визначений період. Видимий інцидент для клієнта був мінімальний.

Після цього вони заспокійливо відлагодили і виявили, що «невинна» зміна в кроці міграції змінила права на піддиректорію, змусивши Postgres працювати менш оптимально. Вони виправили міграцію, ще раз перевірили і пішли далі. Нудна практика — версійовані томи й вікно відкату — зробила саме те, для чого була створена: вона пом’якшила найгірший день.

ЧАВО

1) Чому Docker каже, що том зайнятий, коли контейнер зупинено?

Тому що перевірка Docker базується на посиланнях у конфігурації контейнера, а не на працюючих процесах. Зупинений контейнер все ще «використовує» том у метаданих Docker, поки контейнер не буде видалено.

2) Як знайти контейнер, що використовує мій том?

Використайте фільтр за томом у docker ps -a:

cr0x@server:~$ docker ps -a --filter volume=pgdata
CONTAINER ID   IMAGE        COMMAND                  CREATED       STATUS                 PORTS     NAMES
2d8c1f8b6a1d   postgres:16  "docker-entrypoint.s…"   3 days ago    Up 3 days                        billing-postgres-1

3) Чи можна «від’єднати» том від контейнера без видалення контейнера?

Практично ні. Docker не дозволяє редагувати конфігурацію монтувань контейнера на місці. Потрібно відтворити контейнер з бажаними монтуваннями.

4) Чи безпечно копіювати том бази даних за допомогою cp -a?

Лише якщо база даних повністю зупинена і ви розумієте очікування файлової консистентності двигуна. Для суворих гарантій використовуйте нативні інструменти бекапу та відновлення бази даних.

5) Який найбезпечніший спосіб замінити том у Docker Compose?

Створіть новий іменований том, мігруйте в нього дані, оновіть Compose-файл, щоб посилатися на нову назву тому, потім docker compose up -d. Тримайте старий том для відкату, поки не впевнитеся.

6) Чому у мене томи з довгими випадковими іменами?

Часто це анонімні томи, створені неявно (наприклад, коли image оголошує том, а Compose/run не назвали його явно). Вони накопичуються й важко атрибутуються. Перевіряйте перед видаленням.

7) Що насправді означає «Driver: local»?

Це означає, що Docker зберігає дані на файловій системі хоста під Docker data root. Це не реплікується, не ділиться між вузлами і не захищає від відмови хосту, якщо ви не додали механізми захисту.

8) Чи можна перейменувати Docker том?

Docker не має операції перейменування тому. Стандартний підхід: створити новий том з бажаною назвою, скопіювати дані, потім оновити контейнери/Compose на його використання.

9) Чи безпечний docker volume prune?

Він видаляє томи, на які не посилається жоден контейнер. Це все ще може бути небезпечно, якщо ви зберігаєте бекапи у вигляді відірваних томів або якщо деплой тимчасово від’єднав томи. Використовуйте його лише з політикою й усвідомленням.

Висновок: практичні кроки

Коли Docker каже «volume is in use», він зазвичай захищає вас від вас самих — через те, що том, на який посилається конфігурація контейнера, не можна видалити з-під контейнера. Ваше завдання — знайти ланцюжок посилань, вирішити, видаляєте чи замінюєте, і зробити безпеку даних явною.

Кроки, які ви можете зробити вже сьогодні:

  • Обрати один хост і інвентаризувати томи з мітками та власниками. Невідомі томи — майбутні інциденти.
  • Впровадити стандартний шаблон заміни: новий версіонований том + міграція + валідація + вікно відкату.
  • Написати односторінковий ранобук, що включає: docker volume inspect, пошук контейнерів, tar-бекап і крок затвердження видалення.
  • Перестати трактувати prune як рутинне обслуговування в продакшн, якщо не можете довести, що він не видалить цінні тимчасові томи.

Мета не в тому, щоб ніколи не видаляти томи. Мета — видаляти їх усвідомлено.

← Попередня
Rspamd: первісне налаштування — мінімальна конфігурація, яка реально працює
Наступна →
WordPress «Invalid JSON response»: причини та виправлення, які справді працюють

Залишити коментар