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

Git‑деплой на VDS: GitHub и GitLab без лишней магии

Разбираем, как организовать удобный и безопасный деплой проекта на VDS с помощью git и репозиториев на GitHub или GitLab. Настроим bare‑репозиторий и post‑receive hook, доступ по SSH‑ключам и простую схему с релизными каталогами и CI для выката кода на боевой сервер.
Git‑деплой на VDS: GitHub и GitLab без лишней магии

Git давно стал стандартом для разработки, но на продакшн‑серверы код до сих пор часто выкладывают через SFTP или rsync "вручную". Это больно, медленно и приводит к ошибкам: забытые файлы, несинхронизированные ветки, случайный rm -rf в корне сайта.

В этой статье разберём практичную схему:

  • репозиторий проекта живёт на GitHub или GitLab;
  • на VDS есть свой bare‑репозиторий (git‑зеркало);
  • деплой на боевой каталог происходит через git‑hook или отдельным скриптом;
  • доступ – только по SSH‑ключам, минимум прав, никаких паролей.

Это не заменяет полноценный CI/CD, но даёт простой и предсказуемый деплой, который реально удобно использовать на одиночном VDS‑сервере.

Подходы к деплою через git на VDS

Сначала определимся с архитектурой. Есть три основных паттерна деплоя кода на VDS через git:

  • push из локальной машины в VDS – вы пушите из своего ноутбука прямо в репозиторий на сервере;
  • pull с VDS из GitHub/GitLab – сервер сам делает git pull из удалённого репо;
  • комбинированная схема (зеркало + hook) – VDS держит bare‑репозиторий, обновляемый с GitHub/GitLab, и из него раскатывает код в рабочую директорию.

Рассмотрим плюсы и минусы самых практичных вариантов и соберём из них рабочую схему.

Вариант 1: git push напрямую на VDS

Классическая схема: на VDS создаётся bare‑репозиторий, вы добавляете его как remote в локальный git и делаете git push vds main. В post-receive‑хуке этот bare‑репозиторий раскатывает рабочую копию в каталог сайта.

Плюс – отсутствие зависимости от сторонних сервисов. Минус – вам нужно открывать SSH наружу к VDS и раздавать доступ всем разработчикам прямо на боевой сервер.

Такой вариант часто используют в маленьких командах или для внутренних сервисов без GitHub/GitLab. В статье мы его коснёмся, но основной фокус будет на связке с внешним репозиторием.

Вариант 2: VDS сам делает git pull с GitHub/GitLab

Тут рабочая копия сайта на сервере сама привязана к удалённому репозиторию:

  1. один раз делаем git clone с GitHub/GitLab на VDS;
  2. при деплое заходим на сервер и выполняем git pull origin main;
  3. опционально – оборачиваем это в скрипт или обвязку в CI.

Плюсы:

  • репозиторий остаётся централизованным (GitHub/GitLab);
  • удобная интеграция с CI: раннер может сам ходить на VDS и запускать деплой‑скрипт.

Минусы:

  • нужно хранить деплой‑ключи на VDS, следить за правами и ротацией;
  • надо аккуратно избегать ручных коммитов прямо в рабочую копию на сервере (иначе конфликты при pull).

Вариант 3: bare‑зеркало на VDS + деплой из него

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

  1. на VDS создаётся bare‑репозиторий (без рабочей копии);
  2. GitHub/GitLab пушит в него через deploy key или CI;
  3. hook bare‑репозитория обновляет один или несколько рабочих каталогов (prod, staging и т.п.).

Плюсы:

  • можно легко делать несколько сред из одного репозитория;
  • hook централизованно контролирует, как и куда обновлять код;
  • можно повесить доп. логику (composer, npm, миграции, рестарт сервисов).

Минусы:

  • настройка сложнее, чем просто git pull в рабочем каталоге;
  • нужно внимательно продумать права на каталоги и пользователя деплоя.

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

Подготовка пользователя и каталогов на VDS

Дальше все примеры будут даны для Linux‑VDS (Debian/Ubuntu‑семейство), доступ по SSH от имени root или пользователя с sudo.

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

Создаём отдельного пользователя для деплоя

Не стоит деплоить от root. Создадим пользователя, скажем, deploy:

adduser --disabled-password --gecos "Deploy user" deploy

