Docker как замена Open Server
Эта статья — некий симбиоз кусочков, взятых из других решений, а также некоторых знаний и понимания Docker, которые я получил в процессе изучения различных источников.
Требования⚓︎
Введение⚓︎
Начнем с небольшой аналогии для тех, кто знаком с ООП.
Примечание
Образы — это классы, то есть шаблоны приложения и его зависимостей, собранных вместе. А контейнеры — объекты этих классов, то есть экземпляры образов. Если продолжить аналогию дальше, то в PHP запись вида new Classname() равносильна команде docker run <image name>. Есть и другие аналогии. Например, в статье Как и для чего использовать Docker образы рассматриваются как операционные системы, а контейнеры — как запущенные процессы в этих системах.
Что нужно для счастья рядовому пользователю OSPanel, который захотел попробовать Docker? Вероятно, веб-сервер типа Apache, PHP 8, MySQL и что-нибудь типа PHPMyAdmin для доступа к базам данных. Этот набор называется стеком. Для каждого сервиса в нашем стеке мы создадим отдельный контейнер, с нужной конфигурацией. Контейнеры независимы и могут ничего не знать друг о друге, но при этом отлично работают вместе. Для примера мы создадим проект на основе Apache, PHP 8.3, MariaDB и Adminer (в качестве лёгкой альтернативы PHPMyAdmin).
Вы убедитесь, что не нужно быть системным администратором или DevOps-разработчиком, чтобы иметь базовое понимание, как использовать Docker в своей работе.
Примечание
Более детально разобраться в Docker вам поможет бесплатный курс Docker для начинающих + практический опыт.
Структура каталогов⚓︎
Создайте следующую структуру в корневом каталоге вашего проекта:
.
├── src/
├── docker/
│ ├── db/
│ │ └── mariadb/
│ │ └── my.cnf
│ └── server/
│ ├── apache/
│ │ └── sites-enabled/
│ │ └── site.conf
│ ├── php/
│ │ └── php.ini
│ └── Dockerfile
├── .env
└── compose.yaml
Папка src будет содержать исходный код нашего проекта. В папке docker спрячем конфигурационные файлы наших сервисов. В файле .env будут указаны используемые переменные окружения. О файле compose.yaml читайте ниже.
compose.yaml⚓︎
Любое многоконтейнерное приложение Docker состоит из нескольких строчек кода в файле compose.yaml:
# Используемая версия спецификации
version: '3.9' # необязательно начиная с Docker Compose v1.27.0
services:
# Далее перечисляем используемые сервисы
Наверняка некоторые из читателей, кто пользовался OSPanel, помнят проблему, возникшую (и до сих пор возникающую у некоторых пользователей) при обновлении PHPMyAdmin, когда PHPMyAdmin отказывался работать с несовместимыми версиями PHP. В Docker любой такой сервис запускается в отдельном контейнере, именно с той версией компонентов, которые ему нужны для полноценной работы. Поэтому у вас вполне могут быть, скажем, PHP 5.6 для работы приложения (в первом контейнере), и при этом PHPMyAdmin самой свежей версии, работающая на PHP 8.2 (в другом контейнере).
В рамках этого руководства я не буду настраивать файл hosts на локальном компьютере, чтобы сделать доступ к сайту более удобным для пользователя, а буду обращаться к нему через localhost:[PORT]. Вы можете это сделать сами, добавив в файл hosts строчку типа 127.0.0.1 mysite.dev. Также самостоятельно убедитесь, что указанный вами порт не занят, и сопоставьте доступные порты вашего локального компьютера с портами контейнеров. Другие приложения, которые могут использовать те же порты, на время тестирования лучше отключить.
Начнем с добавления сервиса server, который под капотом устанавливает образ с Apache и PHP 8.3 с нужными нам расширениями:
Однако как же нам добавить конкретные расширения? Поможет Dockerfile с набором определённых команд. Потребуется лишь заменить директиву image на build и указать путь к этому файлу:
version: '3.9'
services:
server:
build:
context: .
dockerfile: ./docker/server/Dockerfile # Указываем путь к файлу Dockerfile
container_name: '${APP_NAME}-server' # Указываем имя контейнера
ports:
- '${APP_PORT}:80' # Порт, по которому сервис будет доступен снаружи:внутри контейнера
working_dir: /var/www/html # Рабочая директория внутри контейнера
environment:
- 'DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_PASS}@db_server:3306/${MYSQL_DB}?serverVersion=${MYSQL_VER}'
volumes:
- ./src:/var/www/html # Сопоставление директории src в корне проекта с рабочей директорией сервера
- ./docker/server/apache/sites-enabled:/etc/apache2/sites-enabled # Конфигурация сайтов
- ./docker/server/php/php.ini:/usr/local/etc/php/conf.d/extra-php-config.ini # ini-файл с необходимыми нам настройками PHP
Примечание
Любой вложенный подпункт в .yml-файле должен содержать 2 или 4 пробела в качестве отступа — придерживайтесь единого стиля во всём файле.
Обратите внимание на секцию volumes. Это так называемые тома, обозначаемые с помощью записи вида <путь в папке проекта>:<путь внутри контейнера>. Такое сопоставление называется пробросом. Пробрасывать можно как файлы, так и директории. При запуске контейнера Докер пробросит локальные файлы и директории на место соответствующих файлов и директорий в файловой системе контейнера. Любые изменения, производимые внутри volumes, применяются как внутри контейнера, так и снаружи.
Вот содержимое docker/server/Dockerfile:
FROM php:8.3-apache
RUN a2enmod rewrite && \
curl -sSLf \
-o /usr/local/bin/install-php-extensions \
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
chmod +x /usr/local/bin/install-php-extensions && \
install-php-extensions gd pdo_mysql bcmath zip intl opcache @composer
Вот как может выглядеть ваш файл .env:
APP_NAME=my_project
APP_PORT=8101
APP_DB_ADMIN_PORT=8102
DB_PORT=33016
MYSQL_ROOT_PASS=root
MYSQL_USER=user
MYSQL_PASS=pass
MYSQL_DB=test_db # База данных, которая будет создана при первоначальном запуске проекта
MYSQL_VER=10.11 # Версия MariaDB
Настроим конфигурацию нашего сайта в docker/server/apache/sites-enabled/site.conf:
<VirtualHost *:80>
DocumentRoot /var/www/html/public
<Directory /var/www/html/public>
AllowOverride None
Order Allow,Deny
Allow from All
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
</Directory>
</VirtualHost>
Теперь добавим оставшиеся сервисы, db_server и db_admin:
db_server:
image: mariadb:${MYSQL_VER}
container_name: '${APP_NAME}-db'
restart: always
ports:
- '${DB_PORT}:3306'
environment:
MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASS}'
MYSQL_USER: '${MYSQL_USER}'
MYSQL_PASSWORD: '${MYSQL_PASS}'
MYSQL_DATABASE: '${MYSQL_DB}'
volumes:
- db_data:/var/lib/mysql
- ./docker/db/mariadb/my.cnf:/etc/mysql/conf.d/my.cnf
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD
interval: 5s
retries: 5
db_admin:
image: adminer
container_name: '${APP_NAME}-db-admin'
ports:
- '${APP_DB_ADMIN_PORT}:8080'
environment:
- ADMINER_DEFAULT_SERVER=db_server
- ADMINER_DESIGN=lucas-sandery
depends_on:
db_server:
condition: service_healthy
volumes:
- db_admin_data:/var/www/html
volumes:
db_data:
db_admin_data:
Мы будем использовать именованные тома (volumes) для сохранения данных. Нам нужны такие тома, потому что без них каждый раз, когда сервисный контейнер БД уничтожается, база данных уничтожается вместе с ним. Именно для этого предназначен последний раздел в файле compose.yaml. На тома ссылаются сервисы db_server и db_admin.
Также обратите внимание на раздел healthcheck:
healthcheck:
test: mysqladmin ping -h 127.0.0.1 -u root --password=$$MYSQL_ROOT_PASSWORD
interval: 5s
retries: 5
Таким образом проверяется, запущен ли наш сервер MariaDB (5 повторений, с интервалом в 5 секунд). Блок depends_on в сервисе db_admin, который инициирует эту проверку, продублируем и для сервиса server.
Осталось добавить пару файлов конфигурации, docker/server/php/php.ini и docker/db/mariadb/my.cnf:
Теперь небольшой лайфхак: переместите всю папку с вашим проектом в \\wsl.localhost\Ubuntu\home\<имя_пользователя_в_ubuntu>\projects (создайте эту директорию). Это поможет избежать задержек при выполнении скриптов (когда страница долго не отвечает, а потом отваливается с ошибкой и т. п.).
Запустим наше приложение:

