Почему ваш PHP-проект заслуживает Docker
Вы когда-нибудь тратили полдня, чтобы объяснить новому разработчику, как настроить окружение? Или сталкивались с ситуацией, когда код работает на вашем localhost, но падает на сервере? Знакомо? Тогда эта статья — для вас.
Docker — это не просто модный buzzword. Это инструмент, который превращает фразу «у меня работает» в «работает у всех». Для PHP-разработчика контейнеризация — это суперсила: вы упаковываете приложение, веб-сервер и базу данных в легкие контейнеры, которые гарантированно ведут себя одинаково на любой машине.
В этом гайде мы не будем читать скучную теорию. Мы сразу перейдем к делу: напишем Dockerfile для PHP, настроим связку Nginx + PHP-FPM + MySQL через Docker Compose и развернем реальное приложение. Вжух — и ваш проект готов к деплою!
Что такое контейнеризация и зачем она PHP-разработчику?
Представьте, что вы переезжаете в новую квартиру. Вместо того чтобы каждый раз собирать мебель заново (устанавливать PHP, настраивать Apache, ставить модули), вы просто берете готовую коробку с вашим окружением. Docker — это такие коробки. Они называются контейнерами.
Для PHP это особенно актуально. Версии PHP меняются, требуются разные расширения (gd, mysqli, redis), а в продакшене всё должно быть стабильно. Контейнеризация решает три главные проблемы:
- Воспроизводимость — окружение одинаково у всех: разработчик, тестировщик, сервер.
- Изоляция — проекты не конфликтуют (у вас больше никогда не сломается старый сайт из-за обновления PHP).
- Простота деплоя — запуск одной командой
docker-compose up.
Готовим Dockerfile для PHP-приложения
Начнем с главного — создания образа для нашего PHP-проекта. Допустим, у нас есть простое приложение на PHP 8.2 с использованием PDO для работы с MySQL. Вот как выглядит минимальный Dockerfile:
# Используем официальный образ PHP 8.2 с FPM
FROM php:8.2-fpm-alpine
# Устанавливаем необходимые системные пакеты
RUN apk add --no-cache \
nginx \
supervisor \
libpq-dev \
&& docker-php-ext-install pdo pdo_mysql
# Копируем конфиги (создадим их позже)
COPY ./docker/nginx.conf /etc/nginx/nginx.conf
COPY ./docker/supervisord.conf /etc/supervisord.conf
# Копируем код приложения
COPY . /var/www/html
# Права на запись для логов и кеша
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
# Открываем порты
EXPOSE 80
# Запускаем supervisor (он будет управлять nginx и php-fpm)
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
Что здесь важно? Мы используем alpine версию — она весит в разы меньше. Устанавливаем только нужные расширения через docker-php-ext-install. И да, мы используем супервизор, чтобы внутри одного контейнера крутились и Nginx, и PHP-FPM. Это удобно для небольших проектов.
Создаем конфиг Nginx
Теперь нам нужно настроить веб-сервер. Создайте файл docker/nginx.conf:
worker_processes auto;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name _;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
}
Обратите внимание: PHP-FPM слушает на порту 9000 внутри контейнера. Это стандартный порт для php-fpm.
Docker Compose: собираем всё вместе
Один контейнер — это круто. Но в реальном проекте нужна база данных. И Redis для кеша. И почему бы не добавить phpMyAdmin для удобства? Тут в игру вступает Docker Compose — оркестратор для мультиконтейнерных приложений.
Создаем docker-compose.yml в корне проекта:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: php_app
ports:
- "8080:80"
volumes:
- .:/var/www/html
depends_on:
- db
- redis
networks:
- app-network
db:
image: mysql:8.0
container_name: mysql_db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: my_app
MYSQL_USER: user
MYSQL_PASSWORD: user_password
ports:
- "3307:3306"
volumes:
- db_data:/var/lib/mysql
networks:
- app-network
redis:
image: redis:7-alpine
container_name: redis_cache
ports:
- "6379:6379"
networks:
- app-network
phpmyadmin:
image: phpmyadmin/phpmyadmin
container_name: phpmyadmin
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: root_password
ports:
- "8081:80"
depends_on:
- db
networks:
- app-network
volumes:
db_data:
networks:
app-network:
driver: bridge
Что мы видим? Четыре сервиса:
- app — наше PHP-приложение. Собирается из Dockerfile.
- db — MySQL 8.0 с настройками пользователя.
- redis — для кеширования сессий.
- phpmyadmin — удобная админка для базы.
Все сервисы подключены к одной сети app-network, поэтому могут общаться по именам контейнеров. Например, в PHP-коде к БД мы обращаемся как к db:
<?php
// config/database.php
return [
'host' => getenv('DB_HOST') ?: 'db',
'database' => getenv('DB_DATABASE') ?: 'my_app',
'username' => getenv('DB_USERNAME') ?: 'user',
'password' => getenv('DB_PASSWORD') ?: 'user_password',
];
Пример PHP-приложения, работающего с базой
Давайте напишем простой скрипт, который выведет список пользователей из БД. Создайте public/index.php:
<?php
require_once __