Положим каталоги так:

  • /home/deploy/repos/myproject.git – bare‑репозиторий;
  • /var/www/myproject – рабочая копия сайта (отдаётся веб‑сервером);
  • /var/www/myproject_releases – опционально, если будете делать релизы по релизным каталогам.

Создадим базовую структуру:

mkdir -p /home/deploy/repos
mkdir -p /var/www/myproject
chown -R deploy:deploy /home/deploy
chown -R deploy:deploy /var/www/myproject

Настраиваем SSH‑доступ по ключу

Если у вас ещё нет SSH‑ключа на рабочей машине, сгенерируйте его:

ssh-keygen -t ed25519 -C "dev-on-laptop"

Скопируйте публичный ключ на VDS для пользователя deploy:

ssh-copy-id deploy@your-vds-host

Проверьте вход:

ssh deploy@your-vds-host

Пароль спрашиваться не должен, только подтверждение fingerprint при первом подключении.

Создание bare‑репозитория на VDS

Под пользователем deploy создадим bare‑репо:

ssh deploy@your-vds-host
cd ~/repos
mkdir myproject.git
cd myproject.git
git init --bare

Bare‑репозиторий нужен, чтобы принимать пуши, но не содержать рабочую копию файлов – это просто хранилище ссылок и объектов git.

Внутри bare‑репозитория будет каталог hooks. Мы будем использовать post-receive‑hook, который после успешного пуша развернёт код в /var/www/myproject.

Связка локального git с bare‑репозиторием на VDS

Теперь привяжем ваш локальный проект к репозиторию на VDS. В каталоге проекта:

git remote add vds deploy@your-vds-host:/home/deploy/repos/myproject.git

Проверьте список remotes:

git remote -v

Сделайте первый пуш (например, ветки main):

git push vds main

На этом этапе код ещё не появляется в каталоге сайта: он только попал в bare‑репозиторий. Теперь научим VDS выполнять деплой после каждого пуша.

Схема деплоя через bare‑репозиторий git на VDS и рабочую директорию сайта

Настройка git hook для деплоя на VDS

Hook post-receive в bare‑репозитории запускается после того, как в него пришли новые коммиты. Это удобная точка для деплоя.

Простой деплой без релизов

Для начала сделаем минимальный, но рабочий hook, который:

  • обновит (или создаст) рабочую копию в /var/www/myproject;
  • переключит её на нужную ветку (например, main);
  • сделает git reset --hard на последний коммит;
  • опционально выполнит команды сборки (composer, npm и т.п.).

Под пользователем deploy создадим или отредактируем файл /home/deploy/repos/myproject.git/hooks/post-receive:

#!/bin/bash
set -e

REPO_DIR="/home/deploy/repos/myproject.git"
WORK_TREE="/var/www/myproject"
BRANCH="main"

if [ ! -d "$WORK_TREE/.git" ]; then
  mkdir -p "$WORK_TREE"
  git --work-tree="$WORK_TREE" --git-dir="$REPO_DIR" checkout -f "$BRANCH"
else
  git --work-tree="$WORK_TREE" --git-dir="$REPO_DIR" fetch origin "$BRANCH"
  git --work-tree="$WORK_TREE" --git-dir="$REPO_DIR" checkout -f "$BRANCH"
fi

cd "$WORK_TREE"
# Примеры: раскомментируйте, если нужно
# composer install --no-dev --optimize-autoloader
# npm install
# npm run build

exit 0

Сделаем его исполняемым:

chmod +x /home/deploy/repos/myproject.git/hooks/post-receive

Несколько важных замечаний по этому скрипту:

  • set -e заставит скрипт падать при ошибке любой команды – это хорошо, чтобы не получить полусобранный деплой;
  • используем --work-tree и --git-dir, чтобы не инициализировать полноценный репозиторий в /var/www/myproject – там можно оставить только рабочие файлы;
  • в примерах сборки обязательно учитывайте, под кем выполняется hook (пользователь deploy, его $PATH, версия PHP/Node и т.д.).

Теперь при каждом git push vds main код автоматически обновляется на сервере.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Интеграция с GitHub: деплой через push или CI

Если ваш "центральный" репозиторий живёт на GitHub, bare‑репо на VDS можно использовать как:

  • дополнительный remote, в который пушат разработчики;
  • "target" для GitHub Actions или другого CI, который пушит только из проверенного пайплайна.

Пуш из локального git и GitHub одновременно

