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 і змінні всередині контейнера.
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.