Ой, ничего не найдено!

К сожалению, по вашему запросу пока ничего нет (но это только пока!), зато вы можете подписаться на нашу замечательную email-рассылку, чтобы не пропустить самое интересное в будущем.

  • 17

Деплой без FTP на виртуальном хостинге: Git, Composer и безопасный откат

Часть 9 из 14 · Серия «Хостинг без магии»

Новая версия сайта готова. Разработчик открывает FTP-клиент, выделяет несколько тысяч файлов и нажимает «Загрузить с заменой».

Первые файлы уже обновились, остальные ещё передаются. В этот момент посетитель открывает сайт. Новый контроллер пытается вызвать класс из новой версии пакета, но каталог vendor обновился только наполовину. Вместо страницы появляется ошибка 500.

Разработчик повторяет загрузку. Затем ещё раз передаёт несколько файлов, которые FTP-клиент пропустил из-за разрыва соединения. Сайт открывается, но без стилей: папка public/build осталась от предыдущей версии.

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

Обычное обновление сайта превратилось в аварию.

Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net

Проблема здесь не в PHP-фреймворке и не обязательно в виртуальном хостинге. Проблема в самом подходе:

Файлы копировались непосредственно поверх работающего сайта без зафиксированной версии, определённого порядка действий и подготовленного способа отката.

В предыдущей статье мы разобрали, какие возможности нужны современному PHP-проекту: SSH, Composer, Node.js, cron, Git и отдельная публичная директория. Теперь используем эти инструменты по назначению и соберём воспроизводимый деплой.

Деплой — это не загрузка файлов

Загрузка кода на сервер — только один из этапов.

Полный деплой современного PHP-приложения обычно включает:

  1. выбор конкретной версии кода;
  2. получение этой версии из репозитория;
  3. установку PHP-зависимостей;
  4. сборку CSS и JavaScript;
  5. применение миграций базы данных;
  6. очистку старых кэшей;
  7. создание production-кэшей;
  8. переключение сайта на новую версию;
  9. проверку основных функций;
  10. откат при неудаче.

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

Хороший деплой должен отвечать на четыре вопроса:

  • какая версия сейчас работает;
  • какие команды выполнялись при её установке;
  • завершились ли все шаги успешно;
  • как вернуть предыдущую версию.
Подключение за минуту
Попробуйте Siteko.net бесплатно в течение месяца
Проверьте хостинг на реальном сайте: скорость, стабильность и поддержка доступны сразу после подключения.
Начать бесплатно

Если ответы приходится восстанавливать по памяти разработчика и журналу FTP-клиента, процесса деплоя фактически нет.

Почему FTP плохо подходит для обновления приложения

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

Сайт оказывается между двумя версиями

Файлы обновляются последовательно.

Пока передача не закончилась, на сервере находится смесь:

  • новых PHP-классов;
  • старых шаблонов;
  • частично обновлённых зависимостей;
  • старой frontend-сборки;
  • нового composer.json;
  • предыдущего каталога vendor.

Посетитель может попасть на сайт именно в этом промежуточном состоянии.

Нельзя точно определить опубликованную версию

В Git каждый рабочий набор файлов связан с конкретным коммитом:

7fd124a Fix checkout validation
Подключение за минуту
Попробуйте Siteko.net бесплатно в течение месяца
Проверьте хостинг на реальном сайте: скорость, стабильность и поддержка доступны сразу после подключения.
Начать бесплатно

После ручной загрузки по FTP точной версии может не существовать. Один файл взят из последнего коммита, другой исправлен прямо на сервере, третий не загрузился, четвёртый остался от предыдущего релиза.

Повторная передача не гарантирует одинаковый результат

FTP-клиент может:

  • пропустить скрытые файлы;
  • изменить права;
  • передать не все каталоги;
  • сравнивать файлы только по дате;
  • прервать соединение;
  • спросить о замене уже после начала операции.

Результат зависит не только от исходного кода, но и от истории ручных действий.

Откат приходится выполнять тем же способом

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

Если база данных уже изменилась, одного возврата файлов может быть недостаточно.

Основа нормального деплоя: код нельзя смешивать с данными

Перед подключением Git проект следует разделить на три категории.

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

1. Код и описание зависимостей

Эти файлы хранятся в репозитории:

app/
config/
database/migrations/
resources/
routes/
src/
templates/
composer.json
composer.lock
package.json
package-lock.json

Для каждого коммита их содержимое должно быть определено заранее.

2. Секреты окружения

Они существуют на сервере, но не должны попадать в репозиторий:

.env
.env.prod.local
закрытые ключи
пароли внешних сервисов
production-токены

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

3. Изменяемые данные

Они появляются уже во время работы сайта:

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

Такие данные нельзя удалять при публикации нового релиза или возвращать к состоянию старого коммита.

Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Для Laravel к ним относится прежде всего часть каталога storage, для Symfony — содержимое изменяемых runtime-каталогов, а для CMS — папка пользовательских загрузок.

Правило простое:

Git управляет кодом, но не production-секретами и не пользовательскими данными.

Что должно быть зафиксировано в репозитории

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

