Привет, коллеги! 👋

В web-разработке процесс деплоя играет важную роль. Это ответственный момент (даже торжественный), когда все усилия, потраченные на написание и тестирование кода, воплощаются в “живое” приложение, доступное пользователям. Ведь для этого приложение и делается, чтобы им кто-то пользовался. Каждый разработчик, независимо от уровня и специализации, регулярно сталкивается с задачей деплоя. Это статья в основном рассчитана на новичков, которые учатся разворачивать приложения на сервере и хотят узнать различные варианты, сравнить их и выбрать подходящий.

Деплой - это не просто загрузка кода на сервер (хотя лет 20 назад деплоили приложения по FTP). Прогресс не стоит на месте, приложения стали сложнее и сейчас деплой - это сложный и многоступенчатый процесс, который обычно включает в себя подготовку окружения, установку зависимостей, миграцию базы данных, минимизацию и компиляцию ассетов, проверку работоспособности и, наконец, переключение трафика на новую версию приложения.

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

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

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

Ссылки на другие части статьи:

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

Также в рамках этой статьи будем использовать GitHub. Я в работе использую именно его, наверное потому что Laravel использует GitHub (шутка). Всем начинающим разработчикам настоятельно рекомендую использовать git и привыкать к современным инструментам и принципам работы в команде, где без системы контроля версий никак не обойтись.

Вариант деплоя с архивом

Этот вариант уже в архиве истории. Его добавил для того чтобы немного поднять настроение. Брали папочку, подключались по ftp на сервер (я использовал программку FileZilla), загружали архив на сервер и там его распаковывали. Для сайтов-визиток работало неплохо. Всё что сложнее уже могут возникнуть проблемы.

"Классический" деплой на shared-хостинге с использованием GitHub

Считаю что этот "классический" способ должен знать каждый начинающий разработчик, чтобы понимать, что происходит при развертывании приложения на сервере. Используется при этом недорогой shared-хостинг (рублей 200-300/месяц на Timeweb, Beget, Рег.ру и т.д.) без необходимости администрирования. Подойдет для начинающих разработчиков. Дешево и сердито. В этой статье я буду показывать как деплоить на хостинге от beget (можно любой другой).

Требования к хостингу

  • поддерживает версию PHP, которая соответствует вашему проекту (в моём случае 8.3 - настоятельно рекомендую своевременно обновлять свои проекты)

  • есть система управления базами данных MySQL

  • есть возможность подключения по SSH

  • установлена система контроля версий Git

Что еще понадобиться

  • домен связан с хостингом (DNS-серверы хостинга внесены в настройки домена), или есть тестовый домен для проверки работоспособности проекта

  • на рабочем месте установлена система контроля версий Git

  • есть аккаунт на GitHub

Проще говоря - на хостинге уже есть папка с сайтом и в браузере можно вбить домен и сайт будет открываться.

Кроме этого понадобятся знания основ php, git и devops. Ну раз вы занялись деплоем, то наверняка вы разбираетесь в основах.

Начинаем.

Настройка git репозиториев

Переходим на GitHub, создаем новый репозиторий и напишем название - пусть будет CutCodeDeploy - очень красиво. Дополнительно сделаем репозиторий приватным (устанавливаем переключатель в Private). Условия приближенные к реальным - обычно, если мы делаем проект на заказ, то заказчику нужна конфиденциальность, для этого и служат приватные репозитории.

Нажимаем кнопку "Create repository". Репозиторий создан и GitHub предлагает нам пошаговую инструкцию - Quick setup. Давайте по ней и работать, чтобы ничего не забыть. 

Приступаем.

Инициализируем новый локальный Git репозиторий на компьютере при помощи команды git init .

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

cd "путь_к_папке_с_вашим_проектом"
git init

Эта команда создала папку .git внутри папки с вашим проектом.

Хорошее начало. Осталось совсем немного.

Чтобы Git понимал какие файлы в проекте изменились, нужно добавить их в индекс.

git add -A

Эта команда добавляет содержимое текущего каталога в индекс. И теперь git будет отслеживать изменения в репозитории и какие из файлов мы добавим в следующий коммит.

