Community
0
HostiServer
2026-09-21 11:18

Docker Compose: кілька сервісів разом

⏱️ Час читання: ~11 хвилин | 📅 Оновлено: вересень 2026

Docker Compose: кілька сервісів разом

У першій статті серії ми навчились запускати один контейнер командою docker run. Реальний застосунок рідко живе сам: до нього додається база даних, кеш, а часто ще й черга повідомлень. Три сервіси — це вже три окремі команди docker run, у кожній до десятка прапорів, плюс мережа, яку треба створити вручну заздалегідь: docker network create app-net. Порядок запуску при цьому важливий, але нічим не гарантований.

Проблема стає особливо відчутною після перезавантаження сервера. Контейнери з restart: unless-stopped піднімаються самі, але Docker не знає, який сервіс має стартувати першим. Якщо застосунок піднявся раніше за базу даних, перший же запит впаде з ECONNREFUSED — і доведеться вручну перезапускати застосунок, коли база вже готова.

Docker Compose вирішує це, змінюючи сам спосіб мислення. Замість послідовності команд ви описуєте бажаний стан у файлі docker-compose.yml: які сервіси мають існувати, як вони пов'язані, що від чого залежить. Порядок запуску, мережі та volumes Compose бере на себе.

Один технічний момент варто зняти одразу. Сучасна команда — docker compose (два слова, плагін на Go, вбудований у Docker Engine з версії 20.10+). Стара docker-compose (через дефіс, окрема утиліта на Python, гілка v1) офіційно зняли з підтримки у травні 2023. Перевірити, яка версія стоїть:

docker compose version

Очікуваний вивід — щось на кшталт Docker Compose version v2.x.x. Якщо команда не знайдена, плагін ставиться окремо: apt install docker-compose-plugin.

2. Структура docker-compose.yml

2.1 Про рядок version:

У більшості старих туторіалів файл починається з version: "3.8". У Compose v2 цей рядок ігнорується з попередженням про застарілість — його можна сміливо прибирати, файл стає коротшим і без зайвого шуму в логах.

2.2 services: — ключ, ім'я і DNS одночасно

Назва кожного сервісу під ключем services: виконує подвійну роль: за замовчуванням з неї складається ім'я контейнера (<проєкт>-<сервіс>-1), і водночас це DNS-ім'я, за яким сервіс видно іншим контейнерам у тій самій мережі. Якщо задати container_name: my-app явно, ім'я стане передбачуваним, але тоді сервіс не можна масштабувати командою docker compose up --scale app=3 — Compose не зможе створити кілька контейнерів з однаковим фіксованим ім'ям.

2.3 image: чи build:

image: postgres:16-alpine завантажує готовий образ з реєстру. build: . збирає образ з Dockerfile у поточній директорії. Якщо Dockerfile лежить не в корені проєкту, вказується явно:

build:
  context: ./app
  dockerfile: Dockerfile.prod

2.4 environment: — два формати запису

