🐳 Розділ 14 · Питання #7

Що таке Docker Compose

У реальному світі застосунок рідко складається з одного контейнера: бекенду на Java потрібні база даних PostgreSQL, кеш Redis, брокер повідомлень Kafka та панель моніторингу.


🟢 Junior Level

30-секундна відповідь

Docker Compose — це декларативний інструмент для оркестрації та одночасного запуску багатоконтейнерних застосунків (Multi-Container Applications) на одному сервері за допомогою одного файлу конфігурації compose.yaml (або docker-compose.yml).

У реальному світі застосунок рідко складається з одного контейнера: бекенду на Java потрібні база даних PostgreSQL, кеш Redis, брокер повідомлень Kafka та панель моніторингу.

  • Замість того щоб вручну виконувати десятки довгих команд docker run із налаштуванням портів, томів та мереж, ви описуєте всю систему у YAML-файлі.
  • Команда docker compose up -d однією операцією створює віртуальну мережу, виділяє томи та підіймає всі взаємопов’язані сервіси в правильному порядку.

Наочна аналогія з життя

  • Docker CLI (docker run) — це керування окремими музикантами: ви підходите до барабанщика, гітариста та піаніста і кожному окремо кажете, коли починати грати.
  • Docker Compose — це диригент оркестру: за єдиною партитурою (compose.yaml) він помахом палички запускає весь оркестр синхронно, стежить за тим, щоб музиканти чули один одного (мережа) і щоб піаніно було налаштоване до початку симфонії (depends_on).

Наочний приклад для Spring Boot 3 та PostgreSQL

# compose.yaml (у сучасних версіях поле 'version' застаріло і не потрібне)
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      # Сервіси звертаються один до одного за ім'ям сервісу 'postgres-db':
      - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres-db:5432/orders_db
      - SPRING_DATASOURCE_USERNAME=postgres
      - SPRING_DATASOURCE_PASSWORD=secret
    depends_on:
      postgres-db:
        condition: service_healthy # Чекаємо на реальну готовність БД!

  postgres-db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=orders_db
      - POSTGRES_PASSWORD=secret
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pgdata: # Іменований том для збереження даних БД
# Запуск усієї інфраструктури у фоновому режимі:
docker compose up -d

# Перегляд логів бекенду в реальному часі:
docker compose logs -f app

# Повна зупинка всіх сервісів та видалення мереж:
docker compose down

🟡 Middle Level

Внутрішня мережа та вбудований DNS (Embedded DNS)

Коли Docker Compose підіймає сервіси:

  1. Він автоматично створює ізольовану віртуальну мережу типу bridge (за замовчуванням project_default).
  2. Усередині цієї мережі активується вбудований DNS-сервер Docker (доступний за службовою адресою 127.0.0.11).
  3. Імена сервісів у YAML стають доменними іменами: Java-застосунку не потрібно знати IP-адресу бази даних; виклик postgres-db:5432 автоматично резолвиться в поточну внутрішню IP-адресу контейнера бази даних.