Для проверки работоспособности сервера создайте файл src/public/index.php со следующим содержимым:
Теперь можно использовать адреса http://localhost:8101/ и http://localhost:8102/ для доступа к сайту и к панели управления Adminer, соответственно!
Осталось зайти в контейнер server:
удалить все файлы в директории var/www/html:
и установить приложение Symfony:

... или Laravel:

... или любое другое на ваш вкус.
Дополнительно можно установить права доступа для файлов и папок, если нужно:
chown -R www-data:www-data .
find . -type f -exec chmod 644 {} \;
find . -type d -exec chmod 755 {} \;
Для создания другого проекта копируем папку с текущим, переименовываем, вносим изменения в .env-файл, чтобы порты не пересекались, и запускаем аналогичным образом. То есть в итоге вы можете создавать независимые приложения, с отдельными базами данных (а не как в LAMP/WAMP-проектах, когда все базы данных лежат вместе), версиями PHP и т. д.
Выводы⚓︎
Таким образом, compose.yaml — некий аналог файлов composer.json и package.json (в проектах на базе Composer и NPM). Он содержит в себе список сервисов (зависимостей), которые должны быть установлены и затем запущены при запуске приложения.
Конечно, по удобству Docker далеко до Open Server, тем более для тех, кто привык к одному продукту. У каждого из них есть как плюсы, так и минусы. Данная статья не является призывом к действию (типа «удаляем OSPanel»), а лишь предлагает для ознакомления современную альтернативу на основе Docker.