Бесплатная миграция IT-инфраструктуры в облако

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

Никита Кулижников
Никита Кулижников
Технический писатель
22 июня 2022 г.
12518
13 минут чтения
Средний рейтинг статьи: 2.6

Контейнеризация — способ запускать приложение со всеми зависимостями в определённой среде, которая не влияет на операционную систему. Таким образом ни одно приложение не зависит ни от другого приложения, ни от глобального окружения. 

Если на вашем сервере, например, установлен php версии 8.1, но по каким-то причинам нужно запустить старый проект, вы можете пойти двумя путями. Первое, что приходит в голову — настроить ещё один сервер, «развернуть» на нём веб-окружение с php версии 5.6, обновить все пакеты и зависимости, где нужно — переписать код. Но есть способ проще: достаточно создать контейнер, окружение которого не будет влиять на всю остальную систему. 

Приведём ещё один пример. Допустим, вы разрабатываете веб-сайт, который посещают люди. Но ваш сервер постепенно выходит из строя и нуждается в обновлении. Вам нужно перенести проект на другую машину, при этом сохранив бесперебойность работы сайта. Решение «в лоб» — построить другой сервер и скопировать файлы на него. При этом нужно обеспечить целостность базы данных, настройки серверного окружения и так далее. Непосредственно переход вы запланируете на какую-нибудь ночь, а после миграции будете чинить проект до самого утра. Однако, если бы ваш проект представлял собой контейнер, вы бы без проблем запустили его на любом сервере той же ОС. 

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

Однако не стоит путать контейнеризацию с виртуализацией. Виртуализация — по сути отдельное устройство со своими оборудованием и операционной системой. Среда контейнера же запускается из ядра основной операционной системы, значит она может работать только в основной ОС. Контейнеры потребляют меньше ресурсов. 

Для разработки и запуска таких контейнерных приложений используют Docker. Он позволяет автоматизировать процессы создания и развёртывания, упрощая задачи программистам и администраторам. Подробнее о Docker и контейнеризации можно почитать в нашей статье «Что такое Docker».

В этой статье мы рассмотрим контейнеризацию на примере стека LEMP: Linux, Nginx, MySQL и PHP. Для демонстрации работы с зависимостями веб-приложений установим Laravel. 

Нам понадобится сервер с установленной ОС Ubuntu, для этого мы заказали облачное решение на timeweb.cloud и настроили пользователю привилегии sudo. Кроме того, на сервере должны быть установлены Docker и Docker Compose.

Установка Laravel

Laravel — бесплатный фреймворк с открытым исходным кодом. Его используют для создания архитектурно сложных веб-приложений. Загрузим в домашнюю директорию из GitHub актуальную версию:

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

Смонтируем образ composer в каталог проекта Laravel, поскольку крайне не рекомендуется устанавливать Composer глобально и запускать из-под суперпользователя:

cd laravel
docker run -v $PWD:/app composer install

Этой командой мы создаём виртуальный контейнер, который будет привязан к текущему каталогу; всё, что находится внутри каталога laravel, скопировано в контейнер. Каталог vendor, который создал менеджер зависимостей Composer в рамках команды install, также скопирован в текущий каталог, то есть в laravel.

Затем изменим права на каталог так, чтобы к нему был доступ у обычного пользователя, поскольку нам понадобится запускать процессы в контейнере без root-привилегий.

sudo chown -R username:username ~/laravel

Теперь можно приступать к работе со службами с помощью Docker Compose.

Файл Docker Compose

Для решения задач, связанных с развёртыванием проектов, которые используют несколько сервисов, разработан инструмент Docker Compose. Если Docker применяется для управления отдельными контейнерами, то Docker Compose позволяет работать с приложениями со сложной архитектурой и множеством зависимостей. 

Для настройки приложения Laravel создадим файл docker-compose.yml и определим необходимые службы — веб-сервер и базу данных. 

nano ~/laravel/docker-compose.yml
version: '3'
services:

  # PHP Service
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: php:8.1
    container_name: app
    restart: unless-stopped
    tty: true
    environment:
      SERVICE_NAME: app
      SERVICE_TAGS: dev
    working_dir: /var/www
