Установка Laravel, Nginx и MySQL с помощью Docker Compose

В последнее время Docker часто используется для развертывания приложений, поскольку он упрощает этот процесс с помощью виртуальных контейнеров. Например, если вы используете стек LEMP (который состоит из PHPNginx и MySQL) и Laravel, Docker может значительно ускорить процедуру настройки приложения.

Docker Compose упрощает разработку, поскольку дает разработчикам возможность определять инфраструктуру, — включая сервисы приложений, сети и тома, — в едином файле. Docker Compose легко заменяет собой сразу несколько команд, в том числе docker container create и docker container run.

В этом мануале вы научитесь создавать приложение в фреймворке Laravel, используя Nginx как веб-сервер и MySQL как БД. Все это будет помещено в контейнеры Docker. Вы напишете полную конфигурацию приложения в файле docker-compose вместе с конфигурационными файлами для PHP, MySQL и Nginx.

Требования

1: Загрузка Laravel и установка зависимостей

Для начала нужно загрузить последнюю версию Laravel и установить зависимости программы, в том числе и Composer, менеджер пакетов PHP уровня приложения. Используйте Docker, чтобы не устанавливать Composer глобально.

Перейдите в домашний каталог и клонируйте последнюю версию Laravel в laravel-app:

cd ~
git clone https://github.com/laravel/laravel.git laravel-app

Затем перейдите в каталог laravel-app:

cd ~/laravel-app

Теперь через образ composer смонтируйте каталоги проекта Laravel, чтобы избежать накладок глобальной установки Composer:

docker run --rm -v $(pwd):/app composer install

Опции -v и –rm в команде docker run создают контейнер, который привязывается к текущему каталогу до тех пор, пока вы его не удалите. Содержимое каталога ~/laravel-app скопируется в контейнер, а содержимое папки vendor, которую Composer создает внутри контейнера, будет скопировано в текущий каталог.

Теперь отредактируйте привилегии каталога, все права на него нужно передать вашему пользователю sudo:

sudo chown -R $USER:$USER ~/laravel-app

Это пригодится вам позже, когда вы будете работать с Dockerfile для образа приложения, поскольку так вы сможете работать с кодом и запускать процессы в контейнере через своего пользователя sudo.

2: Создание конфигурационного файла Docker Compose

Сборка приложений с помощью Docker Compose упрощает настройку и контроль версий в инфраструктуре. Чтобы настроить приложение Laravel, давайте создадим файл docker-compose и определим в нем сервисы веб-сервера, базы данных и приложения.

Откройте файл:

nano ~/laravel-app/docker-compose.yml

В файле docker-compose нужно определить три сервиса: app, webserver и db. Вставьте в файл такой код (только укажите свой сложный пароль root в MYSQL_ROOT_PASSWORD, переменной среды  сервиса db):

version: '3'
services:
#PHP Service
app:
build:
context: .
dockerfile: Dockerfile
image: digitalocean.com/php
container_name: app
restart: unless-stopped
tty: true
environment:
SERVICE_NAME: app
SERVICE_TAGS: dev
working_dir: /var/www
networks:
- app-network
#Nginx Service
webserver:
image: nginx:alpine
container_name: webserver
restart: unless-stopped
tty: true
ports:
- "80:80"
- "443:443"
networks:
- app-network
#MySQL Service
db:
image: mysql:5.7.22
container_name: db
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: laravel
MYSQL_ROOT_PASSWORD: your_mysql_root_password
SERVICE_TAGS: dev
SERVICE_NAME: mysql
networks:
- app-network
#Docker Networks
networks:
app-network:
driver: bridge

В файл входят следующие сервисы:

  • app: определение сервиса, которое содержит приложение Laravel и запускает кастомный образ Docker, его мы определим позже. Также оно присваивает значение /var/www для параметру working_dir в контейнере.
  • webserver: загружает образ nginx:alpine и открывает порты 80 и 443.
  • db: извлекает образ mysql:5.7.22 и определяет новые переменные среды, в том числе БД laravel для приложения и пароль пользователя root этой БД. Вы можете использовать любое имя для базы данных. Также вам следует заменить your_mysql_root_password собственным паролем. Это определение также связывает порт хоста 3306 и такой же порт контейнера .

Свойство container_name определяет имя контейнера, которое должно совпадать с именем сервиса. Если вы не определите это свойство, Docker будет присваивать контейнерам случайные имена (по умолчанию он выбирает имя исторической личности и случайное слово, разделяя их символом подчеркивания).