Змінні середовища можна писати як map (DATABASE_URL: postgres://...) або як список (- DATABASE_URL=postgres://...). Формат map читається легше при великій кількості змінних. Вираз ${VAR} у будь-якому з форматів підставляється зі значення в .env-файлі чи з shell-оточення в момент запуску Compose.

2.5 ports: — тільки те, що дійсно потрібно ззовні

Формат "3000:3000" означає порт хоста : порт контейнера. Без секції ports: сервіс лишається недоступним ззовні, але доступним іншим сервісам у тій самій мережі Compose. Для бази даних у production ports:, як правило, не потрібен і небажаний — відкривати порт СУБД в інтернет без крайньої необхідності не варто.

2.6 volumes: у сервісі

Коротка форма — pgdata:/var/lib/postgresql/data. Довга форма дозволяє явно вказати тип — type: volume для named volume або type: bind для bind mount. Named volume, використаний у сервісі, має відповідний блок на верхньому рівні файлу: volumes: pgdata: — саме тут Compose бере на себе керування його життєвим циклом.

2.7 networks: — своя мережа за замовчуванням

Без явного оголошення Compose створює мережу з ім'ям <проєкт>_default і підключає до неї всі сервіси. Кастомна мережа задається так: у сервісі — networks: [app-net], і внизу файлу — networks: app-net: (порожній блок означає bridge-мережу з налаштуваннями за замовчуванням).

2.8 restart: — чотири варіанти

Значення Поведінка
no За замовчуванням — не перезапускається взагалі
always Перезапускається завжди, навіть після docker compose stop
unless-stopped Перезапускається, крім випадку, коли зупинений вручну
on-failure Тільки якщо процес завершився з ненульовим кодом

Для production за замовчуванням варто обирати unless-stopped: сервіс переживе перезавантаження хоста, але не буде намагатись піднятись знову, якщо ви зупинили його свідомо.

2.9 command: і entrypoint:

Обидві директиви перевизначають те, що задано в Dockerfile через CMD чи ENTRYPOINT. Типове застосування — окремий dev-режим: command: npm run dev замість production-команди запуску, без потреби тримати два різні Dockerfile.

3. Реальний стек: Node.js + PostgreSQL + Redis

Зведемо все в один робочий приклад — застосунок на Node.js, база даних PostgreSQL і кеш Redis:

services:
  app:
    build: .
    ports: ["3000:3000"]
    env_file: .env
    environment:
      DATABASE_URL: postgres://user:${DB_PASSWORD}@db:5432/mydb
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    networks: [app-net]
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: mydb
    volumes: [pgdata:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
      interval: 5s
      timeout: 5s
      retries: 10
    networks: [app-net]
    restart: unless-stopped

  cache:
    image: redis:7-alpine
    command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
    networks: [app-net]
    restart: unless-stopped

volumes:
  pgdata:

networks:
  app-net:

Стек Docker Compose: app, db і cache в одній мережі app-net з named volume pgdata

3.1 Змінні POSTGRES_*

Офіційний образ postgres зчитує POSTGRES_USER, POSTGRES_PASSWORD і POSTGRES_DB лише один раз — при першій ініціалізації кластера на порожньому volume. При наступних запусках, коли volume вже існує, ці змінні ігноруються. Змінити пароль пізніше зміною змінної не вийде — доведеться виконати ALTER USER всередині вже працюючої бази.

3.2 depends_on з умовою service_healthy

Секція depends_on: db: condition: service_healthy означає, що app не почне стартувати, доки healthcheck бази не поверне успіх. Простий запис depends_on: [db], без умови, чекає лише на старт самого процесу контейнера — а PostgreSQL у цей момент ще може ініціалізувати кластер і не приймати з'єднання. Саме це і призводить до ECONNREFUSED при першому запиті після старту стека.

3.3 Чому retries: 10, а не 5

При interval: 5s і retries: 10 Compose готовий чекати на healthy-стан бази до 50 секунд сумарно. Це не з запасом: перша ініціалізація кластера PostgreSQL сама по собі може займати 10-20 секунд, і при retries: 5 (25 секунд) Compose може позначити базу як unhealthy ще до того, як вона реально піднялась.

Порядок запуску стека Docker Compose: app чекає на healthy-стан db, перш ніж стартувати

3.4 redis:7-alpine і ліміт пам'яті

Тег redis:latest означає, що вміст образу може змінитись у будь-який момент; redis:7-alpine фіксує мажорну версію і важить приблизно 40 MB. Прапор --maxmemory 128mb у command: критично важливий у продакшні: без явного ліміту Redis за замовчуванням намагається використати всю доступну пам'ять хоста, і при активному кешуванні це може призвести до OOM-вбивства сусідніх контейнерів.

3.5 Як сервіси знаходять один одного за іменем

Рядок @db:5432 у DATABASE_URL працює завдяки вбудованому DNS Compose: ім'я сервісу db резолвиться у внутрішню IP-адресу відповідного контейнера. Це працює тільки для сервісів, які перебувають в одній секції networks:, і не працює з хоста — з термінала сервера ім'я db нічого не означає.

4. .env — змінні та секрети

4.1 Автоматичне підхоплення

Файл .env у тій самій директорії, що й docker-compose.yml, підхоплюється Compose автоматично, без додаткових налаштувань:

DB_PASSWORD=supersecret123
NODE_ENV=production

4.2 Два різних механізми — не плутати

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

  • ${DB_PASSWORD} у самому compose-файлі — підстановка відбувається під час парсингу YAML самим Compose; значення потрапляє в конфігурацію, але не автоматично в змінні середовища контейнера.
  • env_file: .env у секції сервісу — файл цілком передається всередину контейнера як змінні середовища, доступні застосунку через, наприклад, process.env.DB_PASSWORD.

4.3 Перевірка підстановки

docker compose config

Ця команда виводить фінальний compose-файл уже після підстановки всіх ${VAR} — зручний спосіб переконатись, що значення з .env дійсно підхопились, ще до запуску стека.

4.4 Пріоритет джерел значень

Від вищого пріоритету до нижчого: змінна shell-оточення → значення з файлу .env → дефолт у самому виразі, якщо він заданий як ${VAR:-default}.

4.5 .gitignore

Файл .env обов'язково додається в .gitignore. Розумний шаблон — *.env і .env*, щоб заодно виключити .env.local і .env.production, які часто заводять пізніше.

4.6 .dockerignore теж

.env так само має бути в .dockerignore — інакше COPY . . у Dockerfile скопіює секрети прямо всередину образу. Перевірити це легко:

docker run --rm my-app cat /app/.env

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

4.7 Права доступу до файлу

chmod 600 .env обмежує читання файлу тільки власником — для одного VPS цього достатньо. Для сценаріїв складніших за один сервер (Swarm, кілька команд з різним рівнем довіри) варто дивитись у бік Docker Secrets або HashiCorp Vault.

5. Мережі: як сервіси знаходять один одного

5.1 Ізоляція за проєктами

Кожен Compose-проєкт — це, як правило, окрема директорія — отримує власну ізольовану мережу. Сервіс app з одного проєкту не бачить db іншого проєкту, навіть якщо обидва запущені на тому самому хості.

5.2 Ім'я проєкту

За замовчуванням ім'я проєкту береться з назви директорії. Його можна задати явно прапором docker compose -p myproject up або рядком name: myproject на верхньому рівні compose-файлу — це впливає на імена контейнерів і мереж, а отже і на те, чи "побачать" один одного два стеки.

5.3 Мінімум відкритих портів

Правило просте: ports: прописується тільки тим сервісам, яким дійсно потрібен доступ ззовні. У прикладі вище це app (3000:3000); db і cache такого блоку не мають. Зайвий рядок "5432:5432" у секції бази даних відкриває PostgreSQL для всього інтернету — це одна з найчастіших причин компрометації бази на VPS.

5.4 Спілкування між різними стеками

Якщо два незалежні Compose-проєкти мають взаємодіяти — наприклад, окремий стек моніторингу, який повинен бачити метрики застосунку, — використовується підключення до вже існуючої мережі:

networks:
  monitoring-net:
    external: true

6. Ключові команди

Команда Що робить
docker compose up -d Запустити всі сервіси у фоні; при першому запуску створює мережі, volumes, тягне образи й збирає build:; при повторному — перезапускає тільки змінені сервіси
docker compose up -d --build Примусово перезібрати образи перед запуском — потрібно після змін у Dockerfile чи коді, інакше Compose бере кешований образ
docker compose up -d --no-deps app Перезапустити тільки app, не чіпаючи db і cache — зручно при деплої нової версії застосунку
docker compose down Зупинити і видалити контейнери й мережі; named volumes лишаються — дані в pgdata збережені
docker compose down -v Те саме, плюс видалення named volumes — незворотно для production: pgdata зникне разом з усіма даними бази
docker compose stop / start Зупинити чи запустити без видалення контейнерів — швидше за down+up, volumes і мережі залишаються
docker compose restart app Перезапустити конкретний сервіс; compose-файл при цьому не перечитується — для застосування змін конфігурації потрібен up -d
docker compose ps Статус усіх сервісів: ім'я, image, стан, порти, health
docker compose logs -f app Follow-логи конкретного сервісу; без імені сервісу — логи всіх одразу з кольоровими префіксами; --tail=100 обмежує вивід останніми рядками
docker compose exec db psql -U user -d mydb Відкрити psql усередині контейнера бази даних для дебагу
docker compose run --rm app npm run migrate Запустити одноразову команду в новому контейнері сервісу app і видалити його одразу після виконання — типово для міграцій БД
docker compose pull && docker compose up -d Оновити образи з реєстру і перезапустити — основний патерн деплою в CI/CD (стаття 4 серії)

7. Висновок

Тепер весь стек — застосунок, база даних, кеш — описаний в одному файлі. git clone плюс docker compose up -d відтворюють ідентичне середовище на будь-якому сервері з Docker за одну-дві хвилини, без жодного ручного кроку і без питання "у якому порядку це запускати".

Лишається кілька речей, які docker-compose.yml сам по собі не вирішує. HTTPS і SSL-сертифікати — стеку потрібен reverse proxy на кшталт Nginx чи Traefik перед ним. Кілька доменів на одній IP-адресі теж вирішується на рівні proxy, а не Compose. Ліміти ресурсів (mem_limit: 512m, cpus: "0.5") поки що відсутні — а без них один сервіс, що вийшов з-під контролю, здатен забрати всю пам'ять сервера в інших. І логи поки що доступні лише через docker compose logs, без ротації і без збереження після видалення контейнера.

Саме ці прогалини — тема наступної статті: перед стеком стає Nginx або Traefik, з'являється сертифікат Let's Encrypt, а автозапуск переходить з ручного docker compose up -d на systemd-юніт, який піднімає стек після перезавантаження сервера сам, без участі людини.

📚 Навігація серії:
Ви читаєте частину 2 з 5 «Docker Compose: кілька сервісів разом».
Попередня: Частина 1. Docker на VPS: встановлення і перший контейнер ←
Далі: Частина 3. Docker у production →

🚀 VPS під ваш Compose-стек

Кілька контейнерів одночасно — це вже не одне-два ядра "про запас": застосунок, база даних і кеш працюють паралельно і кожен тримає власну пам'ять. Hostiserver дає ресурси, розраховані саме на такий стек.

🖥️ Виділені Сервери

  • Від $90/міс, повний контроль над залізом для важчих production-стеків
  • Без сусідів по ресурсах: healthcheck бази не залежить від чужого навантаження
  • 24/7 підтримка: допоможемо з мережами, volumes і систeмним автозапуском стека

💻 Cloud (VPS) Хостинг

  • Від $19.95/міс, KVM-ізоляція, виділені vCPU і RAM
  • NVMe-диски: PostgreSQL і Redis з диском, що не стає вузьким місцем
  • Достатньо RAM з першого тарифу: app + db + cache одночасно без свопу
  • Легко масштабувати: більше пам'яті під новий сервіс у тому самому compose-файлі

💬 Не впевнені, який варіант потрібен?
💬 Напишіть нам — допоможемо з вибором!

Часті запитання

Чим docker compose (v2) відрізняється від docker-compose (v1)?

docker-compose (через дефіс) — окрема утиліта на Python, гілка v1, офіційно знята з підтримки у травні 2023 року. docker compose (два слова) — плагін на Go, вбудований у Docker Engine, починаючи з версії 20.10+, і саме він розвивається зараз. Синтаксис compose-файлу практично не змінився, різниця переважно в самій команді запуску та швидкості роботи.

Застосунок падає з ECONNREFUSED одразу після старту — що не так?

Найчастіша причина — простий depends_on: [db] без умови: Compose чекає лише на старт процесу контейнера бази даних, а не на її готовність приймати з'єднання. Рішення — depends_on: db: condition: service_healthy разом із налаштованим healthcheck: у самому сервісі бази даних.

Чи потрібен рядок version: у сучасному compose-файлі?

Ні. У Compose v2 він ігнорується з попередженням про застарілість і не впливає на роботу файлу. Його можна прибрати з будь-якого проєкту без наслідків — файл стане коротшим.

docker compose down чи down -v — у чому різниця?

down видаляє контейнери й мережі, але лишає named volumes — дані бази даних збережені. down -v додатково видаляє й volumes, а разом з ними всі дані всередині. На production-сервері -v варто застосовувати тільки свідомо і після перевірки, що бекап дійсно є.

Як передати пароль бази даних, не зберігаючи його в git?

Пароль виноситься у файл .env поруч із docker-compose.yml, а сам файл додається в .gitignore (і в .dockerignore, щоб не потрапити в образ). У compose-файлі значення підставляється через ${DB_PASSWORD}. Для сценаріїв з кількома серверами чи командами варто розглянути Docker Secrets або зовнішнє сховище на кшталт HashiCorp Vault.

Чи можна відкрити порт бази даних назовні для дебагу?

Технічно можна, додавши ports: ["5432:5432"], але для production це небажано — порт стає доступним усьому інтернету. Для дебагу безпечніший шлях — docker compose exec db psql -U user -d mydb, який відкриває сесію psql прямо всередині контейнера без відкриття порту назовні, або тимчасовий SSH-тунель на час роботи.

Скільки пам'яті виділяти кожному сервісу?

У базовому docker-compose.yml без явних лімітів кожен сервіс може використати всю доступну пам'ять хоста — саме тому Redis у прикладі вище запущений з --maxmemory 128mb. Для системних лімітів на рівні Compose використовуються mem_limit і cpus у кожному сервісі; докладніше про це — у наступній статті серії, присвяченій production-налаштуванням.

Contents

Поділіться цією статтею

VPS з підтримкою від

$19 95 / міс

Виділені сервери від

$80 / міс

CDN починаючи від

$0 / міс

 

Користуючись цим сайтом, ви погоджуєтеся на використання файлів cookies відповідно до нашої Політики Конфіденційності.