Особенностью шаблона Advanced PHP-фреймворка Yii 2 является разделение на фронт-офис (сайт) и бэк (панель управления). Следовательно, веб-сервер должен обслуживать два хоста. В продолжение темы про Lighttpd рассмотрим, как это можно устроить.

Для демонстрации я конечно же возьму свой свежеиспеченный образ Docker. Зря делал, что ли? wink

Начальная конфигурация 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

И через некоторое время открываем сайт:

my_yii_application.png

Наконец-то что-то новое smile – Symfony Demo уже как-то поднадоела. Регистрируемся (Signup), после чего надо каким-то образом верифицировать электронную почту. По умолчанию письма сохраняются во frontend/runtime/mail. Лучше всего скачать файл с письмом, открыть в почтовом клиенте во избежание проблем с кодировкой и перейти по ссылке.

После подтверждения почты можно будет зайти в админку:

login.png
admin_home.png

Заготовка главной страницы админ-панели примерно такая же, как и у сайта. Остались сущие пустяки pardon – написать приложение.

Бонус: редирект с/на www

Для этого я немного поступлюсь гибкостью настройки и пропишу правила маршрутизации явно:

      - traefik.http.routers.yii2adv.rule=HostRegexp(`^(www\.|admin\.)?example\.com$`)

Без www → www

Признаюсь, что я конкретно затупил, поскольку зачем-то пытался экранировать точки в redirectregex.replacement fool. Этого делать не надо! Однако чтобы не допустить зацикливания, придется хардкодить или еще что-то придумывать с хостом без 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

Или по состоянию на момент публикации (на случай, если я в будущем внесу какие-то изменения):

https://git.dmkos.ru/containers/php/-/tree/lighttpd-yii2-app-advanced/linux/lighttpd/examples/yii2-app-advanced

Надеюсь, было интересно и полезно, а Lighttpd продолжил вас приятно удивлять.


Категория: Программирование, веб | Опубликовано 30.09.2025

Похожие материалы

Веб-сервер на FreeBSD с использованием клеток

Здесь вам не Докер, а клетки (jails) - будем говорить, это контейнеры FreeBSD, когда это еще не было мейнстримом (на минуточку, они появились еще во FreeBSD 4.x - 2000 год). Практический смысл в моем случае - неким образом изолированно использовать разные версии PHP, ну и чуть ближе познакомиться с технологией, с которой я уже сталкивался при обзоре TrueNAS. Основано, как говорится, на реальных событиях - я переносил сайты на Drupal 7.x и Yii с сервера на Linux.


Комментарии, обсуждение