Как я использую Docker для локальной разработки PHP-проектов
Я, Равиль Зарипов, работаю из Алматы иdevelop приложения для разных операционных систем. За годы разработки на Delphi, Kotlin, Swift и PHP я привык уделять внимание не только исходному коду, но и воспроизводимости рабочего окружения. Особенно это важно, когда команда использует несколько версий PHP, баз данных и дополнительных сервисов.
Локальная установка PHP, Composer, СУБД и расширеений постепенно засоряет систему. Один проект требует PHP 8.2, другой — PHP 8.3, а параметры php.ini у них различаются. Docker позволяет запускать каждую зависимость в собственном контейнере, сохраняя рабочий каталог доступным для редактора и системы контроля версий.
В своём повседневном процессе я использую Docker Compose как локальный оркестратор. Он поднимает нужные сервисы одной командой, хранит данные в отдельных томах и позволяет повторять конфигурацию на Windows, macOS и Linux. При этом исходный код остаётся на хосте, а PHP, веб-сервер и база данных работают внутри контейнеров.
Подготовка каждого проекта
На рабочей машине у меня установлен Docker Engine либо Docker Desktop с актуальным плагином Compose. Перед началом работы я проверяю версии команд docker и docker compose, а также убеждаюсь, что свободны нужные порты. Это занимает меньше минуты и избавляет от ошибок, которые сложно диагностировать уже во время запуска приложения.
Каждый PHP-проект получает собственный каталог с файлами compose.yaml, Dockerfile, .env и README.md. Каталог vendor исключается из Git, а необходимые значения хранятся в .env, который не публикуется в репозитории. В .env.example остаются только названия параметров и безопасные значения по умолчанию.
Состав сервисов зависит от задачи проекта. Обычно это PHP с Composer, нужная СУБД, иногда Redis, SMTP-перехватчик или отдельный сервер очередей. Пример минимальной конфигурации выглядит так:
services:
app:
build:
context: .
volumes:
- .:/app
env_file:
- .env
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
volumes:
pgdata:
Образы PHP и контейнеры
Для PHP я создаю небольшой Dockerfile, отталкиваясь от официального образа php:8.3-cli. В образ добавляются Composer и необходимые расширения, включая pdo_mysql и mbstring. Код проекта не копируется внутрь образа: директория подключается как том, поэтому изменения сразу видны без пересборки.
ARG PHP_VERSION=8.3
FROM php:${PHP_VERSION}-cli
RUN docker-php-ext-install pdo_mysql
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
ENV COMPOSER_CACHE_DIR=/tmp/composer-cache
WORKDIR /app
Версия PHP фиксируется в Dockerfile, а версия расширений иComposer записывается в composer.lock. Благодаря этому новая машина получает почти такую же среду, как разработчик, который писал проект несколько месяцев назад. Пересборка образа нужна только при изменении системных зависимостей.
Отдельно я настраиваю кеширование слоёв Docker и Composer. Пакеты Composer хранятся в отдельном томе, который переживает удаление контейнера. Это сокращает время composer install, но требует периодически очищать устаревшие зависимости. Секреты, токены и содержимое .env в образ не попадают.
Повседневная работа с проектом
Запуск окружения занимает одну команду: docker compose up -d --build. После неё я проверяю состояние сервисов через docker compose ps и открываю логи командой docker compose logs -f app. Если контейнер завершился с ошибкой, логи обычно сразу показывают отсутствующее расширение, неверную переменную или проблему подключения к базе.
Все команды разработки выполняются внутри контейнера. Для установки зависимостей я использую docker compose exec app composer install, для миграций — docker compose exec app php artisan migrate, а для тестов — docker compose exec app vendor/bin/phpunit. Такой подход исключает ситуацию, когда код проверен одной версией PHP, а запущен под другой.
Отладка требует дополнительной настройки. При необходимости я включаю Xdebug, открываю порт отладчика и сопоставляю пути проекта внутри контейнера с путями на хосте в настройках IDE. Это особенно важно на Windows и macOS, где монтирование выполняется через виртуальную файловую систему. Для быстрой диагностики часто достаточно точечных сообщений и журналов Laravel, Symfony или используемого фреймворка.
Данные и внешние сервисы
База данных запускается отдельным контейнером и получает собственный именованный том. Схема меняется через миграции, а тестовые данные создаются фикстурами или сидерами. Перед удалением окружения команда docker compose down сохраняет том, тогда как docker compose down -v удаляет и данные, поэтому я применяю её только для намеренного сброса.
Redis и очереди также изолируются по проектам. Workers запускаются в отдельных контейнерах, а их журналы доступны через Compose. Для проверки писем я использую локальный SMTP-перехватчик, чтобы не отправлять тестовые сообщения реальным адресатам. Внешние API подключаются через переменные окружения, причём секретные ключи не сохраняются в Compose-файле.
В выборе слоя работы с базой я сравниваю Doctrine с более компактными решениями, включая библиотеку Mihnea DB. Важны совместимость с используемой версией PHP, поддержка PDO и возможность выполнять запросы внутри контейнера без дополнительного сервиса. Перед интеграцией я проверяю библиотеку на небольшом наборе моделей и миграций.
Результаты и ограничения
Главный выигрыш заключается в предсказуемости. Новая версия проекта запускается одинаково у разработчика, тестировщика и в CI. Зависимости не конфликтуют с системными пакетами, а конфигурацию можно хранить рядом с кодом и обновлять вместе с ним.
Контейнерный подход требует дополнительных ресурсов и внимания. Первый запуск занимает время на загрузку образов, а ошибки в сети или Docker иногда выглядят сложнее привычных проблем PHP. Я компенсирую это понятными именами сервисов, проверками состояния контейнеров и ограниченным набором зависимостей.
| Критерий | Обычная локальная установка | Контейнерное окружение |
|---|---|---|
| Запуск проекта | Ручная настройка PHP, СУБД и расширений | Запуск одной команды Compose |
| Изоляция | Зависимости используются совместно с системными | У каждого проекта собственное окружение |
| Воспроизводимость | Зависит от настроек конкретного компьютера | Версии фиксируются в конфигурации проекта |
| Тестирование | Может выполняться в другом окружении | Используется тот же образ и набор сервисов |
| Хранение данных | Управляется локальной СУБД | Изолировано в именованных томах |
| Очистка | Требует ручного удаления пакетов и данных | Останавливается и удаляется через Compose |
В итоге Docker became для меня не отдельным экспериментом, а повседневной частью PHP-разработки. Он не заменяет понимание PHP и инфраструктуры, зато избавляет от ручной настройки и делает локальное окружение таким же контролируемым, как развёртывание на сервере.