Коммит в Git - это как сохранение в игре. Когда вы делаете коммит, вы фактически сохраняете текущее состояние вашего проекта. Это позволяет вам в любой момент вернуться к этому состоянию, если что-то пойдет не так в будущем.  Важно отметить, что при первом коммите Git добавляет весь проект. В последующих коммитах Git будет добавлять только те файлы, которые были изменены после последнего коммита.

Пора сделать первый commit проекта (с комментарием "исходный проект") в созданный репозиторий:

git commit -m "исходный проект"

и настраиваем ветку главной:

git branch -M master

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

Но прежде чем вы сможете загрузить свой проект туда, вы должны "связать" ваш локальный репозиторий на компьютере с удаленным репозиторием на GitHub. Так как у нас репозиторий приватный, то нужно настроить безопасное соединение между компьютером и GitHub через SSH.

  1. Создание SSH ключей на локальном компьютере:

    Откройте терминал и выполните команду для создания нового SSH ключа:

    ssh-keygen
  2. Добавьте SSH ключ в агент ssh:

    eval "$(ssh-agent -s)"
    ssh-add ~/.ssh/id_rsa
  3. Копирование публичного ключа:

    cat ~/.ssh/id_rsa.pub
  4. Добавление публичного ключа на GitHub:

    • Перейдите в "Settings" > "SSH and GPG keys".

    • Нажмите "New SSH key" и вставьте ваш публичный ключ, а затем нажмите "Add SSH key".

Пора связать репозитории! Укажем удаленный репозиторий на GitHub с помощью команды:

git remote add origin https://github.com/"имя_пользователя"/"имя_репозитория".git

Замените  имя_пользователя и имя_репозитория на ваше имя пользователя на GitHub и имя вашего репозитория соответственно.

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

git remote add origin https://github.com/CutCodes/CutCodeDeploy.git

Теперь, когда ваш локальный репозиторий связан с удаленным репозиторием на GitHub, вы можете запушить (или отправить) свой проект на GitHub. Это можно сделать с помощью команды git push origin master:

Если вы планируете часто обновлять свой проект, вам, возможно, не захочется каждый раз писать длинную команду git push origin master. Вместо этого, вы можете сделать команду короче, просто git push. В конце концов, хороший разработчик - это ленивый разработчик!

Чтобы сделать это, вы можете установить ветку master вашего репозитория как ветку по умолчанию для команды git push. Это можно сделать с помощью следующей команды:

git push -u origin master

Отлично. Проект на GitHub! И теперь, когда вы вводите git push, Git автоматически пушит ваши изменения в ветку master вашего удаленного репозитория на GitHub. Это делает процесс обновления вашего проекта на GitHub намного быстрее и проще! 

Клонируем проект на хостинг из GitHub

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

Чтобы это сделать, мы должны установить безопасное соединение между нашим сервером и репозиторием на GitHub.

Для начала, переходим на нашем сервере в консоль. Затем мы создаем новую пару ключей SSH - это приватный и публичный ключи. Эти ключи нужны для установки безопасного соединения.

SSH-ключи создаются командой ssh-keygen. Ключи обычно хранятся на сервере в каталоге ~/home/"имя_пользователя"/.ssh/"имя_файла".

Когда мы запускаем ssh-keygen, мы указываем имя файла, в котором будут сохранены ключи. Также вы можете изменить место хранения ключей и добавить пароль для дополнительной защиты.

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

  • Приватный ключ: ~/home/"имя_пользователя"/.ssh/"имя_файла"

  • Публичный ключ: ~/home/"имя_пользователя"/.ssh/"имя_файла".pub

Теперь нам нужно добавить публичный ключ на GitHub. Копируем путь к публичному ключу, и при помощью команды cat смотрим его содержимое:

cat ~.ssh/"имя_файла".pub

Копируем содержимое публичного ключа и переходим в настройки нашего репозитория на GitHub (это раздел "Settings"). Затем мы выбираем раздел "Deploy Keys".

В этом разделе мы добавляем новый ключ деплоя. Мы даем ему название (например, "CutCodeDeploy.beget.tech") и вставляем наш скопированный ключ в соответствующее поле.

После этого мы подтверждаем добавление ключа, нажав на кнопку "Add key".

Важное примечание.

На GitHub еcть опция в виде чекбокса, которая позволяет разрешить запись в репозиторий. Если этот чекбокс не выбран, то по настроенному SSH-соединению можно будет только считывать содержимое репозитория, но не вносить в него изменения.

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

