Exabit Logo

GitLab CI/CD без лишней сложности: как настроить деплой и тесты так, чтобы релизы перестали пугать команду

03 сентября 2026 · 4 мин чтения ·
GitLab CI/CD без лишней сложности: как настроить деплой и тесты так, чтобы релизы перестали пугать команду
  • «Кто сегодня деплоит?»
  • «Подождите, это умеет только один разработчик, он сейчас не на связи».
  • «Тогда релиз переносим?»

Я слышал это много раз - и в интернет-магазине с оборотом 18 млн ₽ в месяц, и в B2B SaaS с командой 7 человек. Пока релизы редкие, ручной процесс еще терпят. Когда выкладка идет хотя бы 2-4 раза в месяц, цена ошибки уже выше, чем стоимость нормального pipeline.

Проблема ручного деплоя не во времени, а в зависимости от одного человека

Обычно считают так: релиз занимает 30-40 минут, значит потери небольшие. Но дорогая часть в другом. Доступы, порядок команд, миграции, перезапуск очередей, нужный env-файл - все это держится в голове одного разработчика.

У нас был проект для логистики на Laravel 11, PostgreSQL 16 и Redis 7. Релиз шел через SSH и инструкцию в Notion на 14 шагов. Формально процесс был описан, но раз в пару месяцев кто-то пропускал миграцию или забывал перезапуск воркеров. В итоге простой выходил на 20-40 минут, а потом еще полдня уходило на разбор.

Самая дорогая часть ручного деплоя - непредсказуемость.

Если по-простому, CI/CD для малого бизнеса - это способ сделать выкладку повторяемой. Чтобы релиз зависел от файла в репозитории, а не от памяти одного человека.

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

Короче, я бы не рисовал сложные схемы, если у вас обычный веб-продукт, один репозиторий и пара окружений. В большинстве случаев хватает цепочки test -> build -> publish -> deploy. Feature-ветки гоняют быстрые проверки, main уезжает на staging, теги идут на production.

Важно, чтобы логика жила в .gitlab-ci.yml, а не в локальных bash-скриптах где-то «у Пети на ноутбуке». Тогда новый разработчик видит весь процесс сразу, а не выпрашивает инструкцию в личке.

stages:
  - test
  - build
  - publish
  - deploy

test:
  stage: test
  image: node:20
  script:
    - npm ci
    - npm run lint
    - npm test

build:
  stage: build
  image: docker:26
  services:
    - docker:26-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
  only:
    - main
    - tags

publish:
  stage: publish
  image: docker:26
  services:
    - docker:26-dind
  script:
    - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  only:
    - main
    - tags

deploy:
  stage: deploy
  image: alpine:3.20
  script:
    - apk add --no-cache openssh
    - ssh deploy@server "docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA && docker compose up -d"
  only:
    - tags

Если pipeline стабильно идет дольше 12-15 минут, команда почти всегда начинает искать обходные пути. Я это видел не раз.

Нормальный деплой начинается с готового образа, а не с git pull на VPS

Самая частая история у малого бизнеса - git pull && docker compose up --build -d прямо на сервере. В первый месяц удобно. Потом один релиз собирается нормально, другой нет: сеть дернулась, пакет не скачался, место на диске закончилось, кеш поехал.

На сервер должен приезжать уже собранный образ. Тогда staging и production поднимают один и тот же артефакт, а не каждый раз что-то «примерно одинаковое».

Мы это чинили на проекте в онлайн-обучении. Сборка шла прямо на VPS, потом обновился базовый PHP-образ, и часть контейнеров стала стартовать дольше. После релиза клиент ловил случайные 502. Перенесли сборку в GitLab Runner, начали публиковать теги вида app:v1.8.4 в GitLab Container Registry - проблема ушла в ту же неделю.

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

Что сравниваем GitLab Container Registry Docker Hub
Авторизация встроена отдельно
Приватный проект обычно проще нормально
Публичные образы реже нужен чаще выбирают
Для проекта на GitLab хватает в большинстве случаев не всегда нужен

Автодеплой чаще падает из-за сервера и порядка шагов

GitLab тут обычно ни при чем. Сбои сидят в окружении: разные переменные на staging и production, общий root-доступ, кривой rollback, миграции в неправильном порядке.

Самый неприятный случай, который я регулярно встречаю: новая версия приложения стартует раньше миграций. Если схема БД уже не совпадает, сервис падает сразу после релиза. Команда потом говорит, что «сломался pipeline», хотя проблема в сценарии деплоя.

Я бы проверил вот это:

  • секреты лежат в GitLab variables, а не в репозитории;
  • staging максимально похож на production;
  • образы тегируются по commit SHA или release tag, а не только latest;
  • rollback занимает меньше 10 минут;
  • после выкладки есть проверка /health.

На одном e-commerce проекте мы после таких правок сократили среднее время реакции на неудачный релиз с 27 минут до 6 минут. Для бизнеса это уже не «удобство разработки», а просто меньше потерянных заказов.

Проверок в pipeline должно быть немного, но по делу

Тут команды часто перегибают. Запихивают в CI вообще все, и через месяц разработчики начинают выключать проверки руками. Для обычного продукта хватает базового набора:

  • lint и unit-тесты - до 2-3 минут;
  • сборка образа - еще 3-8 минут;
  • после деплоя - smoke-check или /health;
  • по расписанию - сканирование уязвимостей образа.

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

Если у вас релиз до сих пор держится на одном человеке, не начинайте с найма DevOps. Лучше найдите один шаг, который никто, кроме него, не может повторить без созвона. Вот с него и стоит начинать. Именно такие места обычно и срывают релиз в самый неудобный день.

Нужна помощь с реализацией?

Расскажите о задаче - предложим решение и дадим оценку сроков.