composer.lock

В production выполняется:

composer install

Команда устанавливает версии, зафиксированные в composer.lock.

Команда:

composer update
Подключение за минуту
Попробуйте Siteko.net бесплатно в течение месяца
Проверьте хостинг на реальном сайте: скорость, стабильность и поддержка доступны сразу после подключения.
Начать бесплатно

подбирает новые версии пакетов и изменяет lock-файл. Это действие выполняют во время разработки, проверяют тестами и только затем коммитят результат.

Для production Composer поддерживает флаги --no-dev и --optimize-autoloader: первый исключает зависимости для разработки, второй создаёт оптимизированную карту классов.

package-lock.json

Для frontend-зависимостей используется тот же принцип.

Вместо обычного:

npm install

в сценарии деплоя предпочтительнее:

npm ci

npm ci требует существующий lock-файл, не изменяет его и останавливается с ошибкой, если package.json и package-lock.json противоречат друг другу. Команда предназначена в том числе для автоматизированных сборок и деплоя.

1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Миграции

Изменения структуры базы должны храниться в виде миграций:

database/migrations/
migrations/

Ручное добавление столбца через phpMyAdmin без соответствующей миграции создаёт отдельную, нигде не описанную версию базы.

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

Команды сборки

В composer.json и package.json должны находиться рабочие сценарии:

{
  "scripts": {
    "build": "vite build"
  }
}

Деплой не должен зависеть от инструкции вида:

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

Production-сервер не должен быть редактором кода

Перед первым деплоем через Git полезно принять жёсткое правило:

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

Файлы приложения не редактируются непосредственно на production-хостинге.

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

Такая правка создаёт локальное изменение:

git status --short
 M app/Http/Controllers/OrderController.php

Следующий деплой либо остановится из-за конфликта, либо уничтожит изменение.

Правильная последовательность выглядит так:

правка локально

тестирование

commit

push

deploy

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

Как предоставить хостингу доступ к закрытому репозиторию

Для открытого репозитория достаточно обычного git clone.

Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net

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

Для одного проекта удобно использовать deploy key — отдельный SSH-ключ, привязанный к конкретному репозиторию. Закрытая часть остаётся на сервере, а публичная добавляется в настройки репозитория. GitHub позволяет сделать такой ключ только для чтения; доступ на запись для обычного деплоя не требуется.

Ключ создаётся на хостинге:

ssh-keygen \
    -t ed25519 \
    -C "deploy@example.com" \
    -f ~/.ssh/example_deploy

Публичная часть выводится командой:

cat ~/.ssh/example_deploy.pub

Она добавляется в настройки репозитория как deploy key без разрешения записи.

Для выбора ключа создаётся ~/.ssh/config:

Первый месяц за 0 рублей
Хостинг для сайта, который должен работать стабильно
Перенесите проект или запустите новый сайт на Siteko.net и протестируйте сервис без предоплаты.
Перейти к хостингу
Host github-example HostName github.com User git IdentityFile ~/.ssh/example_deploy IdentitiesOnly yes

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

git clone git@github-example:owner/project.git

Если используется несколько закрытых репозиториев, для каждого можно создать отдельный ключ и отдельную запись Host. GitHub также указывает, что один deploy key привязывается к одному репозиторию.

Как это устроено на Siteko. Git доступен в консоли на тарифах для проектов с SSH. Закрытый репозиторий можно подключить по deploy-ключу, а для GitHub в панели предусмотрен модуль импорта. Composer установлен глобально, Node.js подключается через nvm, символические ссылки внутри аккаунта разрешены.

Первый вариант: простой деплой в рабочий каталог

Начнём со схемы, которую можно настроить практически на любом виртуальном хостинге с SSH.

Проект находится в постоянном каталоге:

/home/user/www/example.com/project/

Домен указывает на:

/home/user/www/example.com/project/public/

При обновлении Git заменяет файлы непосредственно в этом каталоге.

1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Это не zero-downtime deployment: на несколько секунд или минут сайт переводится в режим обслуживания. Зато схема понятна, не требует сложной структуры релизов и подходит большинству небольших проектов.

Первый запуск проекта

Переходим в каталог сайта:

cd ~/www/example.com

Клонируем репозиторий:

git clone \
    --branch main \
    git@github-example:owner/project.git \
    project

Переходим в проект:

cd project

Создаём production-конфигурацию:

cp .env.example .env
nano .env

Устанавливаем PHP-зависимости:

composer install \
    --no-dev \
    --prefer-dist \
    --optimize-autoloader \
    --no-interaction
Первый месяц за 0 рублей
Хостинг для сайта, который должен работать стабильно
Перенесите проект или запустите новый сайт на Siteko.net и протестируйте сервис без предоплаты.
Перейти к хостингу

Если frontend собирается на хостинге:

npm ci
npm run build

Для первого запуска Laravel могут понадобиться:

php artisan key:generate
php artisan migrate --force
php artisan storage:link
php artisan optimize

storage:link обычно выполняется один раз. Повторять создание ссылки во время каждого обновления необязательно.