volumes:
- ./laravel/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:
      - /home/username/laravel:/var/www
      - ./nginx/conf.d:/etc/nginx/conf.d
    networks:
      - app-network

  # MySQL Service
  db:
    image: mysql:5.7.22
    volumes:
      - ./mysql:/var/lib/mysql
    container_name: db
    restart: unless-stopped
    tty: true
    ports:
      - "3306:3306"
    environment:
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
     MYSQL_ROOT_PASSWORD: p@$$w0rd
    networks:
      - app-network

# Docker Networks
networks:
  app-network:
    driver: bridge

Подробно разберём некоторые ключевые моменты в этом файле: 

  1. app. Содержит приложение Laravel и запускает образ php, который мы определим в дальнейшем. Также определяет рабочую директорию — /var/www.
  2. webserver. Содержит образ nginx:alpine и определяет порты 80 (http) и 443 (https) для работы веб-сервера. 
  3. db. Указываем образ mysql из Docker Hub. Кроме этого определяем переменные среды — базу данных laravel, пароль (в нашем случае — p@$$w0rd) и порт.

Если Docker Hub недоступен, можно использовать наш бесплатный прокси, который возобновляет этот доступ. 

Обратите внимание, что если вы не укажете имя контейнера container_name, Docker сделает это за вас. При этом он присвоит контейнерам имя исторической личности и случайного слова — это значительно усложнит администрирование вашего приложения. 

Для того чтобы организовать взаимодействие между контейнерами, подключим службы к соединительной сети app-network. Так приложения из разных сетей не смогут взаимодействовать друг с другом — это значительно повысит безопасность проекта. 

Далее приступим к настройке основных служб.

kube

Создание Dockerfile

С помощью специального файла Dockerfile можно указать, как собирать образы, на основе которых создаётся контейнер. Этот файл содержит инструкции и аргументы, которые при работе с Docker обрабатываются сверху вниз. 

Image2

Наш Dockerfile расположим в корне проекта:

nano ~/laravel/Dockerfile
FROM php:8.1-fpm

# Устанавливаем рабочую директорию 
WORKDIR /var/www

