Контейнеризация PHP с помощью Docker: от базового образа до production-сборки
Основы работы с Docker образом PHP
Наиболее эффективный способ получить рабочий Docker образ PHP заключается в использовании официальных образов из Docker Hub. Они предоставляют множество вариантов: с разными версиями PHP, а также с различными базовыми ОС (Debian, Alpine, Ubuntu). Для типичного веб-приложения на PHP рекомендуется использовать образ на основе Alpine, так как он имеет минимальный размер и содержит только необходимые компоненты.
Как запустить простое PHP-приложение в Docker?
Для старта достаточно создать файл index.php с кодом:
<?php
echo 'Hello, Docker!';
Php docker image (docker образ php)
Затем написать Dockerfile, который копирует этот файл в образ и использует встроенный сервер PHP:
FROM php:8.2-cli-alpine
COPY index.php /app/
WORKDIR /app
CMD ["php", "-S", "0.0.0.0:8080"]
Сборка образа и запуск контейнера:
docker build -t my-php-app .
docker run -p 8080:8080 my-php-app
После этого приложение будет доступно по адресу http://localhost:8080. Такой подход подходит для быстрого тестирования и разработки.
Возможные проблемы:
- Необходимость установки дополнительных расширений PHP (например, PDO, mbstring). Встроенный сервер не подходит для production из-за однопоточности.
- Отсутствие передачи переменных окружения для конфигурации.
- Некорректные права на файлы при монтировании томов (UID/GID контейнера отличается от хост-системы).
Решение:
Использовать официальные сборки с необходимыми расширениями через docker-php-ext-install. Для production применять готовый сервер, например, Nginx в связке с PHP-FPM, или включать расширение php-pecl для повышения производительности. Права настраивать через RUN adduser и USER в Dockerfile.
Как выбрать подходящую версию PHP и базовый образ?
Для выбора требуется учитывать требования приложения к версии PHP (7.4, 8.0, 8.1, 8.2, 8.3), необходимость в минимальном размере (Alpine) или полной среде (Debian). Пример Dockerfile для приложения, использующего PHP 8.1 с FPM:
FROM php:8.1-fpm-alpine
RUN docker-php-ext-install pdo pdo_mysql
COPY . /var/www/html
Как добавить расширения PHP в Docker образ?
Расширения устанавливаются с помощью команд docker-php-ext-install, docker-php-ext-configure и docker-php-ext-enable. Если расширение требует зависимости из ОС, их следует установить через apk add (для Alpine) или apt-get install (для Debian). Пример установки расширения gd:
FROM php:8.2-fpm-alpine
RUN apk add --no-cache freetype libpng libjpeg-turbo freetype-dev libpng-dev libjpeg-turbo-dev \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& apk del freetype-dev libpng-dev libjpeg-turbo-dev
Типичные ошибки при установке расширений:
- Отсутствие заголовочных файлов для компиляции (нужно устанавливать
-devпакеты). - Конфликт версий расширений (например,
icuдляintl). - Забытые
apk delдля удаления dev-пакетов, что увеличивает размер образа.
Решение:
Всегда устанавливать только необходимые dev-зависимости, компилировать расширение, затем удалять их. Использовать многоступенчатую сборку или специальные образы вроде mlocati/php-extension-installer для упрощения.
Как создать мультистейджевую сборку для продакшена?
Мультистейджевая сборка позволяет отделить этап компиляции расширений (с dev-зависимостями) от финального образа, что уменьшает его размер. Пример для приложения на Laravel:
# Первый этап: сборка зависимостей
FROM composer:latest AS composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
# Второй этап: PHP-FPM с Nginx
FROM php:8.2-fpm-alpine AS base
RUN docker-php-ext-install pdo pdo_mysql
COPY --from=composer /app/vendor /app/vendor
COPY . /app
RUN chown -R www-data:www-data /app/storage /app/bootstrap/cache
# Третий этап: Nginx
FROM nginx:alpine
COPY --from=base /app /app
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
Проблемы мультистейджевой сборки:
- Ошибки копирования артефактов между стадиями (неверные пути).
- Различия в библиотеках между стадиями (например,
php:8.2-fpm-alpineиcomposer:latestмогут иметь разные версии glibc).
Решение:
Использовать одинаковую базовую ОС для всех стадий, либо копировать только итоговый артефакт в образ, где установлены все runtime-зависимости.
Как работать с Composer в Docker?
Composer можно установить напрямую в образ или использовать официальный образ composer:latest как отдельный этап. Для разработки удобно монтировать директорию с хост-системы и выполнять composer install локально, для продакшена зависимости устанавливаются во время сборки. Пример Dockerfile для продакшена с предустановкой vendor:
FROM php:8.2-cli-alpine AS build
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
FROM php:8.2-cli-alpine
COPY --from=build /app/vendor /app/vendor
COPY . /app
Частые проблемы с Composer:
- Несоответствие версии PHP в образе Composer и целевом образе.
- Потребность в авторизации для приватных пакетов (нужно передавать SSH-ключи или токены через build args).
- Кэширование зависимостей на хост-машине может привести к устаревшим версиям.
Решение:
Использовать --no-cache и явно указывать версию PHP через конфигурацию Composer. Для приватных репозиториев применять --build-arg и удалять ключи после установки.
Как использовать образ PHP для CLI-скриптов?
Для выполнения консольных скриптов (миграции, воркеры очередей, cron) подходит образ на основе CLI. Пример Dockerfile для запуска artisan-команд в Laravel:
FROM php:8.2-cli-alpine
RUN docker-php-ext-install pcntl posix pdo_mysql
COPY . /app
WORKDIR /app
CMD ["php", "artisan", "queue:work"]
Запуск:
docker run -d --name worker my-php-worker
Потенциальные сложности:
- Необходимость в расширении
pcntlдля работы с процессами (недоступно на Windows). - Проблемы с сигналами при остановке контейнера (нужно правильно обрабатывать SIGTERM).
Решение:
Устанавливать pcntl через docker-php-ext-install. В сценариях использовать exec для замены процесса, чтобы сигналы доходили до PHP.
Расширенные примеры сборки Docker образа PHP
В этом разделе представлены нестандартные сценарии использования Docker образа PHP с подробными примерами и результатами.
Пример 1. Образ PHP-FPM с Nginx и поддержкой PostgreSQL
Цель: запустить традиционный стек LEMP (Linux, Nginx, MySQL, PHP) на Docker. Вместо MySQL используем PostgreSQL.
Dockerfile для PHP:
FROM php:8.2-fpm-alpine
RUN apk add --no-cache postgresql-dev libpq-dev \
&& docker-php-ext-install pdo pdo_pgsql \
&& apk del postgresql-dev libpq-dev
COPY . /var/www/html
docker-compose.yml:
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./src:/var/www/html
depends_on:
- php
php:
build: .
volumes:
- ./src:/var/www/html
environment:
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=app
- DB_USER=user
- DB_PASSWORD=pass
depends_on:
- postgres
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=app
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Результат: после docker-compose up -d сайт доступен на localhost. PHP успешно подключается к PostgreSQL через PDO.
# Проверка подключения из PHP-контейнера:
docker exec -it php_container php -r "echo extension_loaded('pdo_pgsql') ? 'OK' : 'FAIL';"
# Вывод: OK
Пример 2. Установка Xdebug для отладки
Xdebug полезен при разработке. Его включение в Docker образ требует настройки как расширения, так и параметров IDE.
Dockerfile для разработки:
FROM php:8.2-fpm-alpine
RUN apk add --no-cache $PHPIZE_DEPS \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug
COPY . /var/www/html
docker-compose.override.yml (для dev):
version: '3.8'
services:
php:
environment:
- XDEBUG_MODE=debug
- XDEBUG_START_WITH_REQUEST=yes
- XDEBUG_SESSION=PHPSTORM
extra_hosts:
- "host.docker.internal:host-gateway"
Результат: при открытии страницы с браузером, в котором включен Xdebug (расширение), IDE (PhpStorm, VS Code) ловит точки останова.
# Проверка, что Xdebug активен: docker exec -it php_container php -v # PHP 8.2.x (cli) (built: ... ) # with Xdebug v3.2.x, Copyright (c) 2002-2023, by Derick Rethans # Информация о настройках: docker exec -it php_container php -i | grep xdebug # (список директив)
Пример 3. Запуск cron-задач внутри PHP контейнера
Для выполнения периодических задач (например, очистка кэша) можно использовать crond в том же контейнере или отдельный образ.
Dockerfile с cron:
FROM php:8.2-cli-alpine
RUN apk add --no-cache dcron
COPY crontab /etc/crontabs/www-data
RUN chmod 0644 /etc/crontabs/www-data
COPY . /app
WORKDIR /app
CMD crond -l 2 -f
crontab:
* * * * * php /app/artisan schedule:run >> /dev/null 2>&1
Результат: каждую минуту выполняется artisan-команда Laravel. Логи выводятся в stdout (но в примере они перенаправлены в /dev/null, для отладки можно перенаправить в лог).
# Проверка работы cron: docker logs -f cron_container # Появится вывод при запуске crond (если не перенаправлен)
Пример 4. Использование образа с специфическими расширениями: imagick и gd
Иногда требуется одновременно установить несколько расширений для работы с изображениями.
Dockerfile:
FROM php:8.2-fpm-alpine
RUN apk add --no-cache imagemagick-dev imagemagick libpng-dev libjpeg-turbo-dev \
&& pecl install imagick \
&& docker-php-ext-enable imagick \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install gd \
&& apk del imagemagick-dev libpng-dev libjpeg-turbo-dev
Результат: в контейнере доступны оба расширения.
# Список установленных расширений: docker exec -it php_container php -m | grep -E 'imagick|gd' imagick gd
Пример 5. Минимальный образ для CLI с оптимизацией размера
Использование php:8.2-cli-alpine и удаление ненужных файлов может уменьшить образ до 50 МБ.
FROM php:8.2-cli-alpine
RUN apk add --no-cache --virtual .builddeps $PHPIZE_DEPS \
&& docker-php-ext-install pcntl \
&& apk del .builddeps \
&& rm -rf /tmp/* /var/cache/apk/*
COPY my_script.php /app/
WORKDIR /app
CMD ["php", "my_script.php"]
Результат: итоговый размер образа около 30-40 МБ.
# Проверка размера образа: docker images my-php-cli REPOSITORY TAG IMAGE ID CREATED SIZE my-php-cli latest abcdef123456 10 seconds ago 38.2MB
Примечание:
Все примеры предполагают, что приложение написано с учетом окружения Docker. Рекомендуется использовать переменные окружения для конфигурации, читать их через getenv() или библиотеки (например, phpdotenv). Также стоит обратить внимание на логирование: stdout/stderr контейнера собираются Docker демоном и доступны через docker logs.