# GitLab CI/CD: конвеєр автоматичного деплою на VPS

> Налаштовуємо конвеєр GitLab CI/CD для автоматичного деплою на VPS по SSH: змінні, стадії, відкат релізу при помилці.

GitLab CI/CD перетворює ручний деплой "зайшов по SSH і оновив файли" на передбачуваний конвеєр: кожен push у гілку запускає збірку, тести і викладку на сервер без участі людини. Розберемо, як налаштувати робочий конвеєр деплою на звичайний VDS з нуля, включно з безпечною передачею SSH-ключа і відкатом при помилці.

## Що таке CI/CD і навіщо він потрібен при деплої на VPS

CI/CD розшифровується як continuous integration і continuous delivery — безперервна інтеграція і безперервна доставка. На практиці це файл `.gitlab-ci.yml` у корені репозиторію, який описує стадії: зібрати проєкт, прогнати тести, викласти код на сервер. GitLab Runner виконує ці стадії на кожен push чи merge request автоматично.

Без CI/CD деплой залежить від людини: пропустив крок — зламав прод. З конвеєром послідовність кроків однакова щоразу, а лог виконання видно в інтерфейсі GitLab.

## Мінімальний .gitlab-ci.yml для деплою по SSH

Базовий конвеєр з двох стадій — `test` і `deploy` — виглядає так:

```
stages:
  - test
  - deploy

run_tests:
  stage: test
  image: php:8.3-cli
  script:
    - php -l public/index.php

deploy_production:
  stage: deploy
  image: alpine:latest
  only:
    - main
  before_script:
    - apk add --no-cache openssh-client rsync
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | ssh-add -
    - mkdir -p ~/.ssh
    - ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts
  script:
    - rsync -avz --delete ./app/ deploy@$DEPLOY_HOST:/var/www/app/
```

## Як безпечно передати SSH-ключ у GitLab CI

Приватний ключ ніколи не зберігають у репозиторії. Його додають у розділі Settings → CI/CD → Variables як змінну `SSH_PRIVATE_KEY` з прапорцями Protected і Masked. Protected означає, що змінна доступна лише на захищених гілках, Masked — що значення не потрапить у відкритому вигляді до логу job.

- Ключ генерують окремо для CI, а не переюзають особистий ключ розробника
- На сервері для цього ключа створюють окремого користувача `deploy` з обмеженими правами
- У `authorized_keys` на сервері ключ прив'язують командою з обмеженням `command=`, якщо потрібен доступ лише до одного скрипту

## Стадії конвеєра: build, test, deploy

Стадія `build` збирає артефакт — наприклад, архів із залежностями Composer чи зібраний фронтенд. Стадія `test` прогонує лінтери і юніт-тести та зупиняє конвеєр при помилці, не даючи зламаному коду потрапити на сервер. Стадія `deploy` копіює вже перевірений артефакт на VDS.

| Стадія | Що робить | Що відбувається при помилці |
| --- | --- | --- |
| build | Збирає залежності та артефакт деплою | Конвеєр зупиняється, деплою не буде |
| test | Запускає лінтери, юніт- і інтеграційні тести | Job позначається failed, деплой блокується |
| deploy | Копіює артефакт на сервер, перезапускає служби | Видно лог job, реліз можна відкатити вручну |

## Деплой через rsync і симлінк на реліз

Надійна схема — деплой в окрему папку з датою і перемикання симлінка `current` лише після успішної викладки. Так сайт жодної секунди не дивиться на частково скопійовані файли.

```
ssh deploy@$DEPLOY_HOST "mkdir -p /var/www/releases/$CI_COMMIT_SHORT_SHA"
rsync -avz ./app/ deploy@$DEPLOY_HOST:/var/www/releases/$CI_COMMIT_SHORT_SHA/
ssh deploy@$DEPLOY_HOST "ln -sfn /var/www/releases/$CI_COMMIT_SHORT_SHA /var/www/current"
ssh deploy@$DEPLOY_HOST "systemctl reload php8.3-fpm"
```

## Відкат при невдалому деплої

Якщо реліз зламав прод, відкат — це перемикання симлінка `current` на попередню папку в `/var/www/releases/`, а не повторний деплой старого коміту. Такий відкат займає секунди і не потребує нового прогону конвеєра.

- Зберігати на сервері останні 5–10 релізів, а не лише поточний
- Тримати окремий job `rollback` у `.gitlab-ci.yml`, який запускається вручну кнопкою в GitLab
- Логувати кожен деплой із міткою часу і хешем коміту в окремий файл на сервері

## Поширені помилки в конфігурації конвеєра

Перша помилка — деплой без стадії тестів: конвеєр викладає на прод код, який навіть не перевірено лінтером. Друга — спільний SSH-ключ на всіх середовищах одразу, через що компрометація одного ключа дає доступ до всіх серверів. Третя — забутий `only` чи `rules` у job деплою, через що викладка запускається з будь-якої гілки, а не лише з `main`.

Якщо після деплою код треба ще й налаштувати на рівні ОС — пакети, cron, конфіги, — цей крок логічно винести в окремий [Ansible-плейбук](https://www.zevshost.net/uk/kb/ansible-ili-terraform/), який конвеєр запускає наступним job. Для порівняння з альтернативною платформою CI дивіться статтю про [деплой через GitHub Actions](https://www.zevshost.net/uk/kb/github-actions-deploy-vps/). Якщо сервером призначення виступає кластер із кількох машин, деплой зручніше націлити на [Docker Swarm](https://www.zevshost.net/uk/kb/docker-swarm-nastroika/) замість одиночного VDS.

## Підсумок: чекліст робочого конвеєра

Робочий деплой на VPS через GitLab CI будується з чотирьох обов'язкових елементів, без яких конвеєр рано чи пізно підведе у проді.

- Окремий SSH-ключ для CI з прапорцями Protected і Masked у змінних
- Стадія тестів перед стадією деплою, а не деплой напряму
- Релізи в окремих папках із симлінком current для миттєвого відкату
- Обмеження деплою правилом `only` чи `rules` на потрібну гілку

---

Джерело: https://www.zevshost.net/uk/kb/gitlab-ci-deploy-vps/