Для простоты взаимодействия между контейнерами сервисы подключаются к соединительной сети app-network. Соединительная сеть использует программный мост, который позволяет подключенным к этой сети контейнерам взаимодействовать друг с другом. Драйвер устанавливает правила хоста автоматически, чтобы контейнеры из разных соединительных сетей не могли взаимодействовать напрямую. Это повышает безопасность приложений, поскольку взаимодействовать в таких условиях смогут только связанные сервисы. Кроме того, так вы сможете указать разные сети и сервисы, подключающиеся к функциям: например, клиентские сервисы приложениймогут использовать сеть frontend, а серверные — сеть backend.

3: Постоянное хранение данных

Docker предоставляет удобные средства для постоянного хранения данных. Для этого в данном тестовом приложении мы будем использовать тома и монтируемые образы. Мы будем хранить файлы базы данных, приложения и конфигурации. Тома очень гибкие в резервном копировании, их легко сохранять по истечении жизненного цикла контейнера, а монтируемые образы упрощают редактирование кода во время разработки и немедленно отражают изменения файлов или каталогов хоста в контейнерах. Мы используем оба варианта.

Важно! Монтируемые образы позволяют менять файловую систему хоста через работающие в контейнерах процессы, в том числе создавать, изменять и удалять важные системные файлы и каталоги. Это может повлиять на процессы в системе хоста, не связанные с Docker. Монтируемые образы следует использовать очень осторожно.
Для постоянного сохранения базы данных MySQL определите том dbdata в файле docker-compose,  в определении сервиса db:

...
#MySQL Service
db:
...
volumes:
- dbdata:/var/lib/mysql
networks:
- app-network
...

Том dbdata используется для постоянного сохранения содержимого /var/lib/mysql в контейнере. Это позволяет останавливать и перезапускать сервис db, не теряя данных.

Вставьте в конец файла определение тома dbdata:

...
#Volumes
volumes:
dbdata:
driver: local

Теперь можно использовать этот том для разных сервисов.

Затем добавьте к сервису db привязку монтируемого образа для конфигурационных файлов MySQL, которые мы создадим позже.

...
#MySQL Service
db:
...
volumes:
- dbdata:/var/lib/mysql
- ./mysql/my.cnf:/etc/mysql/my.cnf
...

Это привяжет файл ~/laravel-app/mysql/my.cnf к каталогу /etc/mysql/my.cnf в контейнере.

А теперь добавьте монтируемые образы в сервис webserver. Образов будет два: один для кода приложения, а второй — для определения настроек Nginx.

#Nginx Service
webserver:
...
volumes:
- ./:/var/www
- ./nginx/conf.d/:/etc/nginx/conf.d/
networks:
- app-network

Первый образ привязывает код приложения из каталога ~/laravel-app к каталогу /var/www внутри контейнера. Конфигурационный файл, добавляемый в ~/laravel-app/nginx/conf.d/, также монтируется в папку /etc/nginx/conf.d/ в контейнере, что позволяет менять содержимое каталога по мере необходимости.

Теперь добавьте следующие привязки образов в сервис app для кода приложения и конфигурационных файлов:

#PHP Service
app:
...
volumes:
- ./:/var/www
- ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
networks:
- app-network

Сервис app привязывает монтируемый образ каталога ~/laravel-app, который содержит код приложения, к /var/www. Это позволяет ускорить разработку, поскольку все изменения в локальном каталоге приложения сразу же отразятся в контейнере. Также конфигурационный файл PHP ~/laravel-app/php/local.ini привязывается к файлу /usr/local/etc/php/conf.d/local.ini в контейнере. Мы создадим локальный конфигурационный файл PHP в разделе 5.

Теперь файл docker-compose имеет такой вид:

version: '3'
services:
#PHP Service
app:
build:
context: .
dockerfile: Dockerfile
image: digitalocean.com/php
container_name: app
restart: unless-stopped
tty: true
environment:
SERVICE_NAME: app
SERVICE_TAGS: dev
working_dir: /var/www
volumes:
- ./:/var/www
- ./php/local.ini:/usr/local/etc/php/conf.d/local.ini
networks:
- app-network
#Nginx Service
webserver:
image: nginx:alpine
container_name: webserver
restart: unless-stopped
tty: true
ports:
- "80:80"
- "443:443"
volumes:
- ./:/var/www
- ./nginx/conf.d/:/etc/nginx/conf.d/
networks:
- app-network
#MySQL Service
db:
image: mysql:5.7.22
container_name: db
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: laravel
MYSQL_ROOT_PASSWORD: your_mysql_root_password
SERVICE_TAGS: dev
SERVICE_NAME: mysql
volumes:
- dbdata:/var/lib/mysql/
- ./mysql/my.cnf:/etc/mysql/my.cnf
networks:
- app-network
#Docker Networks
networks:
app-network:
driver: bridge
#Volumes
volumes:
dbdata:
driver: local

