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

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

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

4 питання

Контейнери Linux потребують ядра Linux. На macOS (і Windows) Docker Desktop запускає легку віртуальну машину, і контейнери працюють усередині неї. Файли проєкту лежать на диску macOS, а процес у контейнері читає їх через межу віртуальної машини - механізм спільного доступу до файлів.

Чому це повільно для PHP і Node-проєктів: проблема не в розмірі файлів, а в їх кількості. Автозавантажувач Composer, node_modules, кеші фреймворку - кожен запит до Laravel відкриває й перевіряє сотні файлів; збирання фронтенду - тисячі. Кожна операція з файлом через межу ВМ коштує набагато дорожче, ніж на локальному диску.

Механізми й еволюція:

  • gRPC FUSE, osxfs - старі механізми, дуже повільні на великих проєктах;
  • VirtioFS - сучасний механізм за замовчуванням на macOS, значно швидший, але на великих репозиторіях усе ще відчутно повільніший за нативну файлову систему;
  • Synchronized file shares - Docker Desktop тримає синхронізовану копію каталогу всередині ВМ (з файловим кешем), і контейнери читають її з нативною швидкістю. Розрахований на великі репозиторії (сотні тисяч файлів). Доступний у платних підписках Docker (Pro, Team, Business).

Практичні прийоми, що працюють незалежно від механізму:

  1. не монтувати залежності: vendor і node_modules - в іменованих томах (лишаються всередині ВМ) або встановлювати в образі;
  2. монтувати лише потрібне: не весь проєкт з .git, логами й кешем, а каталоги з кодом;
  3. кеші й скомпільовані файли (storage/framework, bootstrap/cache) - у томі чи tmpfs, а не на bind mount;
  4. OPcache з validate_timestamps і розумною частотою перевірки - менше звернень до файлової системи;
  5. docker compose watch з sync - копіювати змінені файли в контейнер замість спільного доступу;
  6. ресурси ВМ: достатньо пам'яті й процесорів у налаштуваннях Docker Desktop.

Альтернативи Docker Desktop: OrbStack (швидший доступ до файлів і менше споживання ресурсів на macOS), Colima. Або відмовитися від Docker для самого PHP у розробці - Laravel Herd запускає PHP нативно, а в Docker лишаються лише бази й допоміжні сервіси.

На Linux проблеми немає: контейнери працюють на тому самому ядрі, і bind mount - це звичайне монтування без накладних витрат.

Докладніше в документації: Синхронізовані файлові спільні каталоги

Коли compose.yaml росте до десятків сервісів, з'являються дублювання й конфлікти. Compose має кілька механізмів структурування.

1. include (Compose 2.20+) - підключити інший файл Compose як окремий підпроєкт зі своїми відносними шляхами:

# compose.yaml
include:
  - infra/compose.yaml          # бази, Redis, пошук
  - path: tools/compose.yaml    # інструменти розробки
    env_file: tools/.env

services:
  app:
    build: .
    depends_on: [postgres, redis]   # сервіси з підключених файлів доступні

На відміну від злиття через -f, кожен підключений файл розв'язує шляхи відносно свого розташування, тож команда, що відповідає за інфраструктуру, може тримати свій файл окремо. Конфлікт імен сервісів - помилка, а не тихе злиття.

2. extends - успадкувати конфігурацію сервісу з того самого чи іншого файлу:

services:
  php-base:
    build: .
    environment:
      APP_ENV: local
    volumes:
      - .:/var/www/html

  app:
    extends: php-base
    ports: ["127.0.0.1:8000:8000"]

  worker:
    extends: php-base
    command: php artisan queue:work

  scheduler:
    extends: php-base
    command: php artisan schedule:work

3. Якорі YAML і поля розширення x-:

x-php: &php
  build: .
  env_file: .env
  volumes: [".:/var/www/html"]

services:
  app:
    <<: *php
    ports: ["127.0.0.1:8000:8000"]
  worker:
    <<: *php
    command: php artisan queue:work

Поля верхнього рівня з префіксом x- Compose ігнорує, тож туди зручно класти спільні фрагменти. Злиття <<: - поверхневе: вкладені словники заміняються повністю, а не зливаються.

Як обрати:

  • кілька незалежних частин (інфраструктура, застосунок, інструменти), різні власники - include;
  • кілька ролей одного образу (web, worker, scheduler) - extends чи якорі YAML;
  • різні оточення для тих самих сервісів - кілька файлів через -f чи профілі.

Перевірка результату - завжди docker compose config: усі три механізми розгортаються в підсумкову конфігурацію, і саме її варто перевіряти, а не покладатися на уявлення про злиття.

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