# Копируем composer.lock и composer.json
COPY composer.lock composer.json /var/www/
# Устанавливаем зависимости
RUN apt-get update && apt-get install -y \
    build-essential \
    libpng-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    zip \
    jpegoptim optipng pngquant gifsicle \
    vim \
    unzip \
    git \
    curl \
    libpq-dev \
    libonig-dev \
    libzip-dev && \
    docker-php-ext-configure gd --with-freetype --with-jpeg && \
    docker-php-ext-install pdo_mysql mbstring zip exif pcntl gd && \
    apt-get clean && rm -rf /var/lib/apt/lists/*
# Загружаем актуальную версию Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
# Создаём пользователя и группу www для приложения Laravel
RUN groupadd -g 1000 www
RUN useradd -u 1000 -ms /bin/bash -g www www
# Копируем содержимое текущего каталога в рабочую директорию
COPY . /var/www
COPY --chown=www:www . /var/www
# Меняем пользователя на www
USER www
# В контейнере открываем 9000 порт и запускаем сервер php-fpm
EXPOSE 9000CMD ["php-fpm"]

В первой строке мы указываем, что нужно создать образ PHP-FPM версии 8.1. Кроме того, установим нужные для работы приложения Laravel пакеты: pdo_mysql, mbstring, imagick и composer

В качестве аргументов директивы RUN укажем команды для установки и обновления зависимостей. А также создадим пользователя и группу www для работы с приложением Laravel. Так мы снизим риски при запуске контейнера Docker, поскольку иначе они бы запускались с правами суперпользователя. 

С помощью Workdir укажем рабочий каталог /var/www и зададим необходимые права для нового пользователя www, воспользовавшись командой COPY. Директивой EXPOSE откроем 9000 порт для сервера php-fpm

После создания контейнера нужно запустить php-fpm сервер, для этого используем команду CMD

Конфигурация служб

Чаще всего для веб-приложений достаточно основных файлов конфигурации служб. В рамках нашего примера мы всё же переопределим базовые настройки для демонстрации возможностей контейнеризации. 

PHP

После того, как мы определили конфигурацию контейнера, нужно настроить PHP для обработки входящих запросов NGINX. Для начала нужно создать файл local.ini, путь к нему мы указывали в файле docker-compose.yml/usr/local/etc/php/conf.d/local.ini. Если мы не создадим этот файл, контейнер будет использовать стандартный файл конфигурации PHP: php.ini

В директории приложения laravel создаём каталог php и файл local.ini в нём:

mkdir ~/laravel/php 
nano ~/laravel/php/local.ini

В этот файл вставим директиву для отображения всех ошибок:

error_reporting = E_ALL

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

Прокси-сервер Nginx

В нашем примере мы используем NGINX в качестве прокси-сервера. Это значит, что все входящие запросы будут асинхронно передаваться на бэкенд (в нашем случае — PHP-FPM).

По аналогии с настройкой PHP мы также создадим в каталоге laravel файл /nginx/conf.d/app.conf:

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

Внутри файла определим конфигурацию прокси-сервера:

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 используем следующие директивы:

  1. Listen — порт, на котором сервер будет отлавливать запросы. 
  2. Index — индексные файлы веб-приложения. 
  3. Error_log, access_log — файлы для записи журналов ошибок и запросов к серверу. 
  4. root — корневой каталог веб-приложения.

Внутри первого блока location определим настройки PHP. В частности, укажем, что служба app прослушивает 9000 порт. Директива fastcgi_pass указывает, что сервер PHP-FPM должен выполнять прослушивание через сокет TCP. Второй блок location задаёт правила перенаправления всех запросов на индексный файл index.php с сохранением параметров. 

Настройка MySQL

Настройка MySQL осуществляется аналогично конфигурации веб-сервера и PHP. Сначала мы создадим файл в каталоге, который указали внутри файла docker-compose.yml:

mkdir ~/laravel/mysql
nano ~/laravel/mysql/my.cnf

В этом файле определим общие правила журналирования:

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

Запуск контейнера

После того, как мы определили конфигурацию для основных служб, можно запускать контейнеры. Но перед этим укажем настройки приложения Laravel в специальном файле .env. Внутри каталога Laravel уже есть пример такого файла, который поставляется в дистрибутиве — .env.example. Копируем его и указываем свои настройки там, где это нужно:

cd ~/laravel
cp .env.example .env
nano .env

Основная настройка для приложения — доступ для подключения к базе данных и адрес сервера. В файле найдём строку APP_URL и укажем домен или IP-адрес нашего сайта, далее в блоке DB_CONNECTION укажем наши данные:

APP_URL=http://domain.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=p@$$w0rd
MYSQL_ROOT_PASSWORD=p@$$w0rd

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

docker-compose up -d

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

Вы можете проверить корректность запуска всех процессов с помощью команды docker ps. Она отобразит список контейнеров, их уникальные идентификаторы, имена, образы и статусы.  

Ключ приложения Laravel

Для корректной работы приложения Laravel мы должны сгенерировать ключ APP_KEY в .env-файл. Для этого воспользуемся docker-compose exec, эта утилита позволяет выполнять команды в конкретных контейнерах:

docker-compose exec app php artisan key:generate

… и сохраним настройки в кэш для ускорения загрузки:

docker-compose exec app php artisan config:cache

При кэшировании настройки будут загружены в файл /var/www/bootstrap/cache/config.php.

Теперь, если ввести IP-адрес вашего сервера в адресную строку браузера, вы увидите стартовую страницу Laravel.

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

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

По умолчанию MySQL создаётся только одна учётная запись — root. Но использовать её в веб-среде небезопасно, поскольку root-пользователь обладает неограниченными привилегиями. 

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

docker-compose exec db bash

Далее — уже внутри оболочки — нужно войти в учётную запись root:

mysql -u root -p

Поскольку при создании файла docker-compose.yml мы определили базу данных laravel, она уже создана. Убедимся в этом:

show databases;

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

Создаём учётную запись laravel с доступом только к этой базе данных. Пароль нужно указать такой же, как и в файле .env:

GRANT ALL ON laravel.* TO 'laravel'@'%' IDENTIFIED BY 'p@$$w0rd';

Обновляем привилегии, закрываем MySQL и выходим из контейнера:

mysql> FLUSH PRIVILEGES;
mysql> EXIT;
# exit

Заключение

В этой статье мы рассмотрели преимущества использования контейнеризации в работе с веб-приложениями. Среди основных плюсов — независимость приложений друг от друга и от сервера, на котором они находятся. 

Для упрощения работы с контейнерами использовали Docker Compose, который позволяет создавать группу контейнеров Docker, а не запускать каждый по отдельности. В качестве демонстрации простоты работы с контейнерами создали простое Laravel-приложение, используя стек LEMP. 

В официальном канале Timeweb Cloud собрали комьюнити из специалистов, которые говорят про IT-тренды, делятся полезными инструкциями и даже приглашают к себе работать. 

Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server
22 июня 2022 г.
12518
13 минут чтения
Средний рейтинг статьи: 2.6
Комментарии 16
Abramov
16.11.2024, 19:43

ок, может я чего не понимаю, но у меня ничего не воркает( все по инструкции сделал, контейнеры работают, а как проверить что сайт есть -незнаю

sudo docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS         PORTS                                                                      NAMES
5f5530a842b5   nginx:alpine   "/docker-entrypoint.…"   19 minutes ago   Up 4 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp   webserver
8fdbc008b85a   php:8.1        "docker-php-entrypoi…"   19 minutes ago   Up 4 minutes                                                                              app

какой, где адрес указывать надо?

Timeweb Cloud
Timeweb Cloud
18.11.2024, 13:30

Добрый день!

Если вы следовали инструкции, то сайт должен быть доступен по адресу http://<ip_сервера>.

Если при обращении по этому адресу возникает ошибка, уточните, что именно происходит — например, отображается ли какая-то страница с ошибкой, или запрос вообще не доходит до сервера. Также проверьте логи контейнера с Nginx, чтобы увидеть возможные причины:

docker logs webserver
Abramov
16.11.2024, 19:04

друзья, такая проблема, не знаю что делать, пк перезапускал, docker и docker-compose переустанавливал, ничего не помогает на этом шаге:

:~/laravel$ docker-compose up -d
Traceback (most recent call last):
  File "/usr/bin/docker-compose", line 33, in &lt;module&gt;
    sys.exit(load_entry_point('docker-compose==1.29.2', 'console_scripts', 'docker-compose')())
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/bin/docker-compose", line 25, in importlib_load_entry_point
    return next(matches).load()
           ^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/importlib/metadata/__init__.py", line 205, in load
    module = import_module(match.group('module'))
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "&lt;frozen importlib._bootstrap&gt;", line 1387, in _gcd_import
  File "&lt;frozen importlib._bootstrap&gt;", line 1360, in _find_and_load
  File "&lt;frozen importlib._bootstrap&gt;", line 1331, in _find_and_load_unlocked
  File "&lt;frozen importlib._bootstrap&gt;", line 935, in _load_unlocked
  File "&lt;frozen importlib._bootstrap_external&gt;", line 995, in exec_module
  File "&lt;frozen importlib._bootstrap&gt;", line 488, in _call_with_frames_removed
  File "/usr/lib/python3/dist-packages/compose/cli/main.py", line 9, in &lt;module&gt;
    from distutils.spawn import find_executable
ModuleNotFoundError: No module named 'distutils'

у меня ubuntu 24.04, подскажите как это решить?

Abramov
16.11.2024, 19:15

я не знаю как, но помог искусственный интелект переустановил docker-compose v2

sudo apt remove docker-compose
sudo apt install docker-compose-plugin
Влад
26.09.2024, 15:14

docker-compose up -d Building app Step 1/4 : FROM php:8.1-fpm ---> 17af27b15bfd Step 2/4 : WORKDIR /var/www ---> Using cache ---> 9ac982377b12 Step 3/4 : COPY composer.lock composer.json /var/www ERROR: Service 'app' failed to build: When using COPY with more than one source file, the destination must be a directory and end with a / . Поправьте блок где Копируем composer.lock и composer.json. Видимо в нем проблема. В чем моя ошибка? делал как нужно

Timeweb Cloud
Timeweb Cloud
26.09.2024, 17:34

Добрый день!

Спасибо за замечание! Верстку поправили. Ошибка могла возникать из-за отсутствия слэша в конце строки «COPY composer.lock composer.json /var/www/»

Александр Бакуменко
Александр Бакуменко
15.06.2024, 02:03

Здравствуйте.

Для начала нужно создать файл local.ini, путь к нему мы указывали в файле docker-compose.yml

Нет никаких упоменаний в Вашем примере файла docker-compose.yml о файле local.ini.

Команда Timeweb Cloud
Команда Timeweb Cloud
02.07.2024, 06:13

Спасибо за внимательность! Добавили блок про local.ini в docker-compose.yml.

Vsevolod M.
Vsevolod M.
28.02.2024, 11:47

Здравствуйте!

  1.  По Вашему тексту касаемо директив docker файла  "...# Устанавливаем рабочую директорию WORKDIR /var/www ...."  вопрос:  У меня на локальном компьютере стоит сервер Apache и в папке www у меня находятся папки тестовых сайтов и управлять ими я могу только как суперпользователь - система не позволит (ubuntu). Если я начну экспериментировать с правами , то система начнет либо глючить, либо запретит давать мне работать длительное время в режиме текущего пользователя а не как www-data:www-data -?  Каков выход -  ?
  2. Ваш материал  вообще применим ли для сервера Apache - ?
  3. В директиве  docker файла  указано: "...FROM php:8.1-fpm..." Но у меня нет в системе файлов с таким расширением  -? Версия сервера на  php8.1 - да.
  4.  на официальном сайте Docker -   https://docs.docker.com/guides/walkthroughs/run-a-container/ порт для контейнера  дается 8089  - ? Спасибо заранее за ответы на мои ответы.
Команда Timeweb Cloud
Команда Timeweb Cloud
29.02.2024, 08:36

Добрый день! Инструкция не подойдет для сервера Apache — она написана под стек LEMP, в котором в качестве веб-сервера используется Nginx.

Указанный путь WORKDIR /var/www задействован в качестве DOCUMENT_ROOT для приложения Laravel, которое размещается в контейнере.

Директива FROM при использовании Dockerfile задаёт родительский образ, из которого будет создан контейнер. Конкретно в данном примере директива FROM задействует образ Docker с PHP 8.1, которое работает в режиме FPM.

Порт для Docker-контейнера может быть переопределён в зависимости от необходимой задачи и используемого приложения. В документации Докера описан запуск Docker-контейнера с использованием порта 8089. В нашей же инструкции используются порты 80, 443, 3306 и 9000.

Кирилл
Кирилл
10.10.2023, 12:56

На этапе "Ключ приложения Laravel" получаю ошибку:

[+] Building 0.0s (0/0) docker:default
[+] Building 0.0s (0/0) docker:default
Could not open input file: artisan

Что я делаю не так? Под каким пользователем надо это делать?

Команда Timeweb Cloud
Команда Timeweb Cloud
11.10.2023, 08:49

Добрый день! Нам не удалось воспроизвести ошибку 🤔

Leo
Leo
12.06.2023, 10:29

даже после долгой настройки этот код не работает

Команда Timeweb Cloud
Команда Timeweb Cloud
13.07.2023, 10:38

Спасибо за сигнал, перепроверили инструкцию и доработали её 💙

BeeWay
BeeWay
14.03.2023, 15:56

Какой-то не юзабельный код. А нельзя было указать и пути к конфигам в docker-compose.yml?

Команда Timeweb Cloud
Команда Timeweb Cloud
13.07.2023, 10:46

Спасибо, что подметили, перепроверили статью и исправили недочёты 😉