Простейший вариант – оставить GitHub как основной origin, а VDS добавить вторым remote:

git remote get-url origin
# origin указывает на GitHub

git remote add vds deploy@your-vds-host:/home/deploy/repos/myproject.git

Теперь вы можете пушить так:

git push origin main
git push vds main

Чтобы не забывать про пуш на VDS, можно настроить alias в git или локальный скрипт, но важнее – договориться в команде, что деплой всегда делает ответственный человек (или CI).

Деплой через GitHub Actions

Более чистый вариант – деплой делает только CI после прохождения тестов. Схема такая:

  1. на VDS создаём SSH‑ключ, которым GitHub Actions будет логиниться на сервер;
  2. публичный ключ добавляем в ~/.ssh/authorized_keys пользователя deploy;
  3. приватный ключ кладём в Secrets репозитория на GitHub;
  4. в workflow GitHub Actions выполняем git push в bare‑репо на VDS.

Ключи генерируем на VDS под пользователем deploy:

ssh deploy@your-vds-host
cd ~
ssh-keygen -t ed25519 -f ~/.ssh/github-actions -C "github-actions-deploy"

Публичный ключ ~/.ssh/github-actions.pub добавьте в ~/.ssh/authorized_keys (если ssh-keygen сам не предложил).

Приватный ключ (содержимое файла github-actions) скопируйте и занесите как секрет, например, DEPLOY_SSH_KEY в настройках репозитория GitHub.

В .github/workflows/deploy.yml можно сделать джоб, который после сборки и тестов пушит в VDS:

name: Deploy to VDS

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh
          echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          echo "Host vds-host" >> ~/.ssh/config
          echo "  HostName your-vds-host" >> ~/.ssh/config
          echo "  User deploy" >> ~/.ssh/config
          echo "  IdentityFile ~/.ssh/id_ed25519" >> ~/.ssh/config

      - name: Add VDS remote
        run: |
          git remote add vds deploy@your-vds-host:/home/deploy/repos/myproject.git
          git push vds main

Здесь важно:

  • не показывать приватный ключ в логах (используем секреты GitHub);
  • по возможности ограничить ключ в authorized_keys только нужными хостами/командами через опции from=, command= и т.п.;
  • настроить firewall так, чтобы SSH‑порт был доступен только из нужных подсетей, если это возможно.

Интеграция с GitLab: репозиторий и Runner → VDS

В GitLab схема похожая, но можно пойти двумя путями:

  • использовать стандартный GitLab CI/CD, который пушит в bare‑репо на VDS;
  • использовать GitLab Runner, установленный прямо на VDS, и деплоить локально.

GitLab CI/CD пушит в bare‑репо на VDS

Алгоритм тот же, что и с GitHub Actions:

  1. создаём SSH‑ключ для GitLab CI (на VDS или локально);
  2. публичный ключ помещаем в authorized_keys пользователя deploy;
  3. приватный ключ добавляем в переменную GitLab CI (в Settings → CI/CD → Variables);
  4. в .gitlab-ci.yml описываем джоб, который пушит в deploy@your-vds-host:/home/deploy/repos/myproject.git.

Пример простого пайплайна:

stages:
  - test
  - deploy

test:
  stage: test
  script:
    - echo "Run your tests here"

deploy:
  stage: deploy
  only:
    - main
  before_script:
    - mkdir -p ~/.ssh
    - echo "$DEPLOY_SSH_KEY" > ~/.ssh/id_ed25519
    - chmod 600 ~/.ssh/id_ed25519
    - ssh-keyscan your-vds-host >> ~/.ssh/known_hosts
  script:
    - git remote add vds deploy@your-vds-host:/home/deploy/repos/myproject.git
    - git push vds main

Переменная DEPLOY_SSH_KEY должна содержать приватный ключ, который имеет доступ к пользователю deploy на VDS.

GitLab Runner на VDS

Если у вас есть root‑доступ к VDS, можно поставить GitLab Runner прямо на него. Тогда деплой превращается просто в локальные команды (без отдельного SSH): Runner сам выполняет скрипты на сервере.

Плюсы:

  • нет SSH из CI в прод: Runner уже живёт на этом сервере;
  • проще работать с локальными путями, сокетами, службами;
  • можно использовать shell‑executor и не морочиться с Docker.