Xdebug - налагоджувач PHP: точки зупинки, покрокове виконання, перегляд змінних. У Docker головна складність - мережева: Xdebug сам ініціює з'єднання з IDE (порт 9003), а IDE працює на хості, поза контейнером.

Мінімальна конфігурація PHP у контейнері:

[xdebug]
xdebug.mode=debug,develop
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.start_with_request=trigger   ; лише за запитом, а не на кожен

host.docker.internal - ім'я, що вказує на хост із контейнера. На Docker Desktop (macOS, Windows) воно працює автоматично; на Linux його треба додати явно:

services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"

У Laravel Sail це вже налаштовано: досить змінної в .env - SAIL_XDEBUG_MODE=develop,debug,coverage - і перезапуску контейнерів. Для CLI-команд - sail debug artisan ....

Налаштування IDE (PhpStorm):

  • слухати вхідні з'єднання налагодження на порту 9003;
  • відображення шляхів (path mappings): код у контейнері лежить у /var/www/html, а на хості - у ~/projects/app. Без відображення IDE отримує з'єднання, але не може зіставити файли й не зупиняється на точках;
  • ім'я сервера (PHP_IDE_CONFIG=serverName=...) збігається з налаштуваннями сервера в IDE.

Чому з'єднання не приходить - чек-лист:

  1. Xdebug не ввімкнено: php -v у контейнері має показувати Xdebug, php -i | grep xdebug.mode;
  2. немає тригера при start_with_request=trigger - потрібне розширення браузера Xdebug Helper чи параметр XDEBUG_TRIGGER;
  3. неправильний client_host - особливо на Linux без host-gateway;
  4. фаєрвол хоста блокує вхідний порт 9003;
  5. IDE не слухає чи слухає інший порт (старий Xdebug 2 використовував 9000);
  6. журнал Xdebug показує причину: xdebug.log=/tmp/xdebug.log - там видно спробу з'єднання і помилку.

Продуктивність: Xdebug помітно сповільнює PHP навіть без активного налагодження. Тому start_with_request=trigger чи окремий образ/профіль з Xdebug, а не постійно ввімкнений режим. У продакшені Xdebug не встановлюють узагалі.

Докладніше в документації: Laravel Sail: налагодження з Xdebug

Ресурси Compose (контейнери, мережі, томи) належать проєкту. Назва проєкту - префікс усіх ресурсів: myapp-app-1, myapp_default, myapp_pgdata. За замовчуванням назва - ім'я каталогу з compose.yaml; явно задається полем name: у файлі, змінною COMPOSE_PROJECT_NAME чи прапорцем -p.

Рівні «скидання» - від м'якого до радикального:

docker compose restart app            # перезапустити процес, контейнер той самий
docker compose up -d --force-recreate # перестворити контейнери (записуваний шар скинуто)
docker compose up -d --build          # перезібрати образи й перестворити
docker compose down                   # видалити контейнери й мережі проєкту, ТОМИ ЛИШАЮТЬСЯ
docker compose down -v                # + іменовані томи проєкту: дані бази ЗНИКНУТЬ
docker compose down --rmi local       # + образи, зібрані для проєкту

Головне - розуміти, що де живе:

  • записуваний шар контейнера зникає при перестворенні;
  • іменовані томи (pgdata) переживають down, але не down -v;
  • bind mount - це файли хоста, їх Docker не видаляє ніколи;
  • анонімні томи накопичуються, якщо не видаляти їх разом з контейнерами (down -v чи docker compose rm -v).

Небезпечні звички:

  • docker system prune -a --volumes - чистить усе невикористовуване на машині: томи, образи, мережі всіх проєктів, включно з базами сусідніх проєктів, які зараз просто не запущені. Для чистки одного проєкту - команди docker compose з його назвою;
  • однакова назва проєкту для двох різних каталогів (обидва app/) - вони ділять мережі й томи, і down -v в одному зносить дані іншого. Явне name: у compose.yaml знімає проблему;
  • down -v «за звичкою» при кожному перезапуску - щоразу порожня база й повторні міграції з сидерами.

Безпечне скидання бази розробки:

docker compose exec app php artisan migrate:fresh --seed   # схема й тестові дані
# або точково видалити один том
docker compose down
docker volume rm myapp_pgdata
docker compose up -d

Перед видаленням томів із цінними даними - бекап: docker compose exec -T postgres pg_dump -U postgres app > backup.sql.

Перелік ресурсів проєкту: docker compose ps -a, docker volume ls --filter label=com.docker.compose.project=myapp.

Докладніше в документації: Назва проєкту Compose