Перед обновлением: обязательная проверка

Переходим в рабочий каталог:

cd ~/www/example.com/project

Проверяем текущий коммит:

git rev-parse --short HEAD

Проверяем состояние файлов:

git status --short
1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Команда не должна ничего вывести.

Если появились изменённые или неизвестные файлы, деплой лучше остановить:

 M app/Services/PaymentService.php
?? public/test.php

Сначала нужно понять:

  • кто изменил файл;
  • должно ли изменение попасть в репозиторий;
  • можно ли удалить неизвестный файл;
  • не является ли он результатом взлома;
  • не хранит ли приложение изменяемые данные внутри каталога кода.

Автоматически выполнять git reset --hard, не посмотрев на список изменений, опасно: команда удалит локальные правки.

Получаем новую версию, но пока не публикуем её

Загружаем информацию о новых коммитах:

git fetch --prune origin

Смотрим, что изменится:

git log \
    --oneline \
    --decorate \
    HEAD..origin/main
Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Пример:

91ad733 Fix payment callback validation
e0c45b2 Add order status index
73a80f1 Update frontend dependencies

Можно отдельно посмотреть изменённые файлы:

git diff \
    --stat \
    HEAD..origin/main

Если среди них есть миграции, composer.lock или package-lock.json, мы заранее понимаем, какие дополнительные шаги понадобятся.

Переводим сайт в режим обслуживания

Для Laravel:

php artisan down --retry=60

Режим обслуживания возвращает посетителям ответ 503, пока выполняется обновление. Laravel также позволяет заранее отрисовать страницу обслуживания, чтобы её показ не зависел от загрузки Composer-пакетов во время обновления. После завершения сайт возвращается командой php artisan up.

Если в проекте есть шаблон errors::503, можно использовать:

php artisan down \
    --render="errors::503" \
    --retry=60
Первый месяц за 0 рублей
Хостинг для сайта, который должен работать стабильно
Перенесите проект или запустите новый сайт на Siteko.net и протестируйте сервис без предоплаты.
Перейти к хостингу

Для Symfony или другого фреймворка можно:

  • использовать собственный maintenance-флаг;
  • временно переключить корень сайта на статическую страницу;
  • создать поддерживаемый проектом файл блокировки;
  • включить режим обслуживания через панель или конфигурацию приложения.

Важно, чтобы страница обслуживания не зависела от файлов, которые сейчас заменяются.

Обновляем код через Git

Когда рабочий каталог не содержит локальных изменений:

git pull --ff-only origin main

git pull сначала получает данные с удалённого репозитория, а затем интегрирует их в текущую ветку. Флаг --ff-only запрещает создавать неожиданный merge-коммит на production: если локальная история разошлась с удалённой, команда остановится.

Другой воспроизводимый вариант:

git fetch origin main
git reset --hard origin/main

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

1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Устанавливаем PHP-зависимости

После обновления кода:

composer install \
    --no-dev \
    --prefer-dist \
    --optimize-autoloader \
    --no-interaction

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

Не следует добавлять:

--ignore-platform-reqs

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

Если Composer сообщает о несовместимости окружения, нужно исправлять окружение или зависимости, а не скрывать проверку.

Собираем frontend

Если production-ресурсы собираются непосредственно на хостинге:

npm ci
npm run build
Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Обычно результат появляется в каталоге:

public/build/

Возможны и другие стратегии:

  • собрать frontend на компьютере разработчика и добавить готовый результат в релиз;
  • собрать его в GitHub Actions или GitLab CI;
  • передавать на хостинг готовый архив;
  • хранить production-сборку в репозитории.

Главное — выбрать один способ. Плохая схема выглядит так:

иногда public/build коммитится,
иногда собирается на сервере,
иногда копируется по FTP

После нескольких обновлений становится непонятно, как была создана текущая версия ресурсов.

Очищаем старые кэши

После замены кода в Laravel:

php artisan optimize:clear

Команда удаляет кэши, созданные командами оптимизации. Затем, когда конфигурация и миграции уже применены, production-кэши можно создать заново:

php artisan optimize
Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net

Laravel рекомендует включать optimize в процесс production-деплоя: команда кэширует конфигурацию, маршруты, события и представления.

Для Symfony типовая production-команда выглядит так:

APP_ENV=prod APP_DEBUG=0 \
php bin/console cache:clear

Официальная документация Symfony также относит очистку и прогрев кэша к стандартным шагам деплоя.

Применяем миграции

Для Laravel:

php artisan migrate --force

Для Symfony с Doctrine:

APP_ENV=prod \
php bin/console doctrine:migrations:migrate \
    --no-interaction

Флаг --force в Laravel нужен потому, что фреймворк запрашивает подтверждение перед выполнением потенциально опасной команды в production.

Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Но автоматический запуск миграций требует дисциплины.

Безопасная миграция

Schema::table('orders', function (Blueprint $table) {
    $table->string('external_id')->nullable();
});

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

Опасная миграция

Schema::table('orders', function (Blueprint $table) {
    $table->dropColumn('status');
});

После её выполнения предыдущая версия приложения может перестать работать.