Минусы:

  • необходимо следить за безопасностью Runner (ограничения проектов, доступы);
  • при компрометации проекта с правом деплоя потенциально уязвим сам VDS.

Деплой‑задача в .gitlab-ci.yml в таком случае может просто выполнять:

script:
  - cd /var/www/myproject
  - git fetch origin main
  - git checkout -f main
  - composer install --no-dev --optimize-autoloader

Здесь origin – это GitLab. Вы один раз клонируете репозиторий в /var/www/myproject, а CI только подтягивает изменения.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Продвинутый деплой: релизные каталоги и symlink

Прямой git checkout -f в каталоге, который сейчас обслуживает продакшн, имеет очевидный минус: во время деплоя (особенно с длинным composer install или сборкой фронтенда) пользователи могут ловить ошибки.

Более надёжная схема – релизные каталоги, по сути упрощённый аналог Capistrano/Deployer:

  • /var/www/myproject/releases/<timestamp> – полный код очередного релиза;
  • /var/www/myproject/current – symlink на активный релиз;
  • /var/www/myproject/shared – общие ресурсы (логи, аплоады, .env).

Тогда деплой превращается в:

  1. собрать новый релиз в каталоге releases/...;
  2. настроить права, прогнать миграции, прогреть кэш;
  3. переключить current на новый релиз атомарной операцией ln -sfn;
  4. опционально удалить старые релизы.

Hook post-receive можно доработать под такую схему. Простейший набросок (без shared‑директорий и миграций):

#!/bin/bash
set -e

REPO_DIR="/home/deploy/repos/myproject.git"
APP_DIR="/var/www/myproject"
RELEASES_DIR="$APP_DIR/releases"
BRANCH="main"

TIMESTAMP=$(date +%Y%m%d%H%M%S)
NEW_RELEASE="$RELEASES_DIR/$TIMESTAMP"

mkdir -p "$RELEASES_DIR"

git --work-tree="$NEW_RELEASE" --git-dir="$REPO_DIR" checkout -f "$BRANCH"

cd "$NEW_RELEASE"
# composer install --no-dev --optimize-autoloader
# npm install
# npm run build

ln -sfn "$NEW_RELEASE" "$APP_DIR/current"

# Удаляем старые релизы, оставляя, например, 5 последних
cd "$RELEASES_DIR"
ls -1t | tail -n +6 | xargs -r rm -rf

exit 0

Веб‑сервер (например, nginx) в таком случае должен быть настроен на root /var/www/myproject/current/public;. Пример настройки nginx со статическим кэшем и оптимизациями есть в статье про оптимизацию PHP, OPCache и Brotli – многие идеи оттуда применимы и на VDS.

Структура релизных каталогов с symlink current для деплоя без простоя

Безопасность: SSH, права и защита от случайностей

При деплое через git на VDS важно не только "чтобы работало", но и чтобы не превратить сервер в проходной двор.

Ограничиваем доступ по SSH

Базовые рекомендации:

  • запретите вход по паролю (только ключи) – настройка PasswordAuthentication no в /etc/ssh/sshd_config;
  • по возможности ограничьте вход только нужным пользователям (AllowUsers deploy root и т.п.);
  • используйте нестандартный порт SSH только как дополнительный шум, но не как основную защиту.

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

Права на файлы и каталоги

Типичные роли:

  • веб‑сервер (nginx/Apache) работает от пользователя www-data;
  • деплой выполняется от пользователя deploy;
  • часть каталогов (аплоады, логи) должна быть доступна обеим ролям.

Базовая схема:

  • группа www-data добавлена к пользователю deploy или наоборот;
  • umask для деплоя настроен так, чтобы файлы были доступны группе на чтение;
  • права на каталоги – минимум 755, на файлы – 644 (если нужно только чтение).

Следите, чтобы в репозиторий не попадали конфиги с секретами (.env, ключи и т.п.). Лучше держать их в /var/www/myproject/shared или отдельном каталоге с ограниченными правами, а в коде ссылаться на них через переменные окружения и симлинки. Для хранения и выката секретов в git‑ориентированных пайплайнах можно использовать подходы из статьи про SOPS и GitOps‑секреты.

Типичные грабли и как их обходить

Ручные правки на сервере

Самая частая проблема: кто-то правит файлы прямо в /var/www/myproject, а затем git push затирает эти изменения.

Никаких правок кода на сервере. Всё только через git.

