Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «Compose для розробки»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

5 питань

Docker Compose описує багатоконтейнерне оточення одним файлом: сервіси, мережі, томи, секрети. Одна команда docker compose up піднімає все разом.

# compose.yaml
services:
  app:
    build: .
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      DB_HOST: postgres
    depends_on:
      postgres:
        condition: service_healthy
    volumes:
      - .:/var/www/html

  postgres:
    image: postgres:18
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 5s

volumes:
  pgdata:

Основні розділи верхнього рівня:

  • services - контейнери з образом чи інструкціями збирання, портами, змінними, томами, залежностями;
  • volumes - іменовані томи для даних, що мають переживати перестворення контейнерів;
  • networks - мережі (за замовчуванням створюється одна для всього проєкту);
  • secrets, configs - файли конфігурації й секретів;
  • name - назва проєкту (префікс для контейнерів, мереж, томів).

Чому без version: колись файли починалися з version: "3.8", і версія визначала доступні можливості. Тепер Compose реалізує єдину Compose Specification - поле version застаріле й ігнорується. Сучасний Compose виводить попередження: «the attribute version is obsolete». Його можна просто видалити.

Назва файлу: рекомендована - compose.yaml (також підтримуються compose.yml і старі docker-compose.yml). Команда - docker compose (вбудований плагін, Compose v2), а не окрема програма docker-compose першої версії.

Корисні команди:

docker compose up -d            # запустити у фоні
docker compose ps               # стан сервісів
docker compose logs -f app      # логи сервісу
docker compose down             # зупинити й видалити контейнери й мережі (томи лишаються)
docker compose config           # підсумкова конфігурація після підстановки змінних і злиття файлів

docker compose config - найкорисніша команда для налагодження: показує, що Compose реально «бачить».

Докладніше в документації: Специфікація Compose

Тут дві різні речі, які часто плутають: змінні для самого файлу Compose і змінні всередині контейнера.

1. Підстановка (interpolation) - Compose заміняє ${...} у compose.yaml до запуску контейнерів. Значення беруться з оточення shell і з файлу .env поруч із compose.yaml:

services:
  app:
    image: myapp:${APP_VERSION:-latest}       # за замовчуванням latest
    ports:
      - "${APP_PORT:-8000}:8000"
    environment:
      DB_HOST: ${DB_HOST:?DB_HOST is required} # помилка, якщо не задано
  • ${VAR:-default} - значення за замовчуванням, якщо змінна порожня чи не задана;
  • ${VAR:?повідомлення} - зупинити з помилкою, якщо не задана;
  • $$ - буквальний знак долара.

2. Змінні контейнера - потрапляють в оточення процесу всередині:

services:
  app:
    environment:            # явно в compose.yaml
      APP_ENV: local
    env_file:               # з файлу - усі змінні файлу йдуть у контейнер
      - .env.docker

Ключова різниця: .env поруч із compose.yaml не передається в контейнер автоматично - він лише використовується для підстановки ${...}. А env_file передає змінні в контейнер, але не бере участі в підстановці.

Плутанина з Laravel: у Laravel-проєкті .env - це ще й файл конфігурації застосунку. Laravel Sail використовує це: той самий .env і для підстановки в compose.yaml (${APP_PORT}, ${FORWARD_DB_PORT}), і для застосунку, який читає його з каталогу проєкту через bind mount.

Пріоритет змінних контейнера (від вищого): docker compose run -e, environment у файлі, env_file, ENV в образі.

Перевірка: docker compose config показує файл після підстановки - видно, яке значення реально потрапило.

Безпека: .env з секретами - у .gitignore. Для продакшену секрети краще передавати механізмом секретів, а не змінними (їх видно в docker inspect).

Докладніше в документації: Підстановка змінних у Compose

Обидві команди виконують команду «в сервісі», але по-різному.

docker compose exec - виконує команду в уже запущеному контейнері сервісу:

docker compose exec app php artisan migrate
docker compose exec app sh                       # оболонка в працюючому контейнері
docker compose exec -u root app apk add htop     # від іншого користувача
  • контейнер має бути запущений;
  • команда бачить той самий стан: файли, процеси, змінні оточення, з'єднання;
  • після завершення контейнер продовжує працювати.

docker compose run - створює новий тимчасовий контейнер з конфігурації сервісу й виконує в ньому команду:

docker compose run --rm app composer install
docker compose run --rm app php artisan test
docker compose run --rm --no-deps node npm run build
  • працює, навіть якщо сервіс не запущено;
  • за замовчуванням запускає залежності (depends_on) - --no-deps вимикає це;
  • не публікує порти сервісу (щоб не конфліктувати із запущеним), якщо не вказати --service-ports;
  • --rm - видалити контейнер після завершення, інакше накопичуються зупинені контейнери.

Коли що:

Задача Команда
міграції, tinker, черга в робочому оточенні exec
подивитися, що відбувається в працюючому контейнері exec
одноразова задача без запущеного сервісу (встановлення залежностей, тести в CI) run --rm
команда в чистому оточенні, щоб не зачіпати запущений контейнер run --rm

Laravel Sail обгортає саме ці команди: sail artisan migrate - це docker compose exec laravel.test php artisan migrate, а sail shell - оболонка в працюючому контейнері.

Типова помилка: docker compose run app php artisan queue:work без --rm щодня - десятки забутих контейнерів. Подивитися їх: docker compose ps -a.

Докладніше в документації: docker compose run

Коли щось не працює в Compose-оточенні, є стандартний порядок діагностики.

1. Стан сервісів:

docker compose ps -a

Колонки STATUS (Up, Exited (1), Restarting) і (healthy)/(unhealthy) одразу показують, який сервіс проблемний. Exited (137) - зазвичай вбито через нестачу пам'яті, Exited (1) - помилка застосунку.

2. Логи:

docker compose logs app                 # усі логи сервісу
docker compose logs -f --tail=100 app   # останні 100 рядків і далі в реальному часі
docker compose logs --since 10m         # усі сервіси за 10 хвилин

Видно лише те, що процес пише в stdout/stderr. Якщо Laravel пише в storage/logs/laravel.log, у docker compose logs цього не буде - для контейнерів краще LOG_CHANNEL=stderr.

3. Вхід у контейнер:

docker compose exec app sh
# усередині: перевірити файли, змінні, з'єднання
env | grep DB_
nc -zv postgres 5432
php artisan about

Якщо контейнер одразу падає і exec неможливий - запустити той самий образ з іншою командою:

docker compose run --rm --entrypoint sh app

4. Конфігурація й деталі:

docker compose config                       # що Compose реально застосовує
docker inspect $(docker compose ps -q app)  # мережі, томи, змінні, healthcheck, причина зупинки
docker compose top                          # процеси в контейнерах
docker stats                                # пам'ять і CPU в реальному часі

Типові причини проблем і що перевірити:

  • «Connection refused» до бази - використано localhost замість імені сервісу (DB_HOST=postgres), база ще не готова (healthcheck), сервіси в різних мережах;
  • зміни коду не видно - немає bind mount, кеш конфігурації Laravel (php artisan optimize:clear), OPcache без перевірки часу змін;
  • права на файли - UID у контейнері не збігається з користувачем на хості;
  • порт зайнятий - інший процес на хості вже слухає той самий порт;
  • старий образ - після зміни Dockerfile потрібен docker compose up --build.

Docker Desktop має графічний інтерфейс для логів, терміналу й файлів контейнера - зручно для швидкого огляду.

Докладніше в документації: docker compose logs

Laravel Sail - легкий інструмент для локальної розробки Laravel у Docker. По суті це файл compose.yaml у корені проєкту і скрипт sail, що спрощує команди Docker Compose. Окремої «магії» немає - це звичайний Compose.

Встановлення:

composer require laravel/sail --dev
php artisan sail:install          # обрати сервіси: mysql, pgsql, redis, meilisearch, mailpit...
./vendor/bin/sail up -d

sail:install публікує compose.yaml і додає в .env змінні для підключення до сервісів у контейнерах. Додати сервіс пізніше - php artisan sail:add.

Скрипт sail - обгортка над docker compose:

Sail Що виконується
sail up -d docker compose up -d
sail artisan migrate php artisan migrate у контейнері застосунку
sail composer require ... Composer у контейнері
sail npm run dev Node у контейнері
sail test тести в контейнері
sail shell / sail root-shell оболонка в контейнері
sail tinker Tinker

Зручно зробити аліас: alias sail='sh $([ -f sail ] && echo sail || echo vendor/bin/sail)'.

Що варто знати:

  • версія PHP обирається в compose.yaml (образ на основі runtimes/8.5), підтримуються кілька версій;
  • WWWUSER/WWWGROUP - UID користувача в контейнері відповідає користувачу хоста, щоб файли, створені в контейнері, не належали root;
  • налаштування образів - sail artisan sail:publish копіює Dockerfile-и в каталог docker/ для змін (розширення PHP, пакети);
  • Xdebug вмикається змінною SAIL_XDEBUG_MODE у .env (develop,debug,coverage);
  • порти змінюються в .env (APP_PORT, FORWARD_DB_PORT), якщо стандартні зайняті.

Чого Sail не робить: це інструмент розробки, а не продакшен-оточення. Образи Sail розраховані на зручність (вбудований сервер, інструменти, Node), а не на безпеку чи розмір. Для продакшену будують власні образи (наприклад, на FrankenPHP чи php-fpm + nginx) або використовують хостинг на кшталт Laravel Cloud.

Альтернативи: Laravel Herd (нативно на macOS/Windows без Docker), DDEV, власний compose.yaml.

Докладніше в документації: Laravel Sail