rzaripov.kz

Как я использую 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 и инфраструктуры, зато избавляет от ручной настройки и делает локальное окружение таким же контролируемым, как развёртывание на сервере.