- «Кто сегодня деплоит?»
- «Подождите, это умеет только один разработчик, он сейчас не на связи».
- «Тогда релиз переносим?»
Я слышал это много раз - и в интернет-магазине с оборотом 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. Лучше найдите один шаг, который никто, кроме него, не может повторить без созвона. Вот с него и стоит начинать. Именно такие места обычно и срывают релиз в самый неудобный день.