Сохраните и закройте файл.

Теперь пора создать пользовательский образ вашего приложения.

4: Создание Dockerfile

Docker позволяет настраивать среду внутри отдельных контейнеров с помощью файла Dockerfile. Файл Dockerfile создает пользовательские образы, которые затем можно использовать для установки зависимостей приложения и изменения настроек. Вы можете загружать созданные образы в Docker Hub или в частный реестр.

Файл Dockerfile должен находиться в каталоге ~/laravel-app. Создайте этот файл:

nano ~/laravel-app/Dockerfile

Этот Dockerfile будет определять базовый образ и необходимые команды для сборки образа приложения Laravel. Добавьте в файл такие строки:

FROM php:7.2-fpm
# Copy composer.lock and composer.json
COPY composer.lock composer.json /var/www/
# Set working directory
WORKDIR /var/www
# Install dependencies
RUN apt-get update && apt-get install -y \
build-essential \
mysql-client \
libpng-dev \
libjpeg62-turbo-dev \
libfreetype6-dev \
locales \
zip \
jpegoptim optipng pngquant gifsicle \
vim \
unzip \
git \
curl
# Clear cache
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
# Install extensions
RUN docker-php-ext-install pdo_mysql mbstring zip exif pcntl
RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
RUN docker-php-ext-install gd
# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Add user for laravel application
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www
# Copy existing application directory contents
COPY . /var/www
# Copy existing application directory permissions
COPY --chown=www:www . /var/www
# Change current user to www
USER www
# Expose port 9000 and start php-fpm server
EXPOSE 9000
CMD ["php-fpm"]

Сначала Dockerfile создает образ на основе образа php:7.2-fpm. Это образ Debian с предустановленным экземпляром PHP FastCGI PHP-FPM. Также этот файл устанавливает необходимые пакеты для Laravel: mcrypt, pdo_mysql, mbstring,  imagick и composer.

Директива RUN определяет команды для обновления, установки и настройки параметров внутри контейнера, включая выделенного пользователя и группу www. Директива  WORKDIR указывает рабочий каталог приложения (в данном случае это каталог /var/www ).

Используя отдельного пользователя и группу с ограниченными правами доступа, вы снижаете уязвимости при запуске контейнеров Docker (по умолчанию они запускаются с правами root). Чтобы не запускать контейнер с привилегиями root, мы создали пользователя www с правами на чтение и запись для каталога /var/www.
Это делается с помощью команды COPY и флага —chown для копирования прав каталога приложения.

Команда EXPOSE открывает в контейнере порт 9000 для сервера php-fpm. CMD определяет команду, которая будет запускаться после создания контейнера. Здесь CMD содержит команду php-fpm, которая запускает сервер.

Сохраните и закройте файл.

Теперь пора определить конфигурации PHP.

5: Настройка PHP

Итак, мы определили инфраструктуру в файле docker-compose. Теперь можно настроить сервис PHP в качестве процессора для входящих запросов Nginx.

Для настройки PHP нужно создать файл local.ini в каталоге php. Это файл, который в разделе 2 мы привязали к файлу /usr/local/etc/php/conf.d/local.ini в контейнере. Имея этот файл, вы сможете игнорировать файл по умолчанию php.ini, который PHP считывает при запуске.

Создайте каталог php:

mkdir ~/laravel-app/php

Затем откройте файл local.ini:

nano ~/laravel-app/php/local.ini

Чтобы продемонстрировать настройку PHP, мы добавим следующий код для установки ограничений размера выгруженных файлов:

upload_max_filesize=40M
post_max_size=40M

Директивы upload_max_filesize и post_max_size задают максимальный допустимый размер выгружаемых файлов и показывают, как задавать конфигурации php.ini из local.ini. Все параметры конфигурации PHP, которые вы хотите игнорировать, можно поместить в файл local.ini.

Сохраните и закройте файл.

Теперь пора настроить веб-сервер.

6: Настройка Nginx

Теперь можно настроить Nginx на поддержку PHP-FPM в качестве сервера FastCGI для обслуживания динамического контента. Сервер FastCGI разработан на основе двоичного протокола для взаимодействия интерактивных программ с веб-сервером.

Для Nginx нужно создать в папке ~/laravel-app/nginx/conf.d/ файл app.conf с конфигурацией сервисов.

Создайте каталог nginx/conf.d/:

mkdir -p ~/laravel-app/nginx/conf.d

Затем создайте файл app.conf:

nano ~/laravel-app/nginx/conf.d/app.conf

Добавьте в файл такой код, чтобы определить конфигурацию Nginx:

server {
listen 80;
index index.php index.html;
error_log  /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /var/www/public;
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass app:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
gzip_static on;
}
}

Блок server задает конфигурацию веб-сервера Nginx с помощью директив:

  • listen: определяет порт, который сервер прослушивает для получения входящих запросов.
  • error_log и access_log: определяют файлы для записи логов.
  • root: определяет путь к корневому каталогу, формируя полный путь к любому запрошенному файлу в локальной файловой системе.

В блоке location для php директива fastcgi_pass указывает, что сервис app прослушивает сокет TCP по порту 9000. Благодаря этому сервер PHP-FPM прослушивает запросы через сеть, а не через сокет Unix. Сокет Unix имеет небольшое преимущество в скорости по сравнению с сокетом TCP, однако у него нет сетевого протокола и он пропускает сетевой стек. Если хосты находятся в одной системе, использование сокета Unix может иметь смысл, но если сервисы работают на разных хостах, сокет TCP гораздо лучше, поскольку позволяет подключаться к распределенным сервисам. Поскольку контейнеры app и webserver работают на разных хостах, в данной конфигурации сокет TCP будет эффективнее.

Сохраните и закройте файл.

Благодаря привязке, созданной в разделе 2, все изменения в каталоге nginx/conf.d/ прямо отразятся в контейнере webserver.

7: Настройка MySQL

Настроив PHP и Nginx, вы можете включить MySQL как базу данных для приложения.

Для MySQL нужно создать файл my.cnf в каталоге mysql. Этот файл мы привязали к файлу /etc/mysql/my.cnf внутри контейнера в разделе 2. Привязка монтируемого образа позволяет игнорировать все параметры my.cnf, когда это потребуется.

Чтобы посмотреть, как это работает, давайте добавим в файл my.cnf параметры, которые включают лог общих запросов и задают лог-файл.

Создайте каталог mysql:

mkdir ~/laravel-app/mysql

Создайте файл my.cnf:

nano ~/laravel-app/mysql/my.cnf

Поместите в файл следующий код, чтобы включить лог запросов и задать расположение лога:

[mysqld]
general_log = 1
general_log_file = /var/lib/mysql/general.log

Файл my.cnf включает лог, задавая параметру general_log значение 1. Параметр general_log_file указывает, где будут храниться логи.

Сохраните файл и закройте его.

Все готово, пора запускать контейнеры.

8: Запуск контейнеров и изменение настроек среды

Итак, мы определили все сервисы в файле docker-compose и создали конфигурации для всех сервисов. Теперь можно запустить контейнеры. В качестве последнего шага мы создадим копию файла .env.example, который Laravel включает по умолчанию, и назовем ее .env, поскольку именно такой файл Laravel использует для определения среды:

cp .env.example .env

После запуска контейнеров мы вставим в этот файл параметры.

Теперь все сервисы определены в файле docker-compose, и вам нужно просто запустить одну команду, которая запустит все контейнеры, создаст тома и настройки и подключит сети:

docker-compose up -d

При первом запуске команда docker-compose up загрузит все необходимые образы Docker, что может занять некоторое время. После загрузки образов на локальный компьютер Compose создаст контейнеры. Флаг -d преобразует процесс в демон, что позволяет поддерживать работу контейнеров в фоновом режиме.

После завершения этого процесса используйте следующую команду, чтобы запросить список всех запущенных контейнеров:

docker ps

Вы увидите следующий вывод с данными о контейнерах app, webserver и db:

CONTAINER ID        NAMES               IMAGE                             STATUS              PORTS
c31b7b3251e0        db                  mysql:5.7.22                      Up 2 seconds        0.0.0.0:3306->3306/tcp
ed5a69704580        app                 digitalocean.com/php              Up 2 seconds        9000/tcp
5ce4ee31d7c0        webserver           nginx:alpine                      Up 2 seconds        0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp

В этом выводе CONTAINER ID — это уникальный идентификатор контейнера, а NAMES перечисляет имена сервисов. Вы можете использовать оба эти идентификатора для доступа к контейнерам. IMAGE определяет имя образа каждого контейнера, а STATUS предоставляет данные о состоянии.

Теперь вы можете отредактировать файл .env в контейнере app, чтобы добавить необходимые параметры.

Откройте файл с помощью docker-compose exec, что позволяет запускать определенные команды в контейнерах. В данном случае команда откроет файл для редактирования:

