Top.Mail.Ru
OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

Статический сайт на виртуальном хостинге: CI‑сборка и деплой rsync

Разбираем рабочий пайплайн: коммит в Git, сборка в GitHub Actions и автоматический деплой на виртуальный хостинг через rsync по SSH. Покажу готовые workflow, безопасные ключи, кэширование, выкладку без простоя и быстрые откаты.
Статический сайт на виртуальном хостинге: CI‑сборка и деплой rsync

Задача проста: исходники статического сайта в Git, каждый push в основную ветку автоматически собирает проект и заливает на виртуальный хостинг. В деталях всё сложнее: нужно аккуратно настроить SSH-ключи, подобрать флаги rsync, продумать кэширование и уметь откатываться. Ниже — практический конспект с примерами рабочих конфигов.

Зачем статика и почему rsync на виртуальном хостинге

Статический сайт на виртуальном хостинге — минимальные требования к серверу, высокая скорость, низкие накладные расходы и почти нулевая поверхность атаки. На стороне хостинга нужен лишь веб-сервер и доступ по SSH. Для доставки артефактов идеально подходит rsync: он передаёт только изменения, экономит трафик и время деплоя, а ещё даёт гибкую политику удаления старых файлов.

Ключевое преимущество rsync — дифференциальная передача и точный контроль над конечным деревом файлов через --delete.

Целевой процесс

Соберём такой поток:

  • Вы коммитите изменения и пушите в main.
  • GitHub Actions запускает сборку статического сайта.
  • Готовая папка dist (или public) отправляется на хостинг через rsync по SSH.
  • Опционально — выкладка через промежуточный каталог для минимизации «дрожания» сайта во время обновления.

Подготовка площадки на хостинге

Перед первым деплоем проверьте базовое:

  • Доступ по SSH: логин, хост, порт, домашний каталог, документ‑рут (например, ~/public_html).
  • Наличие rsync на сервере: ssh -p 22 user@host rsync --version должен отработать.
  • Достаточно прав на запись в целевой каталог и создание подкаталогов (например, ~/releases, ~/stage).

Создайте на стороне хостинга финальный путь для артефактов, если его нет:

ssh -p 22 user@host "mkdir -p ~/public_html"

Промежуточный каталог stage и releases для безболезненных релизов на хостинге

Подготовка репозитория

Распределите структуру проекта так, чтобы артефакты сборки были в одной папке: dist, build, public — не критично как называется, главное — единообразие. Добавьте в .gitignore артефакты и кэш:

dist/
.cache/
node_modules/

Для стабильного окружения фиксируйте версии инструментов: package-lock.json для Node‑проектов или определённую версию Hugo/Eleventy/11ty. При необходимости добавьте .gitattributes и включите нормализацию концов строк.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Workflow GitHub Actions: Node‑статик (Vite/Eleventy/Next static export)

Базовый workflow, который собирает проект на Node 20 и деплоит готовую папку dist через rsync. Секреты укажем в настройках репозитория: SSH_PRIVATE_KEY, DEPLOY_HOST, DEPLOY_PORT (обычно 22), DEPLOY_USER, DEPLOY_PATH (например, /home/user/public_html).

name: build-and-deploy
on:
  push:
    branches: [ "main" ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install deps
        run: npm ci

      - name: Build
        run: npm run build

      - name: Prepare SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -p "${{ secrets.DEPLOY_PORT }}" "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts

      - name: Create remote dir if missing
        run: ssh -p "${{ secrets.DEPLOY_PORT }}" "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" "mkdir -p \"${{ secrets.DEPLOY_PATH }}\""

      - name: Rsync deploy
        run: rsync -az --delete --delay-updates --omit-dir-times --no-perms --no-owner --no-group --human-readable --stats -e "ssh -p ${{ secrets.DEPLOY_PORT }}" ./dist/ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/"

Комментарии к ключам:

  • -a архивный режим (сохраняет время и рекурсивно копирует),
  • -z сжатие,
  • --delete удаляет файлы, которых нет в исходном каталоге,
  • --delay-updates сначала загружает во временные файлы и потом заменяет — меньше «дрожания» при обновлениях,
  • --omit-dir-times избегает попытки установить время на директориях (иногда запрещено на шаред‑хостинге),
  • --no-perms --no-owner --no-group не пытается менять права/владельца,
  • --human-readable --stats удобная статистика.

Обратите внимание на слэш в конце ./dist/: с ним rsync переносит содержимое каталога, а не сам каталог.

Альтернатива: Hugo

Если вы на Hugo, используйте подобную схему: сначала установка конкретной версии Hugo, затем hugo --minify и тот же шаг с rsync. Суть остаётся прежней — деплоим папку public.

- name: Install Hugo
  run: sudo apt-get update && sudo apt-get install -y hugo
- name: Build
  run: hugo --minify
- name: Rsync deploy
  run: rsync -az --delete --delay-updates -e "ssh -p ${{ secrets.DEPLOY_PORT }}" ./public/ "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/"

Минимизация простоя: два безопасных подхода

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

1) Отложенная замена файлов (--delay-updates)

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

