Питання на співбесіді: Compose для розробки
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
14 питань
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 і змінні всередині контейнера.
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).
Обидві команди виконують команду «в сервісі», але по-різному.
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.
Коли щось не працює в 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 має графічний інтерфейс для логів, терміналу й файлів контейнера - зручно для швидкого огляду.
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.
Compose вміє зливати кілька файлів: базова конфігурація + доповнення для конкретного оточення.
Автоматичне злиття: якщо поруч з compose.yaml є compose.override.yaml, docker compose up бере обидва - перевизначення застосовується поверх бази.
# compose.yaml - спільне для всіх оточень
services:
app:
image: myapp:${TAG:-latest}
environment:
APP_ENV: production
# compose.override.yaml - лише для локальної розробки (часто в .gitignore)
services:
app:
build: .
environment:
APP_ENV: local
APP_DEBUG: "true"
volumes:
- .:/var/www/html
ports:
- "127.0.0.1:8000:8000"
mailpit:
image: axllent/mailpit
Явний набір файлів - прапорець -f (файл override тоді не підхоплюється автоматично):
docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose -f compose.yaml -f compose.ci.yaml run --rm app php artisan test
Або через змінну COMPOSE_FILE=compose.yaml:compose.ci.yaml.
Правила злиття:
- одиночні значення (
image,command,restart) - заміняються значенням з пізнішого файлу; - словники (
environment,labels) - зливаються за ключами; - списки
ports,volumes- об'єднуються (з урахуванням однакових цілей), а не заміняються; !resetі!override- теги YAML, щоб явно скинути чи повністю замінити значення з базового файлу (наприклад, прибрати порти, оголошені в базі).
services:
app:
ports: !reset []
Відносні шляхи в усіх файлах обчислюються від першого файлу (базової директорії проєкту) - типова пастка при -f інша-папка/compose.yaml.
Перевірити результат злиття:
docker compose -f compose.yaml -f compose.prod.yaml config
Типові схеми:
compose.yaml(база) +compose.override.yaml(розробка, автоматично) +compose.prod.yaml(явно на сервері);- окремі файли для CI (без томів з кодом, з тестовою базою в пам'яті).
Альтернатива злиттю - include (підключення окремих частин) і профілі (сервіси, що вмикаються за потреби).
Профілі дають змогу тримати в одному compose.yaml сервіси, які запускаються лише за потреби.
services:
app:
build: .
postgres:
image: postgres:18
mailpit:
image: axllent/mailpit
profiles: [tools]
phpmyadmin:
image: phpmyadmin
profiles: [tools]
horizon:
build: .
command: php artisan horizon
profiles: [queue]
playwright:
image: mcr.microsoft.com/playwright
profiles: [e2e]
Правила:
- сервіс без
profilesзапускається завжди; - сервіс з профілем - лише коли профіль активовано:
docker compose up -d # app, postgres
docker compose --profile tools up -d # + mailpit, phpmyadmin
docker compose --profile tools --profile queue up -d
COMPOSE_PROFILES=tools,queue docker compose up -d
- явно названий сервіс запускається навіть без активного профілю:
docker compose run --rm playwright- профіль не потрібен; - якщо сервіс з профілем залежить (
depends_on) від іншого сервісу з неактивним профілем - Compose повідомить про помилку; залежності мають бути або без профілю, або в тому самому.
Коли профілі зручніші за окремі файли:
- допоміжні інструменти розробки (адмінки баз, перехоплювачі пошти, профайлери), які потрібні не щодня;
- важкі сервіси (Elasticsearch, браузерні тести), що споживають багато пам'яті, - вмикати лише для відповідних задач;
- ролі одного застосунку - вебсервер, воркер черги, планувальник - запускаються за потреби;
- усе в одному файлі - видно повну картину оточення, без пошуку по кількох файлах.
Коли краще окремі файли (-f): коли відрізняється конфігурація тих самих сервісів між оточеннями (розробка, CI, продакшен), а не набір сервісів.
Перевірка: docker compose --profile tools config --services - перелік сервісів, що будуть запущені з цим профілем.
Healthcheck - команда, яку Docker періодично виконує всередині контейнера, щоб визначити, чи сервіс справді працює, а не просто запущений процес. Результат - стан starting, healthy чи unhealthy.
services:
postgres:
image: postgres:18
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s # як часто перевіряти
timeout: 3s # скільки чекати на відповідь
retries: 10 # скільки невдач поспіль до unhealthy
start_period: 20s # пільговий період на старті: невдачі не рахуються
start_interval: 1s # частіші перевірки під час start_period
app:
build: .
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8000/up"]
interval: 10s
timeout: 3s
retries: 3
Що робить healthcheck добрим:
- перевіряє здатність обслуговувати запити, а не лише живий процес:
pg_isready,redis-cli ping, HTTP-запит до ендпойнта здоров'я (у Laravel 11+ - маршрут/up); - легкий і швидкий - він виконується кожні кілька секунд; важкі запити до бази в healthcheck створюють навантаження;
- не залежить від зовнішніх сервісів у перевірці «живучості»: якщо застосунок вважає себе unhealthy через недоступний сторонній API, оркестратор почне його перезапускати, що нічого не виправить;
- інструмент є в образі:
curlчиwgetчасто відсутні в мінімальних образах - перевірка падає не через сервіс, а через відсутню програму; $$у Compose - щоб змінна підставилася всередині контейнера, а не при розборі файлу.
Як використовується стан:
depends_onзcondition: service_healthy- залежний сервіс стартує лише після готовності;docker compose psпоказує стан, аdocker inspect- історію останніх перевірок з виводом команди;docker compose up --wait- дочекатися, поки всі сервіси стануть healthy (зручно в CI);- оркестратори (Swarm) перезапускають unhealthy-контейнери. Звичайний Docker сам по собі не перезапускає unhealthy-контейнер - лише позначає його.
Healthcheck в образі (HEALTHCHECK у Dockerfile) задає перевірку за замовчуванням; у Compose її можна перевизначити чи вимкнути (disable: true).
Bind mount (volumes: - .:/var/www/html) робить каталог хоста видимим у контейнері напряму: зміна файлу на хості миттєво видна в контейнері.
docker compose watch (Compose 2.22+) - інший підхід: Compose стежить за файлами на хості й при змінах виконує дію з контейнером.
services:
app:
build: .
develop:
watch:
- action: sync # скопіювати змінені файли в контейнер
path: ./app
target: /var/www/html/app
- action: sync+restart # скопіювати й перезапустити сервіс
path: ./config
target: /var/www/html/config
- action: rebuild # перезібрати образ і перестворити контейнер
path: composer.lock
- action: sync
path: ./resources
target: /var/www/html/resources
ignore:
- node_modules/
docker compose watch # або docker compose up --watch
Дії:
sync- копіює змінені файли в контейнер (для коду, що підхоплюється «на льоту»: PHP, шаблони, фронтенд з HMR);sync+restart- копіює й перезапускає основний процес (змінилася конфігурація);sync+exec- копіює й виконує команду в контейнері;rebuild- перезбирає образ (змінилися залежності:composer.lock,package.json,Dockerfile).
Чим відрізняється від bind mount:
- синхронізація лише потрібних каталогів, з виключеннями -
vendor,node_modulesі кеші не синхронізуються, у контейнері лишаються свої версії, зібрані для Linux; - продуктивність на macOS і Windows: bind mount через віртуальну машину Docker Desktop повільний на великих кодових базах; watch копіює лише змінені файли, а читання в контейнері йде з його власної файлової системи;
- права й власники файлів - менше проблем, ніж зі спільними каталогами;
- автоматичний rebuild при зміні залежностей - не треба пам'ятати про
--build.
Обмеження:
- зміни з контейнера назад на хост не синхронізуються (згенеровані файли, міграції, створені командою
artisan make:*) - для таких робочих процесів bind mount зручніший; - потрібен
buildу конфігурації сервісу (watch працює з сервісами, які Compose збирає).
Для Laravel-розробки часто поєднують: bind mount для каталогів, де генеруються файли, і watch - для залежностей з rebuild.
У розробці код зазвичай монтують з хоста: - .:/var/www/html. Разом із кодом у контейнер потрапляють і каталоги залежностей хоста - vendor і node_modules. Звідси три проблеми:
1. Різні платформи. На хості macOS (ARM чи x86), у контейнері - Linux. Нативні модулі npm (esbuild, rollup, sharp) і деякі Composer-пакети з бінарними файлами, встановлені на хості, у контейнері не запрацюють - і навпаки.
2. Продуктивність. vendor і node_modules - десятки тисяч дрібних файлів. На Docker Desktop (macOS, Windows) кожне читання через bind mount проходить через межу віртуальної машини - автозавантаження Composer і збирання фронтенду помітно сповільнюються.
3. Конфлікти. Залежності, встановлені в контейнері, перезаписують хостові (і навпаки) - нескінченне «а в мене працює».
Рішення - іменований том поверх каталогу залежностей:
services:
app:
build: .
volumes:
- .:/var/www/html # код з хоста
- vendor:/var/www/html/vendor # залежності - у томі Docker
- node_modules:/var/www/html/node_modules
volumes:
vendor:
node_modules:
Пізніше змонтований том «закриває» відповідний підкаталог bind mount: у контейнері vendor - з тому (Linux-версії, швидкий доступ), а на хості свій vendor (чи порожньо).
Що варто знати:
- встановлення залежностей - у контейнері:
docker compose run --rm app composer install,docker compose run --rm app npm ci; - IDE на хості не бачить залежностей з тому - автодоповнення й аналіз коду страждають. Варіанти: встановлювати й на хості теж, налаштувати IDE на віддалений інтерпретатор у контейнері, devcontainers;
- новий том порожній при першому створенні, але якщо в образі за цим шляхом уже є файли, Docker копіює їх у порожній том - зручно, якщо залежності встановлені при збиранні образу;
- оновлення залежностей: після зміни
composer.lockтом треба оновити (composer installзнову) - сам по собі він не «знає» про зміни; - скинути -
docker compose down -vвидаляє томи проєкту (разом з даними бази, якщо вони теж у томах!) - або видалити конкретний том:docker volume rm проєкт_vendor.
Альтернативи: docker compose watch з виключенням каталогів залежностей, синхронізовані файлові спільні каталоги Docker Desktop.
Контейнери 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).
Практичні прийоми, що працюють незалежно від механізму:
- не монтувати залежності:
vendorіnode_modules- в іменованих томах (лишаються всередині ВМ) або встановлювати в образі; - монтувати лише потрібне: не весь проєкт з
.git, логами й кешем, а каталоги з кодом; - кеші й скомпільовані файли (
storage/framework,bootstrap/cache) - у томі чи tmpfs, а не на bind mount; - OPcache з
validate_timestampsі розумною частотою перевірки - менше звернень до файлової системи; docker compose watchзsync- копіювати змінені файли в контейнер замість спільного доступу;- ресурси ВМ: достатньо пам'яті й процесорів у налаштуваннях 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: усі три механізми розгортаються в підсумкову конфігурацію, і саме її варто перевіряти, а не покладатися на уявлення про злиття.
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.
Чому з'єднання не приходить - чек-лист:
- Xdebug не ввімкнено:
php -vу контейнері має показувати Xdebug,php -i | grep xdebug.mode; - немає тригера при
start_with_request=trigger- потрібне розширення браузера Xdebug Helper чи параметрXDEBUG_TRIGGER; - неправильний
client_host- особливо на Linux безhost-gateway; - фаєрвол хоста блокує вхідний порт 9003;
- IDE не слухає чи слухає інший порт (старий Xdebug 2 використовував 9000);
- журнал 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.