docker-compose exec app nano .env

Найдите блок DB_CONNECTION и определите в нем особенности настройки вашей системы. Нужно изменить следующие поля:

  • DB_HOST – нужно указать контейнер базы данных db.
  • DB_DATABASE – укажите здесь БД laravel.
  • DB_USERNAME – укажите имя пользователя БД. В этом случае мы используем laraveluser.
  • DB_PASSWORD – надежный пароль этого пользователя.

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laraveluser
DB_PASSWORD=your_laravel_db_password

Сохраните и закройте файл.

Затем настройте ключ для приложения Laravel с помощью команды php artisan key:generate. Она сгенерирует ключ и скопирует его в файл .env, что защитит сессии пользователя и шифрованные данные:

docker-compose exec app php artisan key:generate

Теперь для запуска приложения у вас есть все необходимые настройки среды. Чтобы кэшировать эти настройки для ускорения загрузки приложения, запустите команду:

docker-compose exec app php artisan config:cache

Конфигурации будут загружены в /var/www/bootstrap/cache/config.php в контейнере.

Теперь откройте в браузере сайт http://your_server_ip. На экране появится главная страница приложения Laravel.

Теперь можно перейти к настройке данных пользователя БД  laravel в контейнере db.

9: Создание пользователя MySQL

Установка MySQL по умолчанию предоставляет только учетную запись root с неограниченными привилегиями доступа к серверу СУБД. Обычно при работе с базой данных лучше не использовать администратора, root. Вместо этого лучше создать специального пользователя для базы данных нашего приложения.

Чтобы создать его, запустите интерактивную оболочку bash в контейнере db с помощью команды docker-compose exec:

docker-compose exec db bash

Внутри контейнера войдите в MySQL как root:

mysql -u root -p

Вам будет предложено ввести root пароль MySQL, заданный в файле docker-compose.

Для начала проверьте наличие базы данных laravel, которую вы определили в файле docker-compose. Запустите show databases для проверки существующих баз данных:

show databases;

В выводе вы должны увидеть БД laravel:

+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

Затем создайте пользователя, у которого будет доступ к этой базе данных. Мы используем имя laraveluser, но вы можете выбрать другое. Просто убедитесь, что имя пользователя и пароль соответствуют учетным данным, указанным ранее в файле .env.

GRANT ALL ON laravel.* TO 'laraveluser'@'%' IDENTIFIED BY 'your_laravel_db_password';

Сбросьте привилегии, чтобы сообщить серверу MySQL об изменениях:

FLUSH PRIVILEGES;

Закройте MySQL:

EXIT;

Выйдите из контейнера:

exit

10: Миграция данных и знакомство с консолью Tinker

Теперь приложение запущено, вы можете выполнить миграцию данных и поэкспериментировать с командой tinker, которая запускает консоль PsySH с загруженным приложением Laravel. PsySH — это консоль среды выполнения и интерактивный отладчик PHP, а Tinker — это REPL для Laravel. Команда tinker позволяет взаимодействовать с приложением Laravel из командной строки в интерактивной оболочке.

Сначала протестируйте соединение с MySQL с помощью команды Laravel artisan migrate, которая создаст в БД таблицу migrations внутри контейнера:

docker-compose exec app php artisan migrate

Эта команда выполняет миграцию таблиц Laravel. В случае успешной миграции вы увидите такой вывод:

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table

После завершения миграции вы можете отправить запрос для проверки подключения к БД:

docker-compose exec app php artisan tinker

Проверьте соединение MySQL:

\DB::table('migrations')->get();

Вы получите такой результат:

=> Illuminate\Support\Collection {#2856
all: [
{#2862
+"id": 1,
+"migration": "2014_10_12_000000_create_users_table",
+"batch": 1,
},
{#2865
+"id": 2,
+"migration": "2014_10_12_100000_create_password_resets_table",
+"batch": 1,
},
],
}

Вы можете использовать tinker для взаимодействия с базами данных и для экспериментов с сервисами и моделями.

Теперь приложение Laravel работает, а вы готовы к дальнейшей разработке и экспериментам.

Заключение

Теперь на вашем сервере есть рабочее приложение на основе стека LEMP.

Docker Compose упрощает установку, так как позволяет с помощью одной команды создавать группы контейнеров Docker, определенные в одном файле.

Читайте также:

Tags: , , , , , , , , ,
  • Andrey Shutov

    на 8м этапе выдает ошибку service must be a mapping, not a NoneType. ругается на docker-compose.yml.
    в чем может быть беда?