Это важно помнить при настройке SSH-соединения с GitHub. 

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

На GitHub, в нашем репозитории, мы выбираем раздел "Code", затем переходим на вкладку "ssh" и копируем адрес репозитория.

Заходим на сервер, переходим в директорию проекта и в консоли выполняем необходимую команду.

git clone git@"скопированный_адрес_репозитория"

Команда git clone выполняет несколько действий: она инициализирует репозиторий Git в текущей директории, добавляет удаленный репозиторий (известный как "origin") с GitHub, и затем клонирует содержимое этого репозитория в текущую директорию.

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

Настроим git pull

update

При клонировании репозитория с помощью Git, ссылка на исходный репозиторий автоматически устанавливается как origin. Это означает, что Git знает, откуда получать данные при выполнении git pull.

Когда вы хотите забрать обновления с GitHub, вам просто нужно написать git pull, и Git автоматически заберет обновления из ветки master репозитория origin, как почтальон, который знает, где ваш почтовый ящик.

Настройка символических ссылок

Точка входа в веб-приложении - это файл, который обрабатывает все запросы к вашему веб-сайту и перенаправляет их в ваше приложение. Обычно на shared-хостингах это файл index.php или index.html, который находится в корневом каталоге веб-приложения.

У нас приложение на Laravel. В Laravel точка входа обычно находится в каталоге public. Это сделано для обеспечения безопасности, так как только файлы в каталоге public доступны для прямого доступа, а все остальное - под замком. Круто придумано.

update

Получается, что точка входа index.php в корневой папке веб-сервера на хостинге не совпадает с дефолтной точкой входа приложения на Laravel, и самый простой способ это исправить - это сделать символическую ссылку (мы используем недорогой shared-хостинг, где полного доступа к системе и конфигурационным файлам нет).

Итак, точка входа в приложение Laravel - это файл public/index.php, и нам нужно, чтобы все запросы по умолчанию направлялись на public/index.php. Простое решение этой задачи - создать символическую ссылку. Это как создать ярлык на рабочем столе, который ведет к нужной программе.

Если в корне проекта уже есть файл index.php или index.html, то сначала удалим его. А теперь давайте создадим символическую ссылку. Перейдем в корневой каталог вашего проекта Laravel и выполним нужную команду

ln -s public public_html

Это означает, что теперь у вас есть новый путь public_html, который ведет прямо в ваш каталог public.

Обновляем зависимости

Composer - это мощный инструмент для управления зависимостями в PHP. Он позволяет вам объявлять библиотеки, которые ваш проект использует, и он управляет (устанавливает/обновляет) их за вас. Это как ваш личный помощник, который следит за тем, чтобы все ваши библиотеки были в порядке.

Установка зависимостей Composer при деплое - это важный шаг, который гарантирует, что все необходимые библиотеки и пакеты, необходимые для работы вашего приложения, установлены и настроены правильно на сервере перед запуском вашего приложения. Это как проверка перед стартом, чтобы убедиться, что все готово к запуску.

Composer будет устанавливать все зависимости, указанные в файле composer.json вашего проекта. Это как список покупок для вашего приложения, по которому Composer будет следовать.

На некоторых хостингах Composer может быть не установлен по умолчанию. Но обычно у хостинг-провайдеров есть подробные инструкции о том, как установить Composer на их серверах.

Что делать если на хостинге нет Composer?

Если Composer не установлен на сервере, вы всегда можете установить его локально в ваш проект и после этого обновить проект на сервере. Если Composer есть, то пропускаем эту главу - тык.

  1. Сначала мы загружаем установочный файл Composer с помощью команды:

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
  1. Затем мы устанавливаем Composer с помощью команды:

php composer-setup.php
  1. После завершения установки мы можем удалить установочный файл, он нам больше не понадобится:

php -r "unlink('composer-setup.php');" mv composer.phar /usr/bin/composer

Теперь в вашем проекте есть файл composer.phar, и его можно использовать для управления зависимостями внутри вашего проекта.

Чтобы Composer появился на сервере, обновим проект, выполнив commit и push в репозиторий на GitHub:

Затем выполним pull на сервере. И вуаля, Composer теперь у вас в проекте! Но помните, он доступен только внутри проекта и запускать его нужно командой composer.phar.