Если нужны быстрые эксперименты – отдельная ветка и отдельная среда (staging), но не прод.

Конфликты при git pull

Если вы используете схему без bare‑репо и делаете git pull прямо в рабочей копии, не делайте там git commit. Иначе при pull легко поймать конфликты, которые придётся решать вручную (на проде!).

Лучше или перейти на bare‑схему с hook, или жёстко запретить коммиты в рабочей директории.

Зависимости и бинарники в репозитории

Иногда складывают в репозиторий всё подряд: vendor, node_modules, собранные JS/CSS, картинки в нескольких копиях. Это раздувает репозиторий и делает пуши медленнее.

Рекомендации:

  • в идеале хранить в git только исходники, а сборку и установку зависимостей делать в hook или CI;
  • если сборка тяжёлая и занимает минуты, собирать артефакт в CI и выкладывать его на VDS по SSH или rsync, а git использовать как триггер деплоя;
  • обязательно держать .gitignore в порядке, не коммитить временные файлы и кэш.

Сломанный деплой из-за окружения

Разные версии PHP/Node, отсутствие composer/npm на сервере, отличия в модулях – всё это может выстрелить только на проде, если собирать проект там же.

Минимизировать риски можно так:

  • собирать проект в CI в максимально похожем окружении на VDS;
  • фиксировать версии зависимостей (composer.lock, package-lock.json и т.п.);
  • хранить конфигурацию среды (версии PHP, Node, расширения) в документации или в виде конфигов для конфигурационного менеджмента.

Когда имеет смысл перейти на полноценный CI/CD

Git‑деплой на VDS через bare‑репозиторий и hooks – отличный старт для небольших проектов. Но по мере роста требований и команды вы, скорее всего, упрётесь в ограничения:

  • нужно тестировать код на разных версиях PHP/Node/БД;
  • нужны миграции БД с откатами и проверками;
  • нужно несколько VDS (прод, стейджинг, превью‑окружения на каждый merge request);
  • нужен аудит деплоя: кто, когда, что выкатил.

Тогда уже имеет смысл строить полноценный pipeline в GitHub Actions или GitLab CI, использовать отдельного пользователя/сервис на VDS только для деплоя, учитывать миграции БД, состояние приложений и делать деплой атомарным и наблюдаемым.

Но фундамент всё равно будет тем же: git как источник истины, доступ по ключам, понятный скрипт деплоя и аккуратная работа с окружением на VDS.

Итоги

Мы разобрали практичную схему деплоя на VDS с помощью git, GitHub и GitLab:

  • создали отдельного пользователя и bare‑репозиторий на VDS;
  • настроили post-receive‑hook для автоматического обновления кода;
  • показали, как интегрировать деплой с GitHub Actions и GitLab CI;
  • обсудили релизные каталоги, безопасность SSH и типичные грабли.

Такой подход сохраняет простоту классического git‑деплоя, но даёт возможность масштабировать процесс: добавлять стейджинги, релизы, миграции и полноценный CI/CD, не меняя базовую архитектуру. А если вы только выбираете площадку под такой деплой, имеет смысл сразу смотреть в сторону надёжного хостинга с поддержкой VDS‑тарифов, где все эти практики можно реализовать без танцев с бубном.

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

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

cron healthchecks на VDS: контроль фоновых задач и защита от дабл-старта OpenAI Статья написана AI (GPT 5)

cron healthchecks на VDS: контроль фоновых задач и защита от дабл-старта

Регулярные задачи на VDS часто живут своей жизнью: падают молча, зависают, стартуют в двух экземплярах и конфликтуют за ресурсы. Р ...
HTTP end-to-end tracing: X-Request-ID, W3C Trace Context и заголовки OpenTelemetry OpenAI Статья написана AI (GPT 5)

HTTP end-to-end tracing: X-Request-ID, W3C Trace Context и заголовки OpenTelemetry

Когда микросервисов становится десяток и больше, а запросы проходят через несколько gateway, очередей и фоновых воркеров, простого ...
S3 и CDN для WordPress и Laravel: offload медиа и статики без боли OpenAI Статья написана AI (GPT 5)

S3 и CDN для WordPress и Laravel: offload медиа и статики без боли

Разбираем, как вынести медиа и статические файлы WordPress и Laravel в S3‑совместимый object storage и повесить сверху CDN. Пошаго ...