2) Промежуточный каталог на сервере

Идея: выгрузить сборку в ~/stage и одной дальней командой синхронизировать на боевой каталог. Две короткие операции уменьшают окно несогласованности.

# 1) выгружаем свежую сборку на сервер в stage
rsync -az --delete -e "ssh -p $PORT" ./dist/ user@host:~/stage/
# 2) на сервере быстро синхронизируем stage -> public_html
ssh -p $PORT user@host "rsync -a --delete --omit-dir-times --no-perms --no-owner --no-group ~/stage/ ~/public_html/"

Можно добавить каталог ~/releases/timestamp и хранить несколько версий для откатов — подробно об этом ниже. Отдельно рекомендую материал Миграция сайта без простоя — пригодится при переносе на новую площадку.

Кэширование: быстрые отлеты из браузера и CDN

Статика раскрывается на полную только вместе с грамотным кэшированием. Принцип прост: HTML — короткий TTL (или вообще без кэша), а ассеты с хешем в имени — на год с флагом immutable. При этом новый релиз меняет имя файла (fingerprint) — и браузеры моментально подхватывают его.

Апач (.htaccess)

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

<IfModule mod_headers.c>
  <FilesMatch "\.(css|js|mjs|svg|ico|png|jpg|jpeg|gif|webp|avif|woff|woff2)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
  <FilesMatch "\.(html)$">
    Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0"
  </FilesMatch>
</IfModule>

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType text/html "access"
  ExpiresDefault "access plus 1 year"
</IfModule>

Nginx (для тех, у кого есть доступ к серверному конфигу)

Если площадка отдаётся Nginx и вы управляете конфигом (или через панель есть пользовательские директивы), логика та же. Полный доступ чаще встречается на VDS:

location ~* \.(css|js|mjs|svg|ico|png|jpg|jpeg|gif|webp|avif|woff|woff2)$ {
  expires 1y;
  add_header Cache-Control "public, max-age=31536000, immutable";
}
location ~* \.(html)$ {
  add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0";
}

В любом случае используйте fingerprinting: в процессе сборки добавляйте хеш содержимого в имя файла (app.4f9c1.css). Современные сборщики (Vite, webpack, Hugo) умеют это «из коробки». Не забудьте HTTPS: подключите SSL для корректного кэширования и безопасности.

Безопасность: ключи, known_hosts и права

Секреты храните только в Secrets репозитория/организации. Лучше генерировать отдельный деплой‑ключ и выдать ему доступ только к нужной учётке на хостинге. В раннере:

  • кладём приватный ключ в ~/.ssh/id_ed25519 с правами 600;
  • добавляем ключ хоста в known_hosts через ssh-keyscan — так сохраняем StrictHostKeyChecking;
  • не передаём пароль в явном виде, не отключаем проверку ключа сервера опцией -o StrictHostKeyChecking=no.

На стороне хостинга ограничьте права каталога деплоя только своим пользователем. Если доступно, включите двухфакторную авторизацию в панели, отделите пользовательские учётки «для людей» и «для CI».

Откаты: хранение нескольких релизов

Статика прекрасна тем, что «легендарный откат» — это просто переключение каталога. На виртуальном хостинге это можно реализовать без прав суперпользователя.

  1. Заливка в каталог релиза. Каждый билд выгружайте в ~/releases/<timestamp>.
  2. Быстрое обновление боевого каталога. После заливки выполняйте локальную синхронизацию на сервере.
  3. Чистка старых версий. Держите 3–5 релизов, остальное удаляйте.