Продолжаем работу с Composer

На некоторых хостингах, включая хостинг beget, требуется явно указывать версию PHP для Composer. Это связано с тем, что версия PHP в консоли может отличаться от версии, заданной в контрольной панели для сайта. Это как если бы у вас было два разных языка, один для общения с друзьями, и другой - для работы. И Composer должен знать, на каком языке ему говорить.

Вы можете узнать текущую версию PHP для консоли с помощью команды php -v. Спросим у PHP: "Сколько тебе лет?".

Если на сервере установлено несколько версий PHP, то в команде нужно указывать конкретную версию PHP. В моём случае это php8.3.

Команда php8.3 composer install обновит зависимости в вашем проекте. Однако, эта команда будет работать только в том случае, если php8.3 и composer доступны в вашем PATH. Это означает, что оболочка может найти их, когда вы вводите эти команды.

Если php8.3 или composer не найдены, вам придется указать полный путь до этих программ. Например, если composer находится в /usr/local/bin/composer, вы должны использовать этот полный путь в команде.

Алиас php8.3 обычно создается при установке этой версии PHP. Это делается для удобства, чтобы можно было легко переключаться между разными версиями PHP.

Выполним:

php8.3 composer install

По этой команде все необходимые нашему приложению пакеты Composer скачает и установит, вот такой он молодец.

Пару слов о composer.lock

В проектах, которые используют Composer, есть важный служебный файл под названием composer.lock. Этот файл выполняет несколько ключевых функций:

Фиксация версий зависимостей: когда вы впервые устанавливаете зависимости с помощью команды composer install, Composer смотрит на файл composer.json вашего проекта, чтобы узнать, какие пакеты ему нужны, и скачивает их. Затем Composer записывает точные версии установленных пакетов в composer.lock. Это означает, что если вы или кто-то другой запустите composer install в будущем, Composer будет использовать версии из composer.lock, а не искать новые версии. Это гарантирует, что все, кто работает над проектом, используют одни и те же версии пакетов, что уменьшает вероятность проблем совместимости.

Безопасность: файл composer.lock также содержит хеши каждого пакета. Это позволяет Composer проверить, что пакеты не были изменены или повреждены с момента их установки.

Ускорение установки: файл composer.lock содержит точные версии и источники каждого пакета, поэтому Composer быстрее устанавливает пакеты, не тратя время на их поиск.

Важно помнить, что composer.lock должен быть включен в ваш репозиторий кода, чтобы все, кто работает над проектом, могли использовать одни и те же версии пакетов. Если вы хотите обновить версии пакетов, вы можете использовать composer update, который обновит composer.lock.

Создаём базу данных для приложения

Создание базы данных для приложения - это простой процесс. Все, что вам нужно, это найти раздел "Базы данных" в панели управления вашего хостинга.

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

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

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

Настраиваем .env

Файл .env очень важен для работы проекта на Laravel. Этот файл содержит различные настройки окружения, такие как данные для подключения к базе данных, ключи API и другую конфиденциальную информацию, которая также зависит от окружения. .env на продакшене один, у вас локально свой, у коллег по команде другой. Именно поэтому файл .env не присутствует в git-репозитоиях.

Создадим файл .env из шаблона (.env.example) командой:

 cp .env.example .env

Вот он появился:

Еще один способ просмотреть содержимое текущей директории - это использовать команду ls -la. Эта команда отображает все файлы и папки в текущей директории, включая скрытые файлы (те, что начинаются с точки). Выполняем:

ls -la

В консоли вы увидите список всех файлов и папок в текущей директории.

Итак, файл .env создан, редактируем его! Первым делом займемся APP_KEY. Это ключ безопасности, который очень важен для Laravel, он используется для шифрования данных пользователя, таких как сессии, куки и пароли, а также для защиты от CSRF атак. Без этого ключа ваше приложение Laravel не сможет правильно функционировать.

Надо быть аккуратными при работе с APP_KEY, ведь если заново сгенерировать ключ на уже рабочем проекте, то все ранее зашифрованные данные (например, пароли пользователей) станут недоступными!

Ключ генерируется командой:

php8.3 artisan key:generate

Теперь в файле .env появился новый ключ безопасности в строке APP_KEY.

Кроме этого в .env надо установить еще несколько параметров:

  • APP_ENV=production указываем что режим работы приложения - продакшен

  • APP_URL="домен приложения"

  • DB_DATABASE="имя БД"

  • DB_USERNAME="имя пользователя БД"

  • DB_PASSWORD="пароль от БД"

Миграции

Миграции в Laravel - это очень удобный способ управления структурой вашей базы данных. Команда php8.3 artisan migrate запускает все ваши миграции, что, по сути, “накатывает” изменения на вашу базу данных. А при первом вызове создает структуру базы данных с нуля.

Поскольку вы указали APP_ENV=production в файле .env, Laravel выдает предупреждение перед запуском миграций. Это сделано для того, чтобы предотвратить случайные изменения в вашей продакшен-базе данных. Это двойная проверка перед тем, как вы вносите какие-либо изменения.

Рестарт очередей и компиляция ассетов

Рестарт очередей - это важный шаг, если ваше приложение использует очереди задач Laravel. Когда вы обновляете свое приложение, вам нужно убедиться, что все ваши очереди воркеров перезапускаются. Если в проекте джобы изменились, а вы забудете перезапустить очереди, то изменения не применятся и крутиться в воркерах будут старые джобы.

Рестарт очередей выполняется с помощью команды:

php artisan queue:restart

Компиляция ассетов - это процесс, в котором Laravel собирает и оптимизирует ваши CSS и JavaScript файлы. "Ассеты сбилдились" ("ассеты собраны", стили и скрипты скомпилированы) в отношении Laravel обычно означает, что ваши статические файлы, такие как CSS, JavaScript и изображения, были обработаны и оптимизированы.

Это делается с помощью инструмента под названием Laravel Mix или Vite. Для компиляции ассетов необходим NPM (Node Package Manager) - менеджер пакетов для JavaScript. Ассеты билдятся с помощью команды:

 npm run build

Некоторые shared хостинги поддерживают Node.js и npm, но не все. Важно проверить у вашего провайдера хостинга - есть ли на нем npm.

Что делать есть shared-хостинг не поддерживает npm?

Мы можем скомпилировать стили в локальном проекте и через GitHub отправить на сервер. Вот как это сделать:

  1. Скомпилируйте ваши стили. Используйте команду npm run build.

  2. Убедитесь, что папки с css и js добавлены в индекс, при необходимости добавьте. Используйте команду git add public/css или git add public/js (или обе команды, если вы храните стили в обоих местах) для добавления скомпилированных файлов в локальный репозиторий Git.

  3. Сделайте коммит.

  4. Запушьте изменения на GitHub.

  5. Склонируйте изменения с GitHub на сервер.

Перед следующим коммитом не забудьте сбилдить ассеты!

Компиляция ассетов и рестарт очередей - это два важных шага, которые легко забыть при развертывании приложения Laravel.

Проверяем работу

Всё готово для проверки работы. Обновляем страницу и видим наше приложение на Laravel.

Всё настроено. Как теперь обновлять проект?

1. Отправляем изменения в удалённый репозиторий на GitHub.

1.1. Создаём коммит в локальном репозитории:

commit "описание коммита"

1.2. Отправляем изменения в GitHub:

git push

1.3. Проверяем что репозиторий на GitHub обновлён.

2. Теперь заберем изменения из GitHub-репозитория на сервер.

2.1. Переходим в командную строку на сервере. И выполняем:

git pull 

2.2. Устанавливаем зависимости, если вдруг какие-то пакеты новые подключали:

php8.3 composer install 

2.3. Накатываем миграции, собираем ассеты и делаем рестарт очередей:

php8.3 artisan migrate 
npm run prod
php artisan queue:restart

Выводы

Вот так мы практически с нуля настроили деплой проекта на сервер. Примерно это займет около 30 минут. Каждый дальнейший деплой - 3-5 минут. Не очень радостные перспективы для проектов, на которых необходимо часто обновлять код на продакшене. Также не стоит забывать про человеческий фактор, много рутинной работы - можно забыть выполнить какой-то шаг - не обновить зависимости, забыть про миграции, компиляцию ассетов или рестарт очередей. Легко пропустить какую-то команду, а любая оплошность приведет к ошибке при деплое, и нужно будет тратить время на поиск причины.

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

Ссылки на другие части статьи:

Данил Щуцкий, автор проекта CutCode.