ports vs expose

  • ports: ["8080:8080"]: прокидає порт контейнера на фізичний мережевий інтерфейс хост-машини (Host Port Binding). Сервіс стає доступним ззовні контейнерної мережі (наприклад, у браузері розробника за адресою http://localhost:8080).
  • expose: ["5432"]: відкриває порт тільки для інших контейнерів усередині спільної Docker-мережі, не публікуючи його на хості. Це безпечний вибір для баз даних та внутрішніх брокерів, що захищає їх від зовнішніх атак.

Пастка depends_on та правильний Healthcheck

Розробники-початківці часто стикаються з помилкою:

# ❌ НЕПРАВИЛЬНО: Застосунок впаде під час старту!
services:
  app:
    depends_on:
      - postgres-db
  • Чому це ламається: за замовчуванням depends_on гарантує тільки факт запуску процесу контейнера БД.
  • Але СУБД PostgreSQL після старту виконує ініціалізацію файлів та створення баз даних, що займає 3–10 секунд.
  • Застосунок Spring Boot стартує швидше, намагається встановити з’єднання з базою, отримує Connection refused і падає з Application failed to start.
  • Рішення: використовувати condition: service_healthy у поєднанні з директивою healthcheck: test: ["CMD-SHELL", "pg_isready"].

🔴 Senior Level

Інтеграція Spring Boot 3.1+ з Docker Compose (Auto-Configuration)

У Spring Boot 3.1 з’явилася нативна інтеграція з Docker Compose (spring-boot-docker-compose):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-docker-compose</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
  1. Автоматичний запуск: під час виконання команди mvn spring-boot:run в IDE Spring Boot сам знаходить файл compose.yaml у корені проєкту та виконує docker compose up.
  2. Динамічна ін’єкція Connection Details: Spring Boot самостійно парсить відкриті порти Postgres, Redis або Kafka і динамічно інжектує JDBC URL, username та password в ApplicationContext без необхідності ручного налаштування application.yml!
  3. Автоматична зупинка: під час зупинки застосунку в IDE Spring Boot автоматично гасить контейнери.

Мережева сегментація безпеки (Zero Trust Networks)

У Production-середовищі монолітна спільна мережа є ризиком безпеки. Compose дозволяє розділити топологію:

services:
  api-gateway:
    image: nginx:alpine
    ports: ["80:80"]
    networks: [frontend-net]

  order-service:
    image: order-service:1.0
    networks: [frontend-net, backend-net]

  postgres-db:
    image: postgres:16-alpine
    networks: [backend-net] # БД фізично ізольована від Nginx!

networks:
  frontend-net:
  backend-net:

Зловмисник, що скомпрометував вебсервер Nginx, фізично не зможе надіслати пакети в порт 5432 бази даних PostgreSQL.


Горизонтальне масштабування (--scale) та колізія портів

Команда docker compose up --scale order-service=3 дозволяє запустити 3 репліки сервісу.

  • Пастка: якщо в compose.yaml оголошено статичний мапінг ports: ["8080:8080"], запуск масштабування впаде з помилкою Bind for 0.0.0.0:8080 failed: port is already allocated.
  • Рішення для масштабування: вказувати тільки порт контейнера без прив’язки до порту хоста (ports: ["8080"]) для випадкового виділення порту, або використовувати тільки внутрішню мережу з балансувальником навантаження (Nginx/Traefik).

4 Tricky Questions

1. Чому команда docker compose up --scale service=3 падає з помилкою, якщо у сервісу вказано мапінг ports: ["8080:8080"], і як налаштувати балансування?

Відповідь: Мережевий стек операційної системи хоста не дозволяє двом різним процесам зайняти один і той самий TCP-порт (8080) на одному мережевому інтерфейсі (помилка Address already in use). Щоб масштабувати сервіс:

  1. Прибрати мапінг портів на хост або вказати діапазон: ports: ["8080-8082:8080"] (або динамічний порт "8080").
  2. Запустити перед масштабованими сервісами легковажний зворотний проксі (Nginx або Traefik), підключений до тієї ж мережі. Проксі приймає трафік на зовнішній порт 80 і балансує вхідні запити між трьома контейнерами за їхнім внутрішнім DNS-ім’ям сервісу (Docker DNS автоматично розподіляє запити методом Round-Robin).

2. У чому фундаментальна різниця між командами docker compose down та docker compose down -v? До якої катастрофи може призвести випадкове додавання прапорця -v?

Відповідь:

  • docker compose down: зупиняє та видаляє запущені контейнери і створені віртуальні мережі, але залишає недоторканими іменовані томи (Volumes). Дані бази даних на диску зберігаються в повній безпеці.
  • docker compose down -v (або --volumes): разом із контейнерами безповоротно видаляє всі іменовані томи, оголошені в секції volumes:! Якщо виконати цю команду на сервері з робочою базою даних, весь каталог /var/lib/postgresql/data буде миттєво стертий з диска без можливості відновлення.

3. Чому Java-застосунок, запущений локально в IDE на хост-машині (не в Docker), не може підключитися до БД за адресою jdbc:postgresql://postgres-db:5432/mydb, оголошеною в compose.yaml?

Відповідь: Доменне ім’я сервісу postgres-db реєструється тільки у внутрішньому DNS-сервері Docker (127.0.0.11), що працює виключно всередині створеної віртуальної мережі bridge. Операційна система хоста використовує свій власний системний DNS (роутер або провайдер) і не має доступу до DNS-таблиць Docker. Щоб локальний застосунок з IDE підключився до БД:

  1. У секції postgres-db має бути прокинутий порт на хост: ports: ["5432:5432"].
  2. У локальному application.yml адреса має вказувати на хост: jdbc:postgresql://localhost:5432/mydb.

4. Що таке файли оверрайдів (docker-compose.override.yml), і як вони об’єднуються з основним файлом конфігурації?

Відповідь: За замовчуванням під час запуску docker compose up рушій автоматично шукає два файли: основний compose.yaml та файл перевизначень docker-compose.override.yml, виконуючи їхнє глибоке злиття (Deep Merge):

  • Конфігурації об’єднуються: якщо в основному файлі вказано image, а в override вказано ports: ["5432:5432"] та локальний build: ., то підсумковий сервіс об’єднає обидві директиви.
  • Списки (наприклад, ports, environment) конкатенуються.
  • Це дозволяє тримати в репозиторії чистий базовий compose.yaml для тестових стендів, а розробникам локально створювати .override.yml (доданий у .gitignore) для прокидання локальних портів налагодження та підключення вихідних кодів через volume.

🎯 Шпаргалка для інтерв’ю

Головне про Docker Compose

  • Що це: інструмент оркестрації багатоконтейнерних систем на одному хості.
  • Конфігурація: файл compose.yaml (секції: services, networks, volumes).
  • Мережева взаємодія: вбудований DNS (127.0.0.11); контейнери спілкуються за ім’ям сервісу.
  • Синхронізація старту: використовувати depends_on у зв’язці з condition: service_healthy.
  • Spring Boot 3.1+: бібліотека spring-boot-docker-compose автоматично підіймає інфраструктуру під час старту застосунку.
  • Зберігання даних: дані БД зберігати суворо в іменованих volumes:.
  • Обмеження: призначений для одного хоста; не замінює Kubernetes у production (немає multi-node, немає HPA, немає rolling update).

Docker Compose vs Kubernetes

| Критерій | Docker Compose | Kubernetes | | :— | :— | :— | | Середовище застосування | Локальна розробка, прості VPS | Великий продакшен, хмари | | Кількість вузлів | Суворо 1 хост-сервер | Кластер із сотень серверів | | Автомасштабування| ❌ Немає | 🟢 Є (Horizontal Pod Autoscaler) | | Self-Healing | ❌ Обмежений (restart: always) | 🟢 Автоперезапуск та міграція подів | | Складність порогу входу | 🟢 Низька (кілька годин) | 🔴 Висока (тижні навчання) |

Червоні прапорці на співбесіді (Чого говорити не можна)

  • ❌ «depends_on гарантує, що БД повністю готова до прийому запитів» — він чекає тільки старту контейнера; потрібен service_healthy.
  • ❌ «Docker Compose використовується для керування кластером із 10 серверів у продакшені» — Compose працює тільки на одній машині.
  • ❌ «Щоб видалити контейнери, я завжди пишу docker compose down -v» — прапорець -v знищує всі дані баз даних у volumes!

Пов’язані питання