Docker и Docker Compose — професионално ръководство

Част 7: Docker Compose

Docker Compose превръща групата от отделни контейнери в описана, възпроизводима система. Тук показвам как подреждам services, networks, volumes и настройки така, че целият stack да може да бъде създаден, спрян, обновен и възстановен без ръчно разцъкване на десетки команди.

Docker Compose не е просто съкратен вариант на docker run. Това е начин да опиша цялото приложение като конфигурация: services, networks, volumes и настройки. Целта е stack-ът да бъде възпроизводим и предвидим.

1. Защо ми трябва Docker Compose?

Един container е лесен за управление. Проблемът започва, когато приложението има web server, database, cache, worker и още няколко services. Тогава ръчното стартиране с поредица от docker run команди бързо се превръща в рецепта за грешки.

Compose решава това, като описва желаното състояние на целия stack в YAML файл. Вместо да помня десетки команди, поддържам една декларативна конфигурация.

Compose

описание на stack-а

Services

контейнери

Networks + Volumes

връзка и постоянни данни

2. Compose файлът

Съвременният Docker Compose използва Compose Specification. Обикновено файлът се нарича compose.yaml или compose.yml. Не добавям старото version:, когато няма конкретна причина да го правя.

compose.yaml
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"

Тук имам един service с име web. Compose създава container от посочения image и публикува порт 8080 на host към порт 80 в container.

Внимание с YAML. Indentation-ът е част от синтаксиса. Един грешен интервал може да направи конфигурацията невалидна. Използвам spaces, не tab.

3. Services — сърцето на Compose

Всеки запис под services: описва отделен service. Например web и database могат да бъдат управлявани като един stack.

compose.yaml
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"

  db:
    image: mariadb:11
    environment:
      MARIADB_DATABASE: app
      MARIADB_USER: app
      MARIADB_PASSWORD: change-me
      MARIADB_ROOT_PASSWORD: change-me-too

4. Networking между services

Compose създава network за проекта, ако не съм задал друга конфигурация. Services в една Compose network се намират по service name.

ПРИМЕР
services:
  app:
    image: myapp:latest

  db:
    image: mariadb:11

Приложението се свързва с database-а чрез hostname db, а не чрез IP адрес на container-а. Това е важно, защото IP адресите могат да се променят при recreate.

Правилото ми е просто: между services използвам името на service-а, когато са в обща Compose network.

5. Volumes — данните трябва да преживяват container-а

Container-ът е заменим. Данните не трябва да бъдат. За database използвам volume или друг подходящ persistent storage.

compose.yaml
services:
  db:
    image: mariadb:11
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:
Volume не е backup. Ако host-ът или дисковата система се повреди, volume-ът също може да бъде загубен. Backup стратегията е отделна задача.

6. Environment Variables и .env

За настройки, които се променят между среди, използвам environment variables. Това позволява Compose файлът да остане един и същ.

.env
DB_NAME=app
DB_USER=app
DB_PASSWORD=change-me
DB_ROOT_PASSWORD=change-me-too
compose.yaml
services:
  db:
    image: mariadb:11
    environment:
      MARIADB_DATABASE: ${DB_NAME}
      MARIADB_USER: ${DB_USER}
      MARIADB_PASSWORD: ${DB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
.env не е secrets manager. Ако съдържа реални пароли, не го качвам в Git repository и го защитавам с подходящи permissions.

7. Основните Compose команди

TERMINAL
# Стартиране на stack-а във фонов режим
docker compose up -d

# Състояние на services
docker compose ps

# Логове
docker compose logs

# Логове само за конкретен service
docker compose logs -f web

# Спиране
docker compose stop

# Стартиране отново
docker compose start

# Спиране и премахване на containers и networks
docker compose down

Разликата между stop и down е важна. stop спира containers. down премахва създадените от Compose containers и networks. Named volumes не се премахват от обикновеното down.

8. Какво става при промяна?

Когато променя image, ports, environment или друга част от конфигурацията, Compose може да пресъздаде container-а.

TERMINAL
# Прилагане на промените
docker compose up -d

# При необходимост — принудително пресъздаване
docker compose up -d --force-recreate

Това е още една причина данните да бъдат във Volumes, а не само във writable layer-а на container-а.

9. depends_on и Healthcheck

Фактът, че database container-ът е стартиран, не означава непременно, че database server-ът вече приема connections. При реални приложения това често е източник на проблеми.

ПРИМЕР
services:
  db:
    image: mariadb:11
    healthcheck:
      test: ["CMD-SHELL", "healthcheck.sh --connect --innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    image: myapp:latest
    depends_on:
      db:
        condition: service_healthy

Така зависимостта е свързана с реалното здравословно състояние на service-а, а не само с факта, че container-ът съществува.

10. Profiles

Не всеки service трябва да работи постоянно. Profiles са удобни за инструменти, тестове и административни services.

compose.yaml
services:
  app:
    image: myapp:latest

  adminer:
    image: adminer:latest
    profiles:
      - tools
TERMINAL
# Само основният stack
docker compose up -d

# С включен profile
docker compose --profile tools up -d

11. Как подреждам Compose проекта

Не оставям конфигурацията, данните и backup-ите в една и съща бъркотия. Подреждам проекта така, че след време да мога да разбера какво съм направил.

ПРИМЕРНА СТРУКТУРА
my-stack/
├── compose.yaml
├── .env
├── .gitignore
├── config/
│   └── app.conf
├── data/
└── backup/

Това е пример, не задължителна схема. Конкретната структура зависи от приложението и начина, по който съхранявам данните.

12. Compose в Production

ОбластКакво проверявам
ImagesИзползвам контролирани версии и не разчитам сляпо на latest.
DataКритичните данни са в подходящо persistent storage.
BackupИма отделен backup процес и периодично проверявам restore.
SecretsПаролите не се разнасят в repository и публични конфигурации.
NetworkingПубликувам само портовете, които действително трябва да са достъпни.
UpdatesИмам предвидим начин за обновяване и възстановяване.

13. Един реален малък Stack

Следващият пример събира основните идеи в една конфигурация: web service, database, отделни networks и persistent volume.

compose.yaml
services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"
    networks:
      - frontend
    volumes:
      - ./html:/usr/share/nginx/html:ro

  db:
    image: mariadb:11
    environment:
      MARIADB_DATABASE: app
      MARIADB_USER: app
      MARIADB_PASSWORD: ${DB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  db_data:

В реална application архитектура бих поставил application service между web и database. Идеята е database-ът да не бъде директно изложен към Internet.

14. Диагностика

Когато stack-ът не работи, не започвам с произволни рестартирания. Първо проверявам състоянието, конфигурацията и логовете.

TERMINAL
# Състояние
docker compose ps

# Последните логове
docker compose logs --tail=100

# Логове на конкретен service
docker compose logs --tail=100 -f app

# Проверка на обработената конфигурация
docker compose config

# Networks
docker network ls

# Volumes
docker volume ls

Особено полезна е docker compose config. Тя ми показва как Compose интерпретира конфигурацията и environment variables, преди да започна да гадая.

15. Заключение

Docker Compose е моментът, в който Docker започва да се превръща от набор контейнери в управляема инфраструктура. Services, networks, volumes и configuration вече са описани на едно място.

За мен най-важното предимство не е удобството на една команда. То е предвидимостта. Знам какво трябва да има, как трябва да бъде свързано и какво трябва да остане след пресъздаване на containers.

Следващата стъпка е реалният production stack. След като имам Images, Containers, Networking, Volumes и Compose, мога да събера всичко това в една цялостна инфраструктура и да покажа как я поддържам, обновявам и диагностицирам.