Якщо ви запускаєте docker compose up і бачите повідомлення, що ваш ключ version застарів, ви зіткнулися з типовою операційною проблемою: попередження, яке здається нешкідливим — поки ним не стане. Команди ігнорують його місяцями, а потім оновлення ноутбука, перезапуск CI-ранера або новий продакшен-хост перетворюють це попередження на зламані збірки, змінені мережі або контейнери, які «вчора працювали», а тепер — ні.
Це попередження не про синтаксис. Воно про очікування. Ви не просто редагуєте YAML; ви узгоджуєте поведінку між файлом, CLI Compose, движком та «Compose Specification», що замінила старі версії формату файлів. Давайте модернізуємо без сюрпризів.
Що насправді означає це попередження (і чого воно не означає)
Повідомлення «version is obsolete» найчастіше з’являється, коли ви використовуєте Docker Compose V2 (плагін, команда docker compose) і ваш compose.yaml все ще має заголовок на кшталт version: "2" або version: "3.7". Compose V2 реалізує Compose Specification і більше не потребує цього заголовка для вибору парсера чи режиму поведінки. Насправді рядок може вводити людей в оману, змушуючи думати, що він зафіксовує поведінку. Це не так.
Оперативна реальність така:
- Раніше рядок «version» вибирав набір можливостей файлу (особливо між 2.x і 3.x).
- Compose Spec перейшов на модель можливостей: CLI вирішує, що підтримує; файл описує намір.
- Ваш Compose-файл може поводитися по-різному на різних машинах, але різниця зазвичай не в рядку
version. Певніше за все це версія плагіна Compose, версія движка та середовище.
Отже: видалення version зазвичай безпечне. Небезпека — у припущенні, що все інше в порядку. Міграція — це не просто «видалити рядок», а «перевірити, що семантика не змінилася».
Сухе правило, яке рятує від проблем: ставтеся до Compose як до інструмента розгортання, а не до мови програмування. YAML — це не SLA.
Compose V1 проти V2: чому звичка вас підводить
Compose V1 — це старий Python-інструмент docker-compose. Compose V2 — це плагін на Go, інтегрований у Docker CLI як docker compose. Багато прапорів схожі. Деяка поведінка близька. Критичні випадки — ось де живуть інциденти.
Жарт №1: YAML — це місце, куди відправляється відступ, щоб почати релігійні війни, а Compose просто хоче, щоб ви послідовно вибрали сторону.
Одна цитата (і чому вона застосовна)
Парафраз ідеї — John Allspaw: надійність походить із того, як системи поводяться під навантаженням, а не з нашої впевненості при читанні конфігу.
Це попередження — натяк протестувати поведінку, а не виграти суперечку з лінтером.
Коротка хронологія: чому Compose перестав покладатися на «version»
Невеликий історичний контекст допомагає зробити попередження менш довільним і більше схожим на прибирання спадщини. Ось конкретні факти, що пояснюють траєкторію:
- Файли Compose 2.x і 3.x були не просто «новішими»; вони орієнтувалися на різні сценарії. 3.x узгоджувався з дизайном епохи Swarm (зокрема навколо ключів
deploy). - Секція
deployбула введена для опису оркестраційних намірів, але класичний Compose (не Swarm) ігнорував більшість її параметрів, що роками вводило людей в оману. - Compose V1 (Python) і Compose V2 (плагін на Go) мають різні реалізації, тобто різні крайні випадки навіть при однаковому YAML.
- Docker перемістив Compose у ядро CLI, щоб зменшити розрізненість інструментів,
- Compose Specification стала незалежним стандартом у екосистемі OCI, прагнучи уніфікувати поведінку поза «те, що робить сьогодні Docker».
- Старі заголовки версій стали хаком сумісності: їх використовували щоб обмежувати можливості замість опису намірів. Це неправильний підхід для специфікації.
- Поява функцій на кшталт profiles згодом не вписалася в стару таксономію 2/3; підхід через специфікацію гнучкіший.
- Багато команд використовували Compose як «бідного родственника Kubernetes», що життєздатно, поки ви не почнете змішувати середовища й припускати однакові семантики скрізь.
Попередження — не моральний вирок. Це Compose, що каже: «Не прикидайся, що один рядок керує поведінкою. Це не так».
Принципи безпечної модернізації
1) Спочатку нормалізуйте toolchain, не в кінці
Перед тим як редагувати YAML, зафіксуйте, що ви запускаєте сьогодні: версія Docker Engine, версія плагіна Compose, і точна командна рядок, яку використовують у CI та на продакшені. Якщо вони відрізняються — ваш Compose-файл уже «багатоплатформний», незалежно від намірів.
2) Відрендерений конфіг — це істина
Compose підтримує інтерполяцію змінних, розширення полів, кілька файлів, профілі й значення за замовчуванням. Люди читають YAML. Compose працює з повністю розв’язаною моделлю. Міграція має порівняти відрендерений конфіг до і після змін.
3) «Працює на моєму ноуті» часто пов’язано з правами на томи
Модернізація викликає перебудову й пересоздання контейнерів. Саме тоді власність файлів, SELinux-мітки й драйвери зберігання нагадують про себе. Модернізація Compose — це стільки ж про зберігання, скільки про YAML.
4) Зафіксуйте те, що потрібно фіксувати
Не фіксуйте все (це музей), але фіксуйте те, що змінює поведінку: теги образів (або дайджести), версію плагіна Compose у CI-ранерах і конвенції імен проєктів.
5) Зробіть «up» нудним
Правильний Compose-файл робить docker compose up -d рутинною операцією. Якщо ви покладаєтеся на неявні значення за замовчуванням (мережі, імена контейнерів, build contexts), ваша майбутня версія заплатить відсотки.
План міграції: від «version:» до Compose Spec
Міграція в основному виглядає так: видаліть ключ version, переконайтеся, що ім’я файлу й структура відповідають сучасним очікуванням, валідируйте через docker compose config і підтвердіть поведінку через контрольоване пересоздання.
Крок 1: Перейменуйте й стандартизуйте імена файлів
Сучасний Compose за замовчуванням очікує compose.yaml. Він також читає docker-compose.yml, але стандартизація зменшує питання «чому він вибрав саме цей файл?».
Крок 2: Видаліть рядок version (але на цьому не зупиняйтесь)
Видаліть:
version: "2"version: "3"version: "3.8"(і т.д.)
Залиште все інше. Потім валідируйте відрендерений конфіг. Ваша мета — довести, що модель не змінилася.
Крок 3: Замініть застарілі патерни на сумісні зі специфікацією
Типові приклади:
links: застаріле; сучасний Compose використовує DNS-сервіс за замовчуванням у мережах.depends_onз умовами: старі підходи використовувалиcondition: service_healthy; V2 має часткову підтримку залежно від версій. Віддавайте перевагу явним healthchecks і логіці очікування в entrypoint для критичних залежностей.container_nameповсюди: виглядає охайно, але ламає масштабування і може спричинити конфлікти між проєктами. Користуйтеся вкрай обережно.external_links: зазвичай сигнал про проблеми; моделюйте мережі явно натомість.
Крок 4: Аудит визначень зберігання як серйозну річ
Якщо у вас у Compose працюють бази даних (ймовірно так), ставтеся до томів як до продакшен-структур даних. Явно задавайте імена томів, свідомо обирайте bind mounts і розумійте, де байти зберігаються на диску. Міграція, що пересоздає контейнери, може випадково залишити томи сиротами або створити їх із новими іменами, якщо змінити іменування проєкту.
Крок 5: Доведіть це контрольованим пересозданням
Не «просто запустіть». Дійте як при сухому прогоні: відрендерте конфіг, потягніть образи та пересоздайте в середовищі staging або на копії хоста. Потім порівняйте метадані контейнерів (мережі, монтування, змінні середовища, healthchecks).
Жарт №2: «Я лише змінив попередження» — неофіційний девіз післяінцидентних ретроспектив.
Практичні завдання (команди, виводи, рішення)
Нижче — конкретні виконувані дії, які ви можете зробити на хості або в CI-ранері. Кожна містить (1) команду, (2) що означає вивід, і (3) рішення, яке потрібно прийняти. Це те, що запобігає перетворенню «має бути так само» на шквал тікетів.
Завдання 1: Підтвердіть, що ви використовуєте Compose V2 (плагін), а не V1
cr0x@server:~$ docker compose version
Docker Compose version v2.27.1
Що це означає: Ви запускаєте плагін V2. Попередження «version is obsolete» очікуване, якщо у файлі ще є ключ version:.
Рішення: Перейдіть на стиль Compose Spec (видаліть version) і валідируйте з поведінкою V2.
Завдання 2: Перевірте наявність старого виклику V1 у скриптах
cr0x@server:~$ command -v docker-compose || echo "docker-compose not found"
docker-compose not found
Що це означає: Скрипти, що викликають docker-compose, тут не працюватимуть; на інших машинах вони можуть і працювати. Така невідповідність — ризик при міграції.
Рішення: Стандартизувати автоматизацію на docker compose, або явно встановити/зафіксувати V1 там, де це потрібно (зараз рідко виправдано).
Завдання 3: Зафіксуйте версію Docker Engine (поведінка змінюється)
cr0x@server:~$ docker version --format '{{.Server.Version}}'
26.1.4
Що це означає: Функції движка (опції мережевого драйвера, поведінка build, інтеграція iptables) залежать від версії.
Рішення: Якщо продакшен і CI суттєво відрізняються — вирівняйте версії або тестуйте на найстарішій підтримуваній версії.
Завдання 4: Відрендерте повністю розв’язаний конфіг Compose (базова лінія)
cr0x@server:~$ docker compose -f docker-compose.yml config
name: app
services:
api:
environment:
NODE_ENV: production
image: registry.local/app/api:1.9.2
networks:
default: null
ports:
- mode: ingress
target: 8080
published: "8080"
protocol: tcp
networks:
default:
name: app_default
Що це означає: Це те, що Compose фактично застосує. Вивід включає значення за замовчуванням, інтерпольовані значення й нормалізовані поля.
Рішення: Збережіть цей вивід як ваш «відомо хороший» еталон перед редагуванням.
Завдання 5: Перевірте, що файл розбирається без ключа version
cr0x@server:~$ cp docker-compose.yml compose.yaml
cr0x@server:~$ sed -i '/^version:/d' compose.yaml
cr0x@server:~$ docker compose -f compose.yaml config >/dev/null && echo "config ok"
config ok
Що це означає: Синтаксис валідний під моделлю Compose Spec.
Рішення: Переходьте до семантичного порівняння (наступне завдання). Прохід синтаксичних перевірок необхідний, але недостатній.
Завдання 6: Порівняйте відрендерені конфіги до й після (детектор семантичного дрейфу)
cr0x@server:~$ docker compose -f docker-compose.yml config > /tmp/before.yaml
cr0x@server:~$ docker compose -f compose.yaml config > /tmp/after.yaml
cr0x@server:~$ diff -u /tmp/before.yaml /tmp/after.yaml | head
Що це означає: Відсутність виводу — немає diff у відрендереному конфігу (ідеально). Якщо є diff — читайте його як ревʼю змін: змінні середовища, монтування, мережі й мітки — основні ризикові зони.
Рішення: Якщо є відмінності, або виправте файл, або документуйте, чому зміна очікувана й безпечна.
Завдання 7: Перевірте, чи профілі не змінюють те, що стартує
cr0x@server:~$ docker compose -f compose.yaml config --profiles
default
debug
Що це означає: Є профілі. Запуск docker compose up без --profile може пропустити сервіси. Це може виглядати як «Compose зламався», коли фактично «ви не активували профіль».
Рішення: У продакшен-скриптах явно вказуйте профілі (або уникайте їх у продакшен-файлах).
Завдання 8: Перевірте іменованість проєкту (стабільність імен томів/мереж)
cr0x@server:~$ docker compose -f compose.yaml ls
NAME STATUS CONFIG FILES
app running(3) /srv/app/compose.yaml
Що це означає: Compose використовує «project name» для просторової імені мереж/томів/контейнерів. Зміна імені директорії, імені файлу або використання -p може це змінити.
Рішення: Зафіксуйте іменування проєкту в автоматизації (використовуйте -p app або name: в конфігу), якщо стабільність важлива.
Завдання 9: Проінспектуйте томи, щоб переконатися, що дані не зникнуть
cr0x@server:~$ docker volume ls
DRIVER VOLUME NAME
local app_dbdata
local app_redisdata
Що це означає: Ці томи — самостійні об’єкти, не прив’язані до життєвого циклу контейнера. Вони переживають пересоздання контейнерів, але можуть бути замінені, якщо ви випадково перейменуєте їх (часто через зміну project-name).
Рішення: Якщо імена томів мають префікс проєкту і ви збираєтесь змінювати іменування, явно вкажіть імена томів для стабільності.
Завдання 10: Підтвердіть що монтується куди (пастки прав і SELinux)
cr0x@server:~$ docker inspect app-db-1 --format '{{json .Mounts}}'
[{"Type":"volume","Name":"app_dbdata","Source":"/var/lib/docker/volumes/app_dbdata/_data","Destination":"/var/lib/postgresql/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
Що це означає: База даних пише в том, керований Docker. Режим z вказує на SELinux-мітку (поширено на Fedora/RHEL-похідних хостах).
Рішення: Якщо переходите на bind mounts — опрацюйте власність і SELinux-мітки явно. Якщо залишаєтесь на іменованих томах — зберігайте імена стабільними.
Завдання 11: Виявіть ризик пересоздання контейнерів перед застосуванням змін
cr0x@server:~$ docker compose -f compose.yaml up -d --no-deps --dry-run
service api: would recreate
Що це означає: Compose прогнозує, що пересоздасть контейнери. Саме під час пересоздання ви виявите, чи зберегли ви дані або чи вказали правильний тег образу.
Рішення: Якщо критичні сервіси будуть пересоздані — заплануйте вікно, перевірте монтування томів і підготуйте план відкату.
Завдання 12: Переконайтеся, що образи зафіксовані розумно
cr0x@server:~$ docker compose -f compose.yaml images
CONTAINER REPOSITORY TAG IMAGE ID SIZE
app-api-1 registry.local/app/api 1.9.2 1a2b3c4d5e6f 312MB
Що це означає: Використовується конкретний тег. Якщо ви використовуєте latest, модернізація — слушний час уникнути несподіваних оновлень.
Рішення: Фіксуйте теги, у продакшені — бажано фіксувати дайджести, якщо реєстр це підтримує.
Завдання 13: Перевірте, що healthchecks реально працюють і повідомляють очікувано
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Status}}' | head -n 5
NAMES STATUS
app-api-1 Up 2 minutes (healthy)
app-db-1 Up 2 minutes (healthy)
Що це означає: Healthchecks активні й відзвітували. Якщо сервіси «Up», але ніколи «healthy», ваші припущення про оркестрацію залежностей можуть бути неправильними.
Рішення: Якщо запуск залежить від готовності — впевніться, що є healthchecks і вони поводяться однаково в усіх середовищах.
Завдання 14: Підтвердіть ефективну мережеву та DNS-поведінку
cr0x@server:~$ docker network inspect app_default --format '{{json .IPAM.Config}}'
[{"Subnet":"172.23.0.0/16","Gateway":"172.23.0.1"}]
Що це означає: Compose створив стандартну bridge-мережу. Якщо ви зміните іменування проєкту або опишете мережі інакше, ім’я/підмережа мережі можуть змінитися й порушити жорстко закодовані allowlist-и.
Рішення: Якщо зовнішні компоненти залежать від стабільних підмереж/імён, оголосіть мережу явно з фіксованим іменем (і уникайте хардкодити підмережі, якщо це не потрібно).
Завдання 15: Підтвердіть інтерполяцію змінних оточення та відсутні змінні (тихий підступ)
cr0x@server:~$ docker compose -f compose.yaml config 2>&1 | grep -i warning | head
WARN[0000] The "API_KEY" variable is not set. Defaulting to a blank string.
Що це означає: Compose підставив порожній рядок. Ваш сервіс може стартувати й потім падати у спосіб, що виглядає не пов’язаним з цим.
Рішення: Нехай система валиться швидко: вимагайте необхідні змінні в CI, зберігайте їх у менеджері секретів або надавайте .env з безпечними значеннями лише для розробки.
Завдання 16: Виявлення сирітських контейнерів після рефакторингу
cr0x@server:~$ docker compose -f compose.yaml up -d
[+] Running 3/3
✔ Container app-api-1 Started
✔ Container app-db-1 Started
✔ Container app-redis-1 Started
cr0x@server:~$ docker compose -f compose.yaml ps --all
NAME IMAGE COMMAND SERVICE STATUS
app-api-1 registry.local/app/api:1.9.2 "node server.js" api running
app-db-1 postgres:16 "docker-entrypoint..." db running
app-redis-1 redis:7 "docker-entrypoint..." redis running
Що це означає: Список чистий. Якщо Compose виводить «orphan containers», ви, ймовірно, перейменували сервіси або змінили іменування проєкту й залишили старі контейнери.
Рішення: Якщо бачите сиріт, видаляйте їх свідомо (docker compose down --remove-orphans) після перевірки, що вони не приймають трафік.
Швидкий план діагностики
Коли модернізація Compose йде не за планом, потрібен короткий шлях до вузького місця. Ось порядок, який найшвидше знаходить причину в реальних системах.
По-перше: визначте, який саме toolchain ви запускаєте
- Перевірте
docker compose versionіdocker version. - Підтвердіть, які файли Compose використовуються (явне
-fв автоматизації краще за «те, що в директорії»). - Відрендерте конфіг:
docker compose config. Якщо не вдається відрендерити — нічого іншого не має значення.
По-друге: виявте дрейф імен (project, мережі, томи)
- Перевірте проєкт:
docker compose ls. - Перелічіть томи:
docker volume ls. - Інспектуйте імена мереж:
docker network lsіdocker network inspect.
Якщо імена змінилися, ви могли «втратити» дані просто створенням нових томів під новою просторовою іменою.
По-третє: перевірте зберігання та права, перш ніж шукати баги в додатку
- Інспектуйте монтування контейнера.
- Перевірте логи контейнерів на помилки доступу.
- На хостах з SELinux перевіряйте відмови в операціях після зміни типів монтування.
По-четверте: перевірте порядок запуску й здоров’я
- Статус здоров’я
docker psі лічильники рестартів. - Чи стає БД healthy перед стартом API?
- Якщо використовувався
depends_on, перевірте, чи воно досі виконує ваші припущення.
По-п’яте: лише потім — оптимізація продуктивності
Якщо система стала повільною після модернізації, вимірюйте перед оптимізацією: перевірте затримки файлової системи, поведінку overlay2, CPU-throttling і DNS-різолюцію всередині мережі. Compose не був вузьким місцем, поки ви це не довели.
Типові помилки: симптом → причина → виправлення
1) Симптом: «Після міграції база даних пустіє»
Корінь проблеми: Змінилося іменування проєкту, тож іменований том став іншим об’єктом (наприклад, oldproj_dbdata проти newproj_dbdata). Або ви перейшли з іменованого тому на bind mount без міграції даних.
Виправлення: Явно задайте ім’я тому й збережіть його стабільним. Перевірте відображення томів через docker inspect. Якщо потрібно мігрувати — копіюйте дані зі старого шляху тому в нове місце під час контрольованого вікна технічного обслуговування.
2) Симптом: «Сервіси не можуть дістатися один одного; DNS-запити не проходять»
Корінь проблеми: Ви прибрали links і припустили, що це створювало зв’язність. У сучасному Compose зв’язність забезпечує спільна мережа; DNS-імена походять від імен сервісів.
Виправлення: Розмістіть обидва сервіси в тій самій користувацькій мережі (або у мережі за замовчуванням) і використовуйте DNS-імена сервісів. Якщо потрібна з’єднуваність між проєктами — оголосіть зовнішню мережу й приєднайте до неї обидва проєкти.
3) Симптом: «Попередження зникло, але CI працює інакше, ніж на моєму ноуті»
Корінь проблеми: CI використовує іншу версію плагіна Compose або Docker Engine. Конфіг технічно валідний, але семантика відрізняється (cache build, поведінка pull, таймінги healthcheck-ів, DNS-нюанси).
Виправлення: Зафіксуйте версії toolchain у образах CI. Додайте docker compose version у логи збірки. Трактуйте «дрейф toolchain» як зміну, що потребує ревʼю.
4) Симптом: «Контейнери пересоздаються щоразу, навіть коли нічого не змінювалося»
Корінь проблеми: Недетермінованість у змінних середовища, build-arg або згенерованому конфігу. Або ви змінили мітки й Compose вважає, що визначення сервісу змінилося.
Виправлення: Нормалізуйте джерела змінних оточення. Відрендерте конфіг і порівняйте його між прогонками. Уникайте підставляння timestamp-ів або «рандомних» значень у змінні середовища.
5) Симптом: «Ми використовували depends_on для очікування БД, а тепер додаток падає при старті»
Корінь проблеми: depends_on контролює порядок старту, а не готовність. Старі інструменти давали хибне відчуття безпеки, коли це поєднувалося з умовами або неміцними таймінгами.
Виправлення: Реалізуйте реальні healthchecks і повторні спроби на рівні додатку з backoff-ом. Розгляньте маленький wait-for скрипт як страховку, а не архітектурне рішення.
6) Симптом: «Порти змінилися / трафік потрапляє в неправильний контейнер»
Корінь проблеми: Зміна імені проєкту або імені сервісу змінила імена контейнерів і мітки, які використовує реверс-проксі або агент моніторингу. Іноді видалення container_name виявляє залежність.
Виправлення: Перестаньте орієнтувати маршрутизацію/моніторинг по іменах контейнерів. Використовуйте мітки свідомо (наприклад, для проксі) і зберігайте їх стабільними під час рефакторингу.
7) Симптом: «Після модернізації — Permission denied на bind mounts»
Корінь проблеми: Ви перейшли з іменованих томів (правила Docker) на bind mounts (права хоста), не узгодивши UID/GID або SELinux-мітки.
Виправлення: Узгодьте UID контейнера, задайте власність на шляхах хоста і використайте правильні SELinux-опції монтування де треба. Валідовуйте через логи контейнера й інспект монтувань.
Три корпоративні історії з полів Compose
Міні-історія 1: Інцидент через хибне припущення
Компанія мала «прості» стек Compose: API, Postgres, Redis і сайдкар для логів. Файл docker-compose.yml починався з version: "3.7". Після оновлення Docker Desktop команда побачила попередження і вирішила модернізувати: видалили version і перейменували файл на compose.yaml. Здавалося, прибирання.
Вони розгорнули на маленьку продакшен-VM, скопіювавши директорію. Не вказали -p, а ім’я директорії на VM відрізнялося від staging. Compose створив нове ім’я проєкту на основі директорії і з ним — нові мережі й томи. Postgres стартував швидко. Він був порожнім. API підключився, запустив міграції на свіжій базі і почав обслуговувати запити. Ніхто не помітив, поки клієнт не повідомив про відсутні дані, бо нове середовище виглядало здоровим.
Моніторинг не зреагував. CPU в нормі. Латентність в нормі. Система була «up». Просто вона була направлена на неправильні байти. Згодом хтось виконав docker volume ls і побачив два набори томів з подібними іменами. Старі дані лишилися на місці, прикріплені до старого проєкту.
Виправлення було приземленим: вони зафіксували ім’я проєкту, явно назвали томи і додали крок у рукописі дій для верифікації прикріплення томів перед будь-яким рефакторингом Compose-файлу. Вони також зрозуміли, що «Compose повторно використає мої дані» — це не гарантія, а побічний ефект стабільних імен.
Міні-історія 2: Оптимізація, що відкотилася
Інша організація мала завдання: скоротити час деплою. Їхній стек Compose будував три образи локально на хості через docker compose build і потім запускав їх. Хтось запропонував прискорення: «Перейдемо на bind mounts для всього, щоб рідше rebuild-ити». Вони також торкнулися файлу і видалили version, бо попередження дратувало в логах.
У розробці це спрацювало відмінно. Зміни відображались миттєво. У продакшені це стало повільним фіаско. Контейнери працювали не під root (добре), але директорії на хості були власністю іншого UID/GID (очікувано, але не врегульовано). Сервіси стартували, писали файли, а потім падали на тих каталогах, які не могли створити. Почалися цикли рестартів. Команда спробувала запускати контейнери під root — це вирішило миттєві помилки доступу і створило наступну проблему: тепер хост-шляхи були засмічені файлами власності root, що порушило майбутні запускі під non-root.
Потім прийшов сюрприз із продуктивністю зберігання. Хост працював на файловій системі, налаштованій на надійність, а не на інтенсивну роботу з дрібними файлами. Нові патерни bind mounts збільшили кількість метаоперацій; латентність стрибнула під час пікових записів. «Оптимізація» заощадила час на збірці й витратила десятикратно більше на I/O wait і експлуатаційний хаос.
Вони відкотили продакшен на іменовані томи для stateful-путів, залишивши bind mounts лише для невеликого набору читаних конфігів і статичних артефактів. Також оформили правило: продакшен-зміни, що торкаються семантики зберігання, вимагають окремого огляду шляху даних, а не тільки code review.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
У компанії з фінансовою складовою команда вирішила модернізувати Compose-файли в десятках репозиторіїв. Вони зробили це по-дорослому: єдиний стандартний чекліст міграції плюс CI-job, що відрендерював Compose-конфіг і зберігав його як артефакт. Кожен pull request включав diff виводу docker compose config до й після. Це не було гламурно, але було видимим.
Під час однієї міграції diff показав тонку зміну: ім’я мережі змінилося, бо хтось додав поле name: на верхньому рівні одночасно зі зміною імені директорії в репозиторії. Розробник мав намір покращити консистентність. Відрендерений конфіг показав, що існуючі контейнери будуть пересоздані на нову мережу. Реверс-проксі, що знаходив таргети за членством у мережі, припинив би маршрутизацію під час роллауту.
Оскільки це виявили в CI, вони запланували зміну: попередньо створили цільову мережу, оновили конфіг проксі і виконали роллоут у вікно технічного обслуговування. Міграція пройшла чисто, без чорної діри в трафіку. Ніхто не святкував. У цьому і суть.
Вони зберегли практику: render-and-diff стала частиною definition of done для будь-якої зміни в Compose. Це знову врятувало їх, коли пізніше в одному середовищі змінна оточення дефолтилась у порожній рядок; артефакти відрендереного конфігу зробили відсутнє значення очевидним.
Перевірочні списки / покроковий план
Чекліст A: Безпечна модернізація в одному репозиторії
- Перелік toolchain: зафіксуйте версії Engine + плагіна Compose на dev, CI і prod.
- Відрендерте поточний конфіг і збережіть як базовий артефакт (
docker compose config). - Видаліть
version:і стандартизуйте наcompose.yaml. - Заново відрендерте і порівняйте виводи; усуньте несподівані зміни.
- Зафіксуйте іменування проєкту, якщо томи/мережі мають бути стабільними (
-pабоname:). - Аудит томів: переконайтесь, що stateful-сервіси використовують явно названі томи або свідомі bind mounts.
- Перевірте змінні оточення: немає попереджень про відсутні значення; CI має падати у такому випадку.
- Dry-run пересоздання щоб побачити, що буде замінено, і запланувати час простою, якщо потрібно.
- Розгортання в staging з продакшен-подібними шляхами даних; перевірте монтування, здоров’я і зв’язність.
- Розгортання в prod з планом відкату: старий файл, старе іменування проєкту і відомі прикріплення томів.
Чекліст B: Стандартизація в багатьох репозиторіях (корпоративна реальність)
- Виберіть підтримуваний діапазон версій плагіна Compose і стандартизуйте CI-ранери першими.
- Створіть шаблон CI-job для міграції, який виконує
docker compose configі зберігає артефакти. - Впровадьте політики перевірки: заборона
latestтегів, виявлення відсутніх змінних, прапорcontainer_nameрозростання. - Визначте правила зберігання: які сервіси можуть використовувати bind mounts, які мають використовувати іменовані томи, як працюють резервні копії.
- Зробіть іменування проєкту явним, там де важлива збереженість даних.
- Навчіть людей один раз: короткий внутрішній гайд кращий за племінні знання.
Мінімальний приклад: сучасний стиль Compose Spec (без version)
Не повний туторіал, а шаблон, що уникає поширених пасток: стабільні імена томів, явні мережі і передбачуване іменування.
cr0x@server:~$ cat compose.yaml
name: app
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
volumes:
- dbdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 20
api:
image: registry.local/app/api:1.9.2
depends_on:
- db
environment:
DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD}@db:5432/app
ports:
- "8080:8080"
volumes:
dbdata:
name: app_dbdata
Важливі деталі тут не модні. Вони — стабільні.
Часті питання
1) Чи варто видаляти ключ version?
Так, якщо ви використовуєте Compose V2. Він застарів у тому сенсі, що більше не контролює режим парсингу/функцій. Видаліть його, а потім валідируйте через відрендерені diff-и конфігу.
2) Чи зміниться поведінка після видалення version?
Часто — ні. Іноді — так, але зазвичай опосередковано: модернізація викликає перейменування файлів, зміни імен проєктів або оновлення плагіна Compose. Саме там ховаються зміни поведінки. Доведіть це через docker compose config до і після.
3) Чому Compose все ще приймає version, якщо він застарів?
З міркувань зворотної сумісності. У світі є десятиліття файлів у репозиторіях. Прийняття ключа з попередженням — компроміс між суворістю й практичністю.
4) У чому різниця між версіями файлу Compose 2 і 3?
Історично: 2.x фокусувався на поведінці для одного хоста; 3.x узгоджувався з полями для Swarm. Секція deploy в 3.x вводила в оману багато команд, бо класичний Compose ігнорував більшість її полів. Compose Spec прагне уніфікувати це під єдиною моделлю.
5) Чи потрібно перейменовувати docker-compose.yml у compose.yaml?
Не обов’язково, але стандартизація допомагає. Ризик не в імені файлу; ризик — випадкова зміна імені проєкту і люди, що вгадують який файл використовували. Якщо перейменовуєте — зафіксуйте іменування проєкту й перевірте томи.
6) Як переконатися, що томи не будуть пересоздані?
Явно називайте критичні томи (наприклад, name: app_dbdata) і тримайте іменування проєкту стабільним. Перед деплоєм перелікуйте томи й інспектуйте монтування, щоб підтвердити, що контейнер приєднаний до очікуваного тому.
7) Ми використовуємо depends_on. Чи це надійно?
Воно надійне для порядку старту, але не для готовності. Якщо додаток потребує готовності БД, реалізуйте повторні спроби й healthchecks. Розглядайте depends_on як зручність, а не механізм коректності.
8) Чому CI попереджає про відсутні змінні, а продакшен — ні?
Тому що CI і продакшен завантажують змінні по-різному: .env-файли, оболонка, інжекція секретів або змінні CI. Відрендерте конфіг в обох місцях і порівняйте. Порожні значення за замовчуванням — класичний режим «запустилось, але зламалося».
9) Чи можна під час міграції змішувати кілька Compose-файлів?
Так. Використовуйте шарування -f (base + override). Але пам’ятайте: саме відрендерена модель має значення. Завжди валідируйте через docker compose -f base -f override config і зберігайте цей вивід.
10) Чи варто фіксувати версії Docker і Compose?
Фіксуйте в CI. У продакшені можна або фіксувати, або дотримуватись контрольованого циклу оновлень. Те, чого робити не варто — «будь-яка версія, яка прийшла з образом VM». Це не стратегія; це генератор сюрпризів.
Наступні кроки, які можна зробити сьогодні
Попередження «version is obsolete» — це дешевий сигнал тривоги. Скористайтеся ним, виправте файл і використайте момент, щоб зменшити майбутній дрейф.
- Запустіть
docker compose configна поточному файлі і збережіть вивід. - Видаліть
version:, знову відрендерте конфіг і порівняйте. Мета — відсутність відмінностей; будь-яку відміну поясніть. - Зафіксуйте іменування проєкту, якщо у вас stateful-томи або зовнішні залежності від імен мереж.
- Проаудитьте томи й монтування так, ніби це продакшен-дані (тому це й є).
- Стандартизуйте CI на відомій версії плагіна Compose і логируйте її в кожному пайплайні.
Попередження дратують лише тоді, коли їх ігнорують. Якщо ви сприймаєте їх як привід перевірити поведінку, вони — одні з рідкісних подарунків, які дають вам продакшен-системи безкоштовно.