Особенностью шаблона Advanced PHP-фреймворка Yii 2 является разделение на фронт-офис (сайт) и бэк (панель управления). Следовательно, веб-сервер должен обслуживать два хоста. В продолжение темы про Lighttpd рассмотрим, как это можно устроить.
Для демонстрации я конечно же возьму свой свежеиспеченный образ Docker. Зря делал, что ли?
Начальная конфигурация Lighttpd
На самом деле я ни в коем случае не настаиваю на использовании именно моего образа (хотя был бы очень признателен), можно взять jitesoft/lighttpd в паре с официальным php:fpm или вообще обойтись без Докера (хотя тут могут быть сюрпризы от авторов пакетов). Однако, для синхронизации, вот какая конфигурация Lighttpd получилась у меня (если ее объединить и убрать mod_status):
# Defaults
server.modules += ( "mod_access", "mod_accesslog", "mod_auth", "mod_rewrite" )
server.port = env.LIGHTTPD_PORT
server.document-root = env.LIGHTTPD_DOCUMENT_ROOT
server.tag = ""
index-file.names += ( "index.xhtml", "index.html", "index.htm", "default.htm", "index.php" )
url.access-deny = ( "~", ".inc" )
static-file.exclude-extensions = ( ".php", ".pl", ".cgi", ".fcgi", ".scgi" )
# PHP
server.modules += ( "mod_fastcgi" )
fastcgi.server = ( ".php" =>
((
"socket" => "/tmp/www.sock",
"broken-scriptfilename" => "enable",
"bin-path" => "/usr/local/sbin/php-fpm",
"max-procs" => 1,
"kill-signal" => 3
))
)
Особенности настроек заключаются в том, что образ работает сразу под www-data, поэтому явно указывать пользователя и группу нет необходимости, а еще Lighttpd сам запускает мастер-процесс php-fpm (параметры bin-path
и последующие). Возможно, в будущих версиях образа я и эту конфигурацию почищу, например многовато index-file.names
, да и static-file.exclude-extensions
тоже.
Порт и корневая директория устанавливаются через переменные окружения LIGHTTPD_PORT
и LIGHTTPD_DOCUMENT_ROOT
, это 9000 (вместо переключенного на сокет PHP) и /var/www/html по умолчанию соответственно.
Подготовка Docker
Как обычно, воспользуемся compose.yaml:
services:
php:
build: .
volumes:
- ./src:/var/www/html
В Dockerfile установим необходимое расширение gd и подключим конфигурацию PHP для среды разработки:
FROM dmkos/php:8.4-lighttpd
# Switch to configure PHP
USER root
# Install required gd extension
ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN install-php-extensions \
gd
# Use the default development configuration
RUN cp "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
# Switch back
USER www-data
Создадим подкаталог src. Поскольку контейнер работает под пользователем www-data, на хосте должен быть задан соответствующий владелец (33:33) или права 777:
mkdir src
sudo chown 33:33 src
Установка шаблона
Сделаем это с помощью composer, благо в свой образ я его поместил заранее. Разово запускаем контейнер (предполагается, что текущий пользователь включен в группу docker, иначе придется предварять команды sudo):
docker compose run -it --rm php bash
Устанавливаем:
composer create-project --prefer-dist yiisoft/yii2-app-advanced .
Инициализируем окружение среды разработки:
./init --env=Development --overwrite=All --delete=All
Чтобы не переусложнять пример, вместо предлагаемой по умолчанию MySQL воспользуемся SQLite:
mkdir common/data
touch common/data/db.sqlite
sed -i 's#mysql:host=localhost;dbname=yii2advanced#sqlite:@common/data/db.sqlite#' common/config/main-local.php
php yii migrate
Включение «красивых» URL скриптом слегка неочевидно, поскольку нужный блок закомментирован в main.php (как фронта, так и бэка). Допустим, мы можем убрать начало (/*
) и конец (*/
) блочного комментария вокруг настроек urlManager:
sed -i -r '/(\/\*|\*\/)/d' backend/config/main.php
sed -i -r '/(\/\*|\*\/)/d' frontend/config/main.php
Приходится экранировать как звездочку, так и косую черту, поэтому получился такой вот частокол.
В идеале бы еще подсказать Yii, что мы будем работать позади прокси:
sed -i "/cookieValidationKey/a 'trustedHosts' => ['private']," backend/config/main-local.php
sed -i "/cookieValidationKey/a 'trustedHosts' => ['private']," frontend/config/main-local.php
Наконец, выходим из контейнера:
exit
Настройка Lighttpd и Traefik
Поскольку я разрабатывал образ веб-сервера, ориентируясь на его использование в паре с Traefik, то добавим последний в compose.yaml:
services:
php:
build: .
labels:
- traefik.enable=true
- traefik.http.routers.yii2adv.rule=Host(`back.lighttpd.local`) || Host(`front.lighttpd.local`)
volumes:
- ./config:/usr/local/etc/lighttpd/conf.d
- ./src:/var/www/html
proxy:
image: traefik:v3
# this is very basic configuration, please do not use in production
command:
- --api.insecure=true
- --providers.docker.exposedbydefault=false
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
Пока жестко прописал хосты. Конфигурация Traefik – самая минимальная для среды разработки.
И вот наступает ключевой момент – настройка Lighttpd, файл config/advanced.conf:
# index.php expects original URL in PATH_INFO
url.rewrite-if-not-file = ( "" => "/index.php${url.path}${qsa}" )
# define document root per host
if $HTTP["host"] == "back.lighttpd.local" {
server.document-root = env.LIGHTTPD_DOCUMENT_ROOT + "/backend/web"
} else $HTTP["host"] == "front.lighttpd.local" {
server.document-root = env.LIGHTTPD_DOCUMENT_ROOT + "/frontend/web"
}
Сначала определяем типовые правила перезаписи для работы ЧПУ, а затем условным оператором распределяем корневые директории по хостам. И вот это that’s it, как зачастую не по делу пишут в документации к Symfony.
Теоретически можно было бы определить LIGHTTPD_DOCUMENT_ROOT
как путь к веб-директории сайта и жестко прописать server.document-root
только для админки. Текущая же конфигурация допускает локальные обращения ко всему проекту, например (из контейнера):
curl localhost:9000/requirements.php
Не думаю, что это принципиально, ведь в нашем случае прокси-сервер изолирует контейнер от внешнего мира и пропускает трафик только для определенных нами имен хостов.
Переменные окружения
Предлагаю доработать пример, чтобы можно было задавать имена хостов с помощью файла .env:
BACKEND_HOSTNAME = admin.example.com
FRONTEND_HOSTNAME = www.example.com
Заменяем хардкод в определении сервиса:
services:
php:
build: .
labels:
- traefik.enable=true
- traefik.http.routers.yii2adv.rule=Host(`${BACKEND_HOSTNAME:-back.lighttpd.local}`) || Host(`${FRONTEND_HOSTNAME:-front.lighttpd.local}`)
environment:
BACKEND_HOSTNAME: ${BACKEND_HOSTNAME:-back.lighttpd.local}
FRONTEND_HOSTNAME: ${FRONTEND_HOSTNAME:-front.lighttpd.local}
volumes:
- ./config:/usr/local/etc/lighttpd/conf.d
- ./src:/var/www/html
И настройках Lighttpd:
# define document root per host
if $HTTP["host"] == env.BACKEND_HOSTNAME {
server.document-root = env.LIGHTTPD_DOCUMENT_ROOT + "/backend/web"
} else $HTTP["host"] == env.FRONTEND_HOSTNAME {
server.document-root = env.LIGHTTPD_DOCUMENT_ROOT + "/frontend/web"
}
Проверка
Там, где карту оформляли браузер запускали, сопоставьте IP-адрес сервера (виртуальной машины) с заданными именами, например:
192.168.56.103 admin.example.com
192.168.56.103 www.example.com
В Windows это файл C:\Windows\System32\drivers\etc\hosts, в Linux – /etc/hosts.
Поднимаем приложение:
docker compose up -d
И через некоторое время открываем сайт:
Наконец-то что-то новое – Symfony Demo уже как-то поднадоела. Регистрируемся (Signup), после чего надо каким-то образом верифицировать электронную почту. По умолчанию письма сохраняются во frontend/runtime/mail. Лучше всего скачать файл с письмом, открыть в почтовом клиенте во избежание проблем с кодировкой и перейти по ссылке.
После подтверждения почты можно будет зайти в админку:
Заготовка главной страницы админ-панели примерно такая же, как и у сайта. Остались сущие пустяки – написать приложение.
Бонус: редирект с/на www
Для этого я немного поступлюсь гибкостью настройки и пропишу правила маршрутизации явно:
- traefik.http.routers.yii2adv.rule=HostRegexp(`^(www\.|admin\.)?example\.com$`)
Без www → www
Признаюсь, что я конкретно затупил, поскольку зачем-то пытался экранировать точки в redirectregex.replacement
. Этого делать не надо! Однако чтобы не допустить зацикливания, придется хардкодить или еще что-то придумывать с хостом без www (например использовать отдельный маршрут). Добавляем набор меток в compose.yaml:
- traefik.http.routers.yii2adv.middlewares=non-www-redir
- traefik.http.middlewares.non-www-redir.redirectregex.regex=^http://example\.com/(.*)
- traefik.http.middlewares.non-www-redir.redirectregex.replacement=http://www.example.com/$${1}
- traefik.http.middlewares.non-www-redir.redirectregex.permanent=true
Удвоение $$ нужно для экранирования. В данном случае ${1}
означает первую группу регулярного выражения, то есть путь в URL.
www → без www
Здесь просто – в .env прописываем FRONTEND_HOSTNAME = example.com
, а метки будут такими:
- traefik.http.routers.yii2adv.middlewares=www-redir
- traefik.http.middlewares.www-redir.redirectregex.regex=^(https?)://www\.(.*)
- traefik.http.middlewares.www-redir.redirectregex.replacement=$${1}://$${2}
- traefik.http.middlewares.www-redir.redirectregex.permanent=true
Перенаправляем все, что начинается на http://www.
или https://www.
на схему (первая группа – ${1}
) и хост с путем (${2}
). Это универсальное правило, можно подключать такое «промежуточное ПО» к любому сайту. Хотя если схема заведомо https, то в качестве микрооптимизации лучше убрать первую группу: ^https://www\.(.*)
и https://$${1}
.
Заключение
Актуальный исходный код примера можно посмотреть в репозитории:
https://git.dmkos.ru/containers/php/-/tree/main/linux/lighttpd/examples/yii2-app-advanced
Или по состоянию на момент публикации (на случай, если я в будущем внесу какие-то изменения):
Надеюсь, было интересно и полезно, а Lighttpd продолжил вас приятно удивлять.
Категория: Программирование, веб | Опубликовано 30.09.2025
Похожие материалы
Веб-сервер на FreeBSD с использованием клеток
Здесь вам не Докер, а клетки (jails) - будем говорить, это контейнеры FreeBSD, когда это еще не было мейнстримом (на минуточку, они появились еще во FreeBSD 4.x - 2000 год). Практический смысл в моем случае - неким образом изолированно использовать разные версии PHP, ну и чуть ближе познакомиться с технологией, с которой я уже сталкивался при обзоре TrueNAS. Основано, как говорится, на реальных событиях - я переносил сайты на Drupal 7.x и Yii с сервера на Linux.