Зазвичай усе починається так: код лежить на GitHub чи в хмарному GitLab, збірку хтось запускає з ноутбука, а на прод викладають через git pull по SSH. Поки розробник один, це навіть працює. Потім приходить другий, третій, з’являється тестовий сервер, і вже ніхто точно не скаже, яка версія зараз у проді й хто її туди виклав.

Друга половина болю — залежність від чужого сервісу. Ліміти на хвилини CI, ціна за користувача, яка росте разом із командою, образи контейнерів у чужому реєстрі. А для частини клієнтів ще й вимога тримати код і артефакти на своїй інфраструктурі.

Ми розгортаємо власний GitLab, налаштовуємо пайплайн і розводимо середовища так, щоб у прод потрапляло тільки те, що пройшло збірку, тести і чий реліз хтось свідомо підтвердив.

Що ми робимо

  • GitLab CE на вашому сервері. Окрема віртуальна машина на Proxmox або виділений сервер в OVH чи Hetzner. Ставимо пакет Omnibus, HTTPS, пошту, вхід через SSH-ключі, 2FA для всіх.
  • Container Registry. Вмикаємо вбудований реєстр образів. Раннер збирає образ і пушить його туди. Сервери тягнуть образ звідти за deploy-токеном лише з правом читання реєстру.
  • Раннери. Docker executor для збірок і тестів: кожна задача в чистому контейнері. Окремий раннер під прод, який бере тільки захищені гілки й теги. Shell executor — там, де контейнер заважає, наприклад для деплою через Ansible.
  • Пайплайн build → test → deploy. Збірка образу, тести, потім деплой: на сервер заходить окремий користувач deploy, тягне потрібний тег образу і перезапускає docker compose.
  • Середовища dev, stage, prod. У кожного свої змінні, свій сервер і своя історія деплоїв у GitLab. Dev оновлюється з кожним комітом у робочу гілку, stage — з main, prod — з тегу релізу.
  • Review-середовища (за бажанням). Для кожної гілки піднімається тимчасова копія застосунку на окремому піддомені й зникає після мерджу.

Як прод захищений від випадкових рук

Головне правило: у прод не можна викласти «з гілки, щоб глянути». Ось як це виглядає в .gitlab-ci.yml:

deploy_prod:
  stage: deploy
  tags: [prod]                 # тільки раннер прод-контуру
  environment:
    name: production
  resource_group: production   # два деплої одночасно не підуть
  rules:
    - if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
      when: manual             # кнопку тисне людина
  script:
    - ssh deploy@$PROD_HOST "cd /opt/app && IMAGE_TAG=$CI_COMMIT_TAG docker compose pull && docker compose up -d"

До цього додаємо захищені гілки й теги: створити тег v* можуть тільки мейнтейнери. Середовище production теж захищене, тож кнопку деплою бачить лише визначене коло людей. Прод-раннер зареєстрований як protected і чужих задач не бере.

Секрети живуть у змінних CI/CD, а не в репозиторії. Ставимо їм прапорці masked (не вивалюються в лог) і protected (доступні тільки на захищених гілках). Змінні прив’язуємо до середовища: DATABASE_URL для stage і для prod — різні, і задача з dev-гілки прод-пароль просто не побачить.

Бекапи, оновлення й де тут граблі

Бекапи. gitlab-backup create за розкладом, архів одразу копіюємо за межі сервера. Тут перша пастка: у цей архів не потрапляють gitlab.rb і gitlab-secrets.json. Без другого файлу після відновлення не розшифруються змінні CI й токени. Тому зберігаємо їх окремо і хоча б раз проганяємо відновлення на чистій машині. Відновити бекап можна тільки на ту саму версію GitLab — це теж треба пам’ятати.

Оновлення. GitLab виходить щомісяця, і оновлювати його треба регулярно: там постійно закривають вразливості. Перескочити з дуже старої версії на свіжу одним кроком не вийде. Є обов’язкові проміжні версії (upgrade path), і після кожної треба дочекатися фонових міграцій. Перед оновленням робимо снапшот віртуальної машини. Після gitlab-ctl reconfigure перевіряємо, що піднялися всі сервіси: буває, що Sidekiq не стартує з першого разу і його доводиться перезапускати вручну.

Моніторинг. GitLab сам віддає метрики для Prometheus. Ми стежимо за пам’яттю, диском (реєстр і артефакти ростуть швидко, тому налаштовуємо політику очищення старих образів), чергами Sidekiq і живістю раннерів. Алерти приходять у месенджер або на пошту.

Чесно про ресурси. GitLab важкий. На 4 ГБ пам’яті він стартує тільки з урізаними налаштуваннями й помітно гальмує. Для комфортної роботи невеликої команди закладайте від 8 ГБ і 4 ядра, плюс окремі ресурси під раннери — збірки краще не ганяти на тій самій машині.

Коли GitLab не потрібен. Якщо у вас двоє розробників, один проєкт і достатньо простого CI — подивіться на Gitea або Forgejo. Вони вміщуються в кілька сотень мегабайт пам’яті, мають свій реєстр і CI, сумісний з GitHub Actions. Ми так і скажемо, якщо побачимо, що GitLab для вас завеликий.

Що ви отримуєте

  • GitLab на вашому сервері з адмін-доступом для вас, а не для нас.
  • Container Registry з політикою очищення старих образів.
  • Раннери: загальні для збірок і окремий захищений під прод.
  • Готовий .gitlab-ci.yml з етапами build → test → deploy і шаблон для нових проєктів.
  • Середовища dev, stage, prod з окремими змінними, захищеними гілками й ручним підтвердженням релізу.
  • Бекапи GitLab разом із файлами конфігурації і перевірене відновлення.
  • Моніторинг і алерти на пам’ять, диск, черги й раннери.
  • Документацію: як додати проєкт, як випустити реліз, як відкотитися, як оновити GitLab.

Якщо у вас зараз деплой «руками по SSH» або рахунок за хмарний CI росте швидше за команду — напишіть нам. Розкажіть, скільки у вас людей і проєктів, і ми запропонуємо, що розгорнути: GitLab чи щось легше.