Docker Compose – це спосіб описати кілька контейнерів, мережі й томи в одному YAML-файлі. Для homelab це базовий інструмент: один файл тримає весь стек, а помилки в ньому ламають запуск одразу. Нижче – структура compose-файлу, де найчастіше сиплеться конфіг, і як перевіряти його до запуску.
Головна проблема не в самому Docker Compose, а в тому, що файл виглядає простим. Поки сервіс один, це майже не відчувається. Коли з’являються reverse proxy, база, volumes, змінні середовища і кілька мереж, дрібна помилка в YAML або в логіці зв’язків дає дуже неочевидний збій.
Нормальний compose-файл не просто “заводиться”. Він має читатися, перевірятися командою, переживати оновлення образів і не перетворюватися на випадковий набір параметрів. Саме тут Docker Compose або допомагає тримати стек під контролем, або стає джерелом хаосу.
З чого реально складається compose-файл
У практичному сенсі структура Docker Compose зводиться до чотирьох сутностей: сервіси, томи, мережі та змінні. Майже все, що запускається, описується в блоці services. Інше – це або залежності сервісів, або спосіб зберегти стан між перезапусками.
Мінімальний робочий файл зазвичай має один або кілька сервісів. Усередині кожного сервісу описують образ, ім’я контейнера, порти, volumes, environment, restart policy та мережу. Якщо файл читається лише автором через два дні після написання, структура вже слабка.
services:
app:
image: nginx:alpine
container_name: demo-nginx
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
restart: unless-stopped
networks:
- frontend
networks:
frontend:
volumes:
Цього досить, щоб побачити базову схему. У Docker Compose блок services описує, що запускати. networks визначає, як сервіси бачать один одного. volumes відокремлює дані від життєвого циклу контейнера. Якщо ці речі не розділені в голові, файл швидко обростає випадковими рішеннями.
Окремий нюанс – назва сервісу важливіша, ніж container_name. Усередині однієї compose-мережі сервіси звертаються один до одного саме за ім’ям сервісу. Через це ручне навішування container_name часто лише ускладнює міграцію або масштабування.
Як будувати Docker Compose без хаосу
Щоб Docker Compose не перетворився на смітник параметрів, файл треба збирати від зв’язків між сервісами, а не від списку опцій. Спочатку визначають, які контейнери є stateful, які порти мають бути зовні, а що повинно жити лише у внутрішній мережі. Потім уже додають volumes, environment і restart policy.
- Описати сервіси за ролями: frontend, app, db, proxy, worker.
- Виводити назовні тільки ті порти, які реально потрібні хосту або клієнтам.
- Для стану використовувати volumes, а не запис у filesystem контейнера.
- Розділяти зовнішню і внутрішню мережу, якщо сервісів більше двох-трьох.
- Тримати змінні середовища в передбачуваному місці, а не розкидати по файлах.
У домашній інфраструктурі типова помилка – пробросити назовні все підряд. База даних, Redis, admin-панель і backend часто отримують відкриті порти просто “щоб працювало”. На одному вузлі це ще терпимо, але після появи reverse proxy така схема лише роздуває поверхню атаки і ускладнює налагодження.
Ще один робочий принцип: якщо параметр потрібен тільки контейнеру, він має жити в сервісі. Якщо параметр описує спільний ресурс, його краще винести на верхній рівень. Інакше Docker Compose-файл втрачає межі відповідальності, і зміна одного сервісу ламає сусідні.
Де ламається YAML, хоча синтаксис наче правильний
Найнеприємніші помилки в Docker Compose часто не в Docker, а в YAML. Файл може виглядати правильно, але відступ, тип значення або неочевидне перетворення рядка дають іншу структуру, ніж очікувалося. У результаті команда проходить парсинг, а сервіс поводиться дивно.
Типові збої тут доволі банальні:
- змішані пробіли й таби;
- неправильний рівень відступу в services або volumes;
- рядок без лапок, який YAML трактує не так, як очікувалося;
- список, випадково записаний як словник;
- дублі ключів, де пізніше значення тихо перекриває попереднє.
На практиці особливо підступні environment і labels. Їх можна писати у двох формах, і змішування стилів у великому файлі робить конфіг крихким. Коли стек розростається, краще тримати один стиль запису в усьому файлі, навіть якщо обидва формально валідні.
services:
app:
environment:
TZ: Europe/Kyiv
APP_ENV: production
Або так:
services:
app:
environment:
- TZ=Europe/Kyiv
- APP_ENV=production
Обидва варіанти працюють. Але в одному файлі краще не стрибати між ними. Інакше під час правок легше пропустити зайвий пробіл, двокрапку або неправильний відступ.
Типові помилки Docker Compose, які з’являються в homelab
Найчастіше Docker Compose ламається не через складність, а через дрібні логічні помилки: неправильні шляхи до volumes, плутанину між expose і ports, залежність сервісів без реальної перевірки готовності та невірне уявлення про мережу між контейнерами. У homelab це повторюється постійно.
Неправильні шляхи до bind mounts
Локальний шлях у volumes перевіряють рідко. Якщо каталог на хості відсутній або має не ті права, контейнер стартує, але застосунок всередині бачить порожню директорію або не може записати дані. Особливо часто це б’є по SQLite, конфіг-файлах і каталогах з медіа.
services:
app:
volumes:
- ./config:/config
- ./data:/data
Проблема тут у відносних шляхах. Вони прив’язані до каталогу, з якого запускається Docker Compose. Якщо стек запускають через automation, systemd або з іншого робочого каталогу, поведінка стає непередбачуваною. Для критичних сервісів краще або жорстко контролювати робочий каталог, або використовувати named volumes там, де це допустимо.
Плутанина між ports і внутрішньою мережею
Сервісам у межах однієї мережі Docker Compose не потрібен проброс портів назовні, щоб бачити один одного. Вони ходять по внутрішньому DNS за ім’ям сервісу. Коли база доступна тільки app-контейнеру, відкривати її порт на хості просто не треба.
Це одна з найтиповіших помилок у self-hosted стеках. Відкрита назовні база чи кеш часто не потрібні взагалі. Далі з’являються конфлікти портів, зайві правила firewall і хибне відчуття, що без ports контейнер “не працює”.
depends_on не означає готовність сервісу
Docker Compose може запустити сервіси в певному порядку, але це не гарантує, що база або інший бекенд уже готові приймати з’єднання. Контейнер app стартує, одразу йде в database, отримує відмову і падає. Потім починається полювання на фантомні проблеми в мережі.
Це старий і дуже живучий підводний камінь. Для реальних стеків порядок запуску сам по собі слабкий. Потрібні або healthchecks, або логіка retry на рівні застосунку. Якщо сервіс критично залежить від БД, Docker Compose без перевірки готовності дає лише половину рішення.
container_name там, де він не потрібен
Фіксоване ім’я контейнера виглядає охайно, але часто більше заважає, ніж допомагає. Воно ускладнює дублювання середовищ, ламає одночасний запуск схожих стеків і збиває з пантелику, коли очікується робота через назви сервісів. Для більшості homelab-сценаріїв достатньо імені сервісу.
До малого масштабу це ще не болить. Коли на одному хості з’являються staging, test і production-подібні стеки, ручні container_name починають конфліктувати першими.
Як перевірити Docker Compose до запуску контейнерів
Найкраща звичка при роботі з Docker Compose – валідовувати файл до реального запуску. Це дозволяє спіймати синтаксичні проблеми, побачити фінальну конфігурацію після підстановки змінних і швидко відловити помилки в структурі. Перевірка займає секунди, але економить години на пошук дивної поведінки.
Базовий набір команд виглядає так:
docker compose config docker compose up -d docker compose ps docker compose logs --tail=100
Команда
docker compose config
особливо корисна. Вона показує, у що Docker Compose перетворив YAML після обробки. Саме тут часто видно, що змінна не підставилася, мережа названа не так, або volumes вийшли не тієї структури.
Після старту варто перевірити не лише статус контейнерів, а й зв’язність між сервісами. Якщо app має ходити в db, факт запущеного контейнера ще нічого не гарантує. Контейнер може бути в статусі running, але сервіс всередині вже впав або завис на ініціалізації.
Коли один compose-файл уже не тягне
Один Docker Compose-файл добре працює для компактного стека. Але коли в ньому з’являються десятки сервісів, профілі, кілька мереж, проксі, бекграундні воркери й окремі override-сценарії, читабельність падає швидше, ніж здається. Формально все ще працює, практично – файл стає крихким.
Межа тут не в кількості рядків, а в щільності зв’язків. Якщо зміна environment в одному сервісі змушує перевіряти пів файла, час розбивати конфіг. Частину сервісів варто винести в окремі стеки: наприклад, reverse proxy окремо, моніторинг окремо, додаток з базою окремо.
Є і менш очевидний момент. Docker Compose добре описує локально пов’язані сервіси, але не замінює інвентаризацію, секрет-менеджмент і нормальний деплой-процес. До певного масштабу цього вистачає. Далі підтримка починає коштувати дорожче за саму простоту YAML.
Офіційна документація Docker Compose доступна на docs.docker.com/compose. Для перевірки специфіки опцій краще звірятися саме з нею, а не з випадковими прикладами з форумів.
Що тримає compose-файл у робочому стані
Сильний Docker Compose-файл – це не список усіх доступних параметрів, а зрозуміла схема сервісів. Менше відкритих портів, акуратна робота з volumes, перевірка через
docker compose config
і відмова від зайвого
container_name
закривають більшість типових збоїв ще до першого запуску.
Для homelab цього часто достатньо надовго. Коли стек починає рости, головна загроза вже не в Docker Compose як такому, а в спокусі тримати всю інфраструктуру в одному файлі. На цьому етапі краще різати конфіг по межах відповідальності, інакше навіть дрібне оновлення перетворюється на ручне розмінування.