# 1) создание каталога релиза
ssh -p $PORT user@host "mkdir -p ~/releases/2025-01-01_120000"
# 2) выгрузка в релиз
rsync -az --delete -e "ssh -p $PORT" ./dist/ user@host:~/releases/2025-01-01_120000/
# 3) быстрое обновление продового каталога
ssh -p $PORT user@host "rsync -a --delete --omit-dir-times --no-perms --no-owner --no-group ~/releases/2025-01-01_120000/ ~/public_html/"
# 4) чистка старых релизов (оставляем 5)
ssh -p $PORT user@host "ls -1dt ~/releases/* | tail -n +6 | xargs -r rm -rf"

Такой подход обеспечивает предсказуемый откат: просто повторите шаг 3 с прежним каталогом релиза. Дополнительно про бэкапы и транспорт — в статье Резервные копии и rsync: практики.

Диагностика и типичные ловушки rsync

  • Права и владельцы. На шаред‑хостинге часто нельзя менять владельца/группу. Отключайте это флагами --no-owner --no-group, а также --no-perms, если права выставляются на стороне сервера.
  • Временные метки директорий. Некоторые FS запрещают менять их время — используйте --omit-dir-times.
  • Пустой деплой. Проверьте, что путь назначения существует и у вас есть права на запись. Перед первым деплоем выполните ssh user@host "mkdir -p /path".
  • Слэш в конце пути. Убедитесь, что понимаете семантику ./dist/ vs ./dist: первый копирует содержимое, второй создаст вложенную папку.
  • Трим старых файлов. Если вы переименовали папку ассетов, но не включили --delete, «мусор» останется и может продолжить отдаваться по прямым URL. Включайте --delete и поддерживайте чистоту.
  • Проверка перед выкладкой. Запускайте rsync с --dry-run --itemize-changes, когда меняете флаги, чтобы увидеть, что именно будет обновлено.

Производительность сборки в CI

Чтобы ускорить пайплайн, включайте кэш менеджера пакетов: для Node — cache: 'npm' в шаге actions/setup-node. Фиксируйте версии, избегайте установок глобальных инструментов, держите сборку идемпотентной. Следите, чтобы в артефакты не попадали лишние мегабайты (исключайте исходники, карты, черновики и т. п., если они не нужны в проде).

Контроль кэша на стороне клиента

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

  • Хеши в именах ассетов — длинный TTL и immutable.
  • HTML и JSON‑манифесты — короткий TTL или без кэша, чтобы новые версии ассетов подтягивались мгновенно.

Отдельно проверьте, что в шаблонах HTML путь к ассетам действительно обновляется на новые имена. Большинство сборщиков генерируют манифест и автоматически подставляют пути.

Расширения: предпросмотр Pull Request и матрица окружений

Полезно добавить job, который собирает сайт для каждой ветки или PR и выгружает его в самостоятельный префикс каталога, например ~/previews/pr-123/. Это не заменяет продовый деплой, но сильно помогает в тестировании. Очистку таких окружений привязывайте к закрытию PR.

Предпросмотр окружений для Pull Request в GitHub Actions

Итоги

Статический сайт на виртуальном хостинге плюс GitHub Actions и rsync — надёжная, быстрая и экономичная схема. Один раз настроили SSH‑ключи, подобрали флаги, оформили правила кэша — и дальше деплой занимает секунды. Для безболезненных релизов используйте --delay-updates или промежуточный каталог, храните несколько релизов для откатов и держите секреты в порядке. Остальное за вас сделает автоматизация.

Поделиться статьей

Вам будет интересно

Nginx SSI и подзапросы: сборка страниц из блоков с кэшированием OpenAI Статья написана AI Fastfox

Nginx SSI и подзапросы: сборка страниц из блоков с кэшированием

Практическое руководство по Nginx SSI и subrequest: сборка страницы из блоков, фрагментное кэширование, разделение гостей и автори ...
Мониторинг OPcache: метрики, алерты и быстрая диагностика утечек OpenAI Статья написана AI Fastfox

Мониторинг OPcache: метрики, алерты и быстрая диагностика утечек

OPcache ускоряет PHP, но под нагрузкой может «захлебнуться»: заканчивается память, растёт фрагментация, падает hit rate. Разбираем ...
rclone для больших файлов: multipart‑загрузки, параллелизм и контроль памяти OpenAI Статья написана AI Fastfox

rclone для больших файлов: multipart‑загрузки, параллелизм и контроль памяти

Большие файлы и S3 требуют точной настройки rclone: multipart‑загрузка, параллелизм потоков, контроль памяти и полосы, устойчивост ...