Изменение типа большого столбца, удаление данных или переименование поля также может:

  • надолго заблокировать таблицу;
  • завершиться по таймауту;
  • сделать откат кода невозможным;
  • потребовать отдельного преобразования данных.

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

Возвращаем сайт и проверяем результат

После успешного выполнения всех шагов:

php artisan up
Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Затем проверяем не только главную страницу.

Минимальный список:

php artisan about
php artisan migrate:status

Для проектов, где доступен health-маршрут:

curl -fsS https://example.com/up

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

После этого следует вручную проверить критический сценарий:

  • вход в личный кабинет;
  • оформление заказа;
  • отправку формы;
  • загрузку файла;
  • работу административной панели;
  • отправку письма;
  • создание фонового задания.

И посмотреть журнал:

tail -n 100 storage/logs/laravel.log
Подключение за минуту
Попробуйте Siteko.net бесплатно в течение месяца
Проверьте хостинг на реальном сайте: скорость, стабильность и поддержка доступны сразу после подключения.
Начать бесплатно

Отсутствие ошибки на главной странице ещё не означает, что весь деплой прошёл успешно.

Полная последовательность простого деплоя

Для Laravel она может выглядеть так:

cd ~/www/example.com/project

git status --short
git fetch --prune origin
git log --oneline HEAD..origin/main

php artisan down --retry=60

git pull --ff-only origin main

composer install \
    --no-dev \
    --prefer-dist \
    --optimize-autoloader \
    --no-interaction

npm ci
npm run build

php artisan optimize:clear
php artisan migrate --force
php artisan optimize

php artisan up

Эту последовательность не нужно копировать вслепую.

Например:

  • если frontend собирается в CI, команды npm не нужны;
  • если в проекте нет миграций, соответствующий шаг ничего не изменит;
  • если используется Symfony, Artisan-команды заменяются консолью Symfony;
  • если проект не поддерживает maintenance mode, нужен другой механизм;
  • если работает постоянная очередь, после деплоя её workers нужно перезапустить.

Laravel предупреждает, что долгоживущие queue workers не замечают изменения кода, пока не будут перезапущены. Для таких процессов предусмотрена команда php artisan queue:restart.

На виртуальном хостинге Siteko очередь из предыдущей статьи запускается через cron с --stop-when-empty: процесс обрабатывает задания и завершается. Постоянного worker-процесса там нет, поэтому отдельный перезапуск обычно не требуется.

1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Автоматизируем последовательность в deploy.sh

Когда ручной процесс несколько раз успешно проверен, его можно перенести в сценарий.

Создадим файл за пределами публичной директории:

nano ~/deploy-example.sh

Пример:

#!/usr/bin/env bash

set -Eeuo pipefail

APP_DIR="$HOME/www/example.com/project"
BRANCH="main"
STATE_DIR="$HOME/.deploy/example.com"

mkdir -p "$STATE_DIR"
cd "$APP_DIR"

echo "Проверяем рабочий каталог..."

if [ -n "$(git status --porcelain)" ]; then
    echo "ОШИБКА: в production-каталоге есть локальные изменения."
    git status --short
    exit 1
fi

OLD_COMMIT="$(git rev-parse HEAD)"

echo "Текущая версия: $OLD_COMMIT"
echo "$OLD_COMMIT" > "$STATE_DIR/previous-commit"

echo "Получаем новую версию..."
git fetch --prune origin "$BRANCH"

NEW_COMMIT="$(git rev-parse "origin/$BRANCH")"

if [ "$OLD_COMMIT" = "$NEW_COMMIT" ]; then
    echo "Новых коммитов нет."
    exit 0
fi

echo "Новая версия: $NEW_COMMIT"

echo "Включаем режим обслуживания..."
php artisan down --retry=60

restore_access() {
    php artisan up >/dev/null 2>&1 || true
}

trap restore_access EXIT

echo "Обновляем код..."
git reset --hard "$NEW_COMMIT"

echo "Устанавливаем PHP-зависимости..."
composer install \
    --no-dev \
    --prefer-dist \
    --optimize-autoloader \
    --no-interaction

if [ -f package-lock.json ]; then
    echo "Собираем frontend..."
    npm ci
    npm run build
fi

echo "Обновляем приложение..."
php artisan optimize:clear
php artisan migrate --force
php artisan optimize

echo "Возвращаем сайт..."
php artisan up

trap - EXIT

echo "$NEW_COMMIT" > "$STATE_DIR/current-commit"

echo "Деплой завершён успешно."

Делаем файл исполняемым:

chmod 700 ~/deploy-example.sh

Запускаем:

~/deploy-example.sh

Сценарий останавливается при первой ошибке благодаря:

set -Eeuo pipefail
Первый месяц за 0 рублей
Хостинг для сайта, который должен работать стабильно
Перенесите проект или запустите новый сайт на Siteko.net и протестируйте сервис без предоплаты.
Перейти к хостингу

Однако автоматизация не превращает рискованную миграцию в безопасную. Она лишь гарантирует одинаковую последовательность команд.

Что ещё следует добавить в реальный сценарий

В зависимости от проекта:

  • резервное копирование базы;
  • проверку свободного места;
  • проверку версии PHP;
  • composer check-platform-reqs;
  • запуск автоматических тестов;
  • отдельный журнал деплоя;
  • уведомление разработчику;
  • проверку health-маршрута;
  • удаление maintenance mode при ошибке;
  • блокировку от одновременного запуска двух деплоев.

Например, защититься от параллельного запуска можно через flock:

flock -n \
    "$HOME/.deploy/example.com.lock" \
    "$HOME/deploy-example.sh"

Если предыдущий деплой ещё выполняется, второй не начнёт заменять те же файлы.

Как откатить простой деплой

Сценарий сохранил предыдущий коммит:

~/.deploy/example.com/previous-commit

Посмотрим его:

cat ~/.deploy/example.com/previous-commit
Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net

Допустим, это:

7fd124a86cf...

Возвращаем код:

cd ~/www/example.com/project

php artisan down --retry=60

git reset --hard 7fd124a86cf

composer install \
    --no-dev \
    --prefer-dist \
    --optimize-autoloader \
    --no-interaction

npm ci
npm run build

php artisan optimize:clear
php artisan optimize

php artisan up

Но здесь есть важное ограничение:

Откат Git возвращает код, но не возвращает базу данных.

Автоматически выполнять:

php artisan migrate:rollback

после каждого неудачного деплоя опасно.

Миграция могла:

  • удалить столбец с данными;
  • объединить несколько полей;
  • преобразовать значения;
  • создать данные, которые уже используют посетители;
  • входить в одну группу с другими миграциями;
  • не иметь корректного обратного действия.
Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Иногда безопаснее оставить новую структуру базы и вернуть только совместимый код. Иногда требуется восстановить резервную копию. Решение зависит от конкретной миграции.

Безопасные миграции для возможности отката

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

Допустим, поле name нужно заменить на display_name.

Релиз 1

Добавляем новое поле, не удаляя старое:

name
display_name nullable

Новый код умеет работать с обоими полями.

Релиз 2

Переносим существующие данные:

display_name = name
1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Приложение начинает записывать значение в новое поле, но сохраняет совместимость со старым.

Релиз 3

Код перестаёт использовать name.

Релиз 4

Только после проверки удаляется старый столбец.

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

Главный принцип:

Сначала расширяем структуру, затем переводим на неё код и только потом удаляем старое.

Почему простой деплой всё равно не полностью безопасен

Даже с maintenance mode остаётся один недостаток: рабочий каталог изменяется на месте.

Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net

Последовательность выглядит так:

старый код

частично обновлённый код

новые зависимости

новая сборка

готовый релиз

Посетители видят страницу обслуживания, но сам каталог некоторое время остаётся неполным.

Если Composer завершится с ошибкой, для восстановления придётся снова устанавливать старые зависимости. Если frontend-сборка не пройдёт, старый public/build уже может быть удалён командой npm ci или последующим build-процессом.

Для небольшого сайта этот риск приемлем. Для более важного проекта лучше готовить следующую версию в отдельной папке.

Второй вариант: отдельный каталог для каждого релиза

Релизная схема выглядит так:

/home/user/www/example.com/
├── current -> releases/20260721103042
├── releases/
│   ├── 20260715142010/
│   ├── 20260718110533/
│   └── 20260721103042/
├── shared/
│   ├── .env
│   └── storage/
└── deploy.sh

Домен направлен на:

/home/user/www/example.com/current/public/
1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

current — символическая ссылка на активный релиз.

Новая версия полностью собирается в отдельном каталоге:

releases/20260721103042/

Пока установка Composer, сборка frontend и проверки не завершены, посетители продолжают работать с предыдущим релизом.

Только после успешной подготовки ссылка current переключается на новую папку.

Что хранится в shared

Каталог shared содержит данные, которые должны переживать смену релизов:

shared/
├── .env
└── storage/
    ├── app/
    ├── framework/
    └── logs/

В каждом релизе создаются ссылки:

release/.env     -> shared/.env
release/storage  -> shared/storage
Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net

Поэтому:

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

Для других фреймворков набор общих каталогов будет отличаться.

Например, не все кэши следует сохранять между релизами. Нужно разделять:

  • пользовательские данные;
  • логи;
  • временный framework-кэш;
  • скомпилированные файлы;
  • загруженные документы.

Подготовка shared-каталогов

Создаём структуру:

APP_ROOT="$HOME/www/example.com"

mkdir -p "$APP_ROOT/releases"
mkdir -p "$APP_ROOT/shared/storage/app/public"
mkdir -p "$APP_ROOT/shared/storage/framework/cache"
mkdir -p "$APP_ROOT/shared/storage/framework/sessions"
mkdir -p "$APP_ROOT/shared/storage/framework/views"
mkdir -p "$APP_ROOT/shared/storage/logs"

Создаём production-конфигурацию:

nano "$APP_ROOT/shared/.env"
chmod 600 "$APP_ROOT/shared/.env"

Корень домена направляем на:

/home/user/www/example.com/current/public
Подключение за минуту
Попробуйте Siteko.net бесплатно в течение месяца
Проверьте хостинг на реальном сайте: скорость, стабильность и поддержка доступны сразу после подключения.
Начать бесплатно

На Siteko произвольный Document Root и символические ссылки внутри аккаунта поддерживаются, поэтому такая структура может использоваться без переноса index.php и изменения стандартной структуры фреймворка.

Подготавливаем новый релиз

Создаём идентификатор:

RELEASE_ID="$(date +%Y%m%d%H%M%S)"

Определяем путь:

APP_ROOT="$HOME/www/example.com"
RELEASE_DIR="$APP_ROOT/releases/$RELEASE_ID"

Клонируем код:

git clone \
    --depth 1 \
    --branch main \
    git@github-example:owner/project.git \
    "$RELEASE_DIR"

Подключаем .env:

ln -s \
    "$APP_ROOT/shared/.env" \
    "$RELEASE_DIR/.env"

Заменяем каталог storage ссылкой:

rm -rf "$RELEASE_DIR/storage"

ln -s \
    "$APP_ROOT/shared/storage" \
    "$RELEASE_DIR/storage"
Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Переходим в релиз:

cd "$RELEASE_DIR"

Устанавливаем зависимости:

composer install \
    --no-dev \
    --prefer-dist \
    --optimize-autoloader \
    --no-interaction

Собираем frontend:

npm ci
npm run build

Проверяем окружение:

composer check-platform-reqs
php artisan about

Готовим кэши:

php artisan optimize:clear
php artisan optimize

В этот момент сайт всё ещё обслуживается из предыдущего релиза.

1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Миграции в релизной схеме

Здесь возникает тонкий момент.

Если выполнить миграцию до переключения ссылки:

php artisan migrate --force

новая структура базы начнёт работать вместе со старым кодом.

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

Zero-downtime deployment возможен только тогда, когда миграции совместимы с обеими версиями приложения.

Если совместимость не гарантирована, лучше честно включить короткий maintenance mode:

php artisan down --retry=60
php artisan migrate --force
Первый месяц за 0 рублей
Хостинг для сайта, который должен работать стабильно
Перенесите проект или запустите новый сайт на Siteko.net и протестируйте сервис без предоплаты.
Перейти к хостингу

А затем переключить релиз и вернуть сайт.

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

Атомарно переключаем current

Запоминаем текущий релиз:

readlink -f "$APP_ROOT/current"

Создаём временную ссылку:

ln -s \
    "$RELEASE_DIR" \
    "$APP_ROOT/current.next"

Перемещаем её на место current:

mv -Tf \
    "$APP_ROOT/current.next" \
    "$APP_ROOT/current"

На Linux операция переименования внутри одной файловой системы выполняется как единое переключение: запрос увидит либо старую, либо новую ссылку, а не наполовину скопированный каталог.

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

Если в доступном окружении mv не поддерживает -T, используется более простой вариант:

ln -sfn \
    "$RELEASE_DIR" \
    "$APP_ROOT/current"

После переключения:

cd "$APP_ROOT/current"
php artisan up

Затем выполняются health-проверки.

Откат релизной схемы

Предположим, предыдущая версия находится здесь:

/home/user/www/example.com/releases/20260718110533

Откат кода:

APP_ROOT="$HOME/www/example.com"
PREVIOUS_RELEASE="$APP_ROOT/releases/20260718110533"

ln -s \
    "$PREVIOUS_RELEASE" \
    "$APP_ROOT/current.next"

mv -Tf \
    "$APP_ROOT/current.next" \
    "$APP_ROOT/current"

Сайт снова использует предыдущий каталог.

Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net

Повторно запускать Composer и npm не нужно: старый релиз уже содержит свои зависимости и frontend-сборку.

В этом главное преимущество релизной схемы:

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

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

Удаляем старые релизы

Хранить все версии бесконечно не нужно.

Например, оставим пять последних:

ls -1dt \
    "$APP_ROOT"/releases/* |
    tail -n +6 |
    xargs -r rm -rf

Перед удалением следует убедиться, что:

  • каталог current не указывает на удаляемый релиз;
  • предыдущая стабильная версия сохранена;
  • пользовательские данные действительно находятся в shared;
  • в старых релизах нет единственной копии загруженных файлов;
  • резервная копия базы существует отдельно.
Подключение за минуту
Попробуйте Siteko.net бесплатно в течение месяца
Проверьте хостинг на реальном сайте: скорость, стабильность и поддержка доступны сразу после подключения.
Начать бесплатно

Обычно достаточно хранить несколько последних релизов и более долгосрочные резервные копии.

Git-деплой не заменяет резервное копирование

Git хранит код, но не содержит:

  • production-базу;
  • пользовательские загрузки;
  • .env;
  • почтовые ящики;
  • DNS-записи;
  • закрытые ключи;
  • данные сторонних сервисов.

Поэтому команда:

git reset --hard HEAD~1

не является полноценным восстановлением сайта.

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

базу данных
shared/storage
production-конфигурацию
текущий commit или release ID

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

1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Этой теме будет посвящена двенадцатая часть серии.

Нужен ли автоматический деплой после каждого push

Технически репозиторий может отправить webhook, после которого сервер автоматически запустит deploy.sh.

Но автоматическая публикация каждого коммита из main подходит не всем проектам.

До production должны быть проверены:

  • тесты;
  • Composer-зависимости;
  • frontend-сборка;
  • синтаксис;
  • миграции;
  • статический анализ;
  • критические пользовательские сценарии.

Более безопасные варианты:

  • запускать deploy вручную после проверки;
  • публиковать только Git-теги;
  • использовать отдельную production-ветку;
  • требовать подтверждение перед production;
  • сначала разворачивать версию на тестовом поддомене.

Например:

feature branch

pull request

tests

main

staging

production tag

deploy
1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Чем важнее сайт, тем меньше production должен зависеть от одного случайного git push.

Деплой по ветке или по тегу

Обновление из ветки:

git fetch origin main
git reset --hard origin/main

подходит для небольшого проекта с простой моделью публикации.

Но ветка — движущаяся цель. Сегодня main указывает на один коммит, завтра — на другой.

Git-тег фиксирует конкретный релиз:

git tag -a v1.4.0 -m "Release 1.4.0"
git push origin v1.4.0

На сервере:

git fetch --tags
git checkout v1.4.0
Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Преимущества тегов:

  • версия релиза имеет понятное имя;
  • случайный новый commit не публикуется автоматически;
  • проще вести журнал изменений;
  • проще откатиться на v1.3.2;
  • production соответствует конкретному неизменяемому состоянию.

Для релизной схемы каталог можно назвать по версии:

releases/v1.4.0/

или сочетать дату и commit:

releases/20260721-91ad733/

Какие проверки выполнять перед переключением

Минимальный smoke test можно выполнить ещё в каталоге нового релиза:

php -l public/index.php
composer check-platform-reqs
php artisan about
php artisan migrate:status

Дополнительно:

php artisan route:list >/dev/null
php artisan config:show app >/dev/null

Для Symfony:

APP_ENV=prod APP_DEBUG=0 \
php bin/console about

APP_ENV=prod APP_DEBUG=0 \
php bin/console lint:container
Подключение за минуту
Попробуйте Siteko.net бесплатно в течение месяца
Проверьте хостинг на реальном сайте: скорость, стабильность и поддержка доступны сразу после подключения.
Начать бесплатно

После переключения проверяется HTTP:

curl \
    --fail \
    --silent \
    --show-error \
    https://example.com/up

Если команда завершилась с ошибкой, сценарий может автоматически вернуть предыдущую ссылку.

Но health-маршрут должен проверять не только то, что index.php способен вывести ответ. Для критического проекта полезно проверить:

  • соединение с основной базой;
  • доступность файлового хранилища;
  • правильность production-конфигурации;
  • обязательный внешний сервис;
  • возможность чтения кэша.

Не следует при этом превращать health-check в тяжёлый тест, который сам создаёт нагрузку.

Что делать с очередями во время деплоя

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

Для Laravel после публикации новой версии используется:

php artisan queue:restart
Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Команда просит workers завершить текущее задание и корректно перезапуститься. Для автоматического запуска нового процесса обычно нужен Supervisor или другой менеджер процессов.

Для Symfony Messenger:

php bin/console messenger:stop-workers

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

Если очередь запускается через cron:

php artisan queue:work \
    --stop-when-empty \
    --max-time=50

процесс сам завершается. Следующий cron-запуск уже загрузит новую версию приложения.

Что нельзя хранить внутри release-каталога

Следует особенно внимательно проверить:

public/uploads/
public/images/users/
storage/app/public/
var/uploads/
runtime/files/
Старт без оплаты
Месяц хостинга бесплатно для новых проектов
Разместите сайт, проверьте скорость и оцените удобство Siteko.net. Просто выберите тариф и начните тестовый месяц.
Посмотреть тарифы

Если пользовательские файлы находятся внутри каталога релиза, после смены current они внезапно «исчезнут»: новая версия просто не содержит старых загрузок.

Такие каталоги переносятся в shared и подключаются символической ссылкой.

Например:

ln -s \
    "$APP_ROOT/shared/uploads" \
    "$RELEASE_DIR/public/uploads"

Перед внедрением схемы нужно составить полный список данных, которые приложение создаёт само.

Грабли деплоя без FTP

Грабля Симптом Решение
Код исправляют прямо на сервере git pull останавливается из-за локальных изменений Все изменения проводить через commit и push
.env добавлен в Git Production-пароли попадают в репозиторий Исключить секреты и хранить их отдельно
composer.lock не закоммичен На каждом сервере устанавливаются разные пакеты Зафиксировать и публиковать lock-файл
На production выполняется composer update Во время деплоя неожиданно обновляются зависимости Использовать composer install
Выполняется npm install без единой стратегии Меняется lock-файл или дерево зависимостей Использовать зафиксированный lock и npm ci
git pull создаёт merge на сервере Production получает уникальную историю Использовать --ff-only или точный commit
Миграция удаляет поле сразу После отката старый код падает Делать изменения базы в совместимых этапах
Откат возвращает только Git Код старый, база и загрузки остались новыми Планировать откат данных отдельно
Пользовательские файлы лежат в release После переключения исчезают изображения Вынести изменяемые данные в shared
Composer запускается после переключения current Посетители видят релиз без vendor Полностью собирать релиз до переключения
Frontend не собран Новый PHP-код работает со старыми JS и CSS Включить build в обязательные шаги
Кэши не очищены Приложение использует старые маршруты или настройки Выполнять команды очистки и оптимизации
Два деплоя запущены одновременно Команды заменяют файлы друг у друга Добавить блокировку через flock
Deploy key имеет доступ на запись Компрометация сервера позволяет изменить репозиторий Использовать ключ только для чтения
Хранятся все релизы Заканчиваются диск и inode Оставлять несколько последних версий
Проверяется только главная страница Ошибка обнаруживается при первом заказе Выполнять health-check и критический smoke test

Какой вариант выбрать

Простой деплой в одном каталоге

Подходит, если:

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

Преимущества:

  • легко понять;
  • легко настроить;
  • мало каталогов и ссылок;
  • подходит почти любому SSH-хостингу.
Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net

Недостатки:

  • рабочий каталог временно находится в промежуточном состоянии;
  • откат требует повторной установки зависимостей;
  • ошибка Composer может увеличить простой;
  • frontend собирается внутри активного проекта.

Отдельный каталог для каждого релиза

Подходит, если:

  • сайт важен для бизнеса;
  • обновления выполняются регулярно;
  • нужен быстрый откат;
  • Composer или npm работают долго;
  • проект имеет понятное разделение кода и данных;
  • хостинг поддерживает symlink и произвольный Document Root.

Преимущества:

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

Недостатки:

  • сложнее первоначальная настройка;
  • нужно управлять shared;
  • старые релизы расходуют inode и диск;
  • миграции всё равно требуют отдельной стратегии;
  • не каждый проект изначально готов к неизменяемым release-каталогам.

Как это применить на Siteko

Для простой схемы достаточно:

  1. разместить проект через Git;
  2. направить домен на project/public;
  3. создать .env;
  4. выполнить Composer;
  5. подключить Node.js через nvm при необходимости;
  6. добавить cron;
  7. запускать проверенный deploy-сценарий по SSH.

Для релизной схемы:

Тестовый период
Оцените хостинг Siteko.net на своем проекте
Один бесплатный месяц поможет проверить панель, скорость и поддержку до оплаты следующего периода.
Открыть Siteko.net
  1. корень домена направляется на current/public;
  2. код хранится в releases;
  3. .env и изменяемые данные выносятся в shared;
  4. current переключается символической ссылкой;
  5. несколько предыдущих релизов сохраняются для отката.

Необходимые для обеих схем инструменты — SSH, Git, глобальный Composer, Node.js через nvm, произвольный Document Root и symlink внутри аккаунта — подробно разобраны в восьмой статье серии.

Постоянные Supervisor-workers на виртуальном хостинге не предполагаются. Если проект требует Horizon, нескольких постоянно работающих очередей, WebSocket-сервера или собственных демонов, процесс деплоя уже следует строить на VDS.

Что в итоге

Переход с FTP на Git решает не только проблему медленной загрузки файлов.

Он создаёт точку отсчёта:

этот сайт работает на commit 91ad733

Composer и lock-файл воспроизводят PHP-зависимости. npm ci и frontend-сценарий воспроизводят клиентскую сборку. Миграции описывают изменения базы. Maintenance mode защищает посетителей от промежуточного состояния. Health-check показывает, что приложение действительно запустилось.

Но сам по себе Git ещё не делает деплой безопасным.

1 месяц бесплатно
Запустите сайт на Siteko.net без стартовых затрат
Быстрый хостинг, понятная панель и поддержка рядом с первого дня. Тестовый месяц доступен сразу после выбора тарифа.
Выбрать хостинг

Для этого нужны:

  • чистый production-каталог;
  • фиксированный порядок команд;
  • отделение кода от данных;
  • проверка каждого шага;
  • совместимые миграции;
  • резервная копия;
  • заранее проверенный откат.

Для небольшого проекта достаточно обновления в одном каталоге с коротким maintenance mode. Для более важного сайта лучше готовить отдельные релизы и переключать current только после успешной сборки.

Главный принцип можно сформулировать так:

Новая версия должна сначала стать готовым приложением и только потом стать работающим сайтом.

Следующая часть серии будет посвящена ситуации, когда этот процесс всё же завершился ошибкой: сайт отвечает кодом 500, показывает пустую страницу или перестаёт открываться после переноса. Разберём, где искать причину до обращения в поддержку — от логов и версии PHP до .env, прав, Document Root и отсутствующих расширений.

Хостинг Siteko

Первый месяц бесплатно

Хостинг Siteko.net для стабильного запуска сайта

Разместите проект на Siteko.net и проверьте скорость, панель управления и поддержку без стартовой оплаты.

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