HostiServer
2026-08-18 11:18
GitHub Actions vs GitLab CI vs Gitea Actions у 2026: що обрати для self-hosted CI/CD
📚 Серія «CI/CD з нуля», частина 6 з 6:
- Що таке CI/CD: від ручного deploy до автоматизованих pipelines
- Self-hosted CI/CD: концепція, архітектура і runner'и
- GitHub Actions self-hosted runner: встановлення та перший pipeline на VPS
- GitLab CI self-hosted runner: встановлення та перший pipeline на VPS
- Gitea Actions: Git-платформа і pipeline на одному сервері
- GitHub Actions vs GitLab CI vs Gitea Actions у 2026: що обрати для self-hosted CI/CD ← ви тут
GitHub Actions vs GitLab CI vs Gitea Actions у 2026: що обрати для self-hosted CI/CD
У трьох попередніх частинах ми виконали одну задачу тричі. Сценарій щоразу був той самий: push у main, збірка, тести, викладка на продакшн через SSH, перевірка, що застосунок піднявся. Змінювалася лише платформа.
Тепер ці три рішення варто покласти поруч. Не для того, щоб визначити переможця, бо його немає: усі три роблять роботу, і будь-яку з них можна довести до робочого стану. Питання інше — яке з них дасть менше зайвої роботи саме вашій команді, з її кодом, вимогами і кількістю часу на адміністрування.
1.1 Що саме порівнюємо
Умови для всіх трьох однакові: код у приватному репозиторії, збірка і деплой виконуються на вашому VPS, продакшн у закритій мережі, доступ по SSH з окремим користувачем без прав root.
Ключова річ, яку ця серія показала на практиці: self-hosted runner відповідає на питання, де виконується код, і не відповідає на питання, де він зберігається. Це два різні рішення, і плутанина між ними породжує більшість помилок при виборі.
2. Конфіг-файл і структура
2.1 Де живе конфігурація
| Платформа | Шлях | Кількість файлів |
|---|---|---|
| GitHub Actions | .github/workflows/*.yml |
Скільки завгодно |
| GitLab CI | .gitlab-ci.yml |
Один, решта через include |
| Gitea Actions | .gitea/workflows/*.yml |
Скільки завгодно |
Різниця не косметична. У GitHub і Gitea перевірки на pull request, нічне сканування і реліз за тегом лежать окремими файлами, які редагуються незалежно. У GitLab точка входу одна, а поділ робиться через include, і це дає інший ефект: конфігурацію легко централізувати в окремому репозиторії шаблонів і підключати десятками проєктів.
2.2 Ієрархія
GitHub Actions і Gitea Actions мають однакову будову: on → jobs → steps. Стадій як окремої сутності немає, порядок задається через needs.
GitLab CI будується інакше: stages → задачі → script. Стадії оголошуються явно і виконуються послідовно, а кроків усередині задачі немає — є список команд.
# GitHub / Gitea
jobs:
build:
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
# GitLab
stages: [build, test, deploy]
build:
stage: build
script:
- npm ci
- npm run build
Практичний наслідок видно на великому pipeline. Стадії GitLab читаються згори вниз як порядок виконання, і це зручно, коли конфіг переглядає людина ззовні: аудитор, новий інженер, замовник. Модель GitHub гнучкіша, але щоб побачити порядок, треба простежити ланцюжок needs між задачами.
2.3 Оточення задачі
У GitLab образ вказується на рівні задачі ключем image, і це базовий спосіб роботи: задача виконується у контейнері.
У GitHub і Gitea контейнер це опція. За замовчуванням кроки виконуються прямо на runner, а для запуску у контейнері є ключ container на рівні задачі. Потрібні версії мов частіше ставляться діями setup-node, setup-python і подібними.
# GitLab: контейнер за замовчуванням
test:
image: node:20
script: [npm test]
# GitHub / Gitea: версія через дію
jobs:
test:
steps:
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm test
2.4 Умовний запуск
Тут логіка різна, і при міграції це місце потребує уваги.
У GitHub і Gitea ключ if ставиться на задачу або на крок і обчислює вираз:
deploy:
if: github.ref == 'refs/heads/main'
У GitLab список rules перевіряється згори вниз, спрацьовує перше правило, що збіглося, і воно визначає не тільки запуск, а й поведінку задачі:
deploy:
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: false
- when: never
Різниця у можливостях: rules вирішує одразу три питання — чи потрапить задача у pipeline, чи буде вона ручною, чи блокує вона результат. У GitHub і Gitea те саме збирається з кількох механізмів: if, workflow_dispatch, environment з підтвердженням.
3. Secrets і змінні
3.1 Синтаксис
GitHub і Gitea звертаються до секретів однаково, через контекст:
run: ./deploy.sh
env:
KEY: ${{ secrets.SSH_PRIVATE_KEY }}
GitLab підставляє секрет як звичайну змінну оточення, без окремого простору імен:
script:
- ./deploy.sh
variables:
KEY: $SSH_PRIVATE_KEY
За цим стоїть різниця у моделі. У GitHub і Gitea секрет це окрема сутність зі своїм сховищем. У GitLab це змінна з прапорцями захисту, і саме прапорці визначають, наскільки вона захищена. Звідси головне правило четвертої частини: Masked без Protected не захищає ні від чого серйозного.
3.2 Рівні зберігання
| Рівень | GitHub Actions | GitLab CI | Gitea Actions |
|---|---|---|---|
| Репозиторій | Так | Так | Так |
| Організація або група | Так | Так, з успадкуванням підгрупами | Так |
| Уся інсталяція | Тільки Enterprise | Так, у self-managed | Так |
| Оточення (staging / production) | Так, з підтвердженням рецензента | Так, через scope | Немає |
Один рядок тут вирішує більше за решту. Оточень у Gitea немає, тому розділення доступів між staging і продакшном доводиться робити вручну: різними іменами секретів, окремими репозиторіями або окремими runner з різними мітками. Для команди з двох людей це дрібниця, для процесу, де деплой на продакшн вимагає підтвердження відповідального, це серйозне обмеження.
ℹ️ Про рівні у Gitea: у старіших інструкціях трапляється твердження, що секрети там бувають лише на рівні репозиторію. Це застаріло: секрети зберігаються на рівні користувача, організації або репозиторію, а при збігу імен пріоритет має нижчий рівень. Чого справді немає, так це оточень.
4. Runner і executor
Механіка виконання у всіх трьох однакова, і це головне, що варто винести з серії. Агент сам звертається до платформи, забирає задачу з черги, виконує і повертає логи. Платформа нікуди не «штовхає» задачі, тому агенту не потрібні відкриті вхідні порти і біла адреса.
| Параметр | GitHub Actions | GitLab CI | Gitea Actions |
|---|---|---|---|
| Агент | actions/runner | gitlab-runner | act_runner |
| Виконання на хості | За замовчуванням | Executor shell | Мітка з :host |
| Виконання у контейнері | Ключ container |
Executor docker | Мітка з docker:// |
| Kubernetes | Через Actions Runner Controller | Executor kubernetes | Немає |
| Паралельні задачі на агенті | Одна, потрібні кілька агентів | Кілька, ключ concurrent |
Кілька, ключ capacity |
| Оформлення сервісу | Скрипт svc.sh |
Пакет ставить сам | Unit пишеться вручну |
Рядок про паралельність має практичну вагу при плануванні сервера. Один агент GitHub бере одну задачу, тому для чотирьох паралельних збірок реєструються чотири агенти. GitLab і Gitea роблять те саме одним процесом і одним параметром у конфізі.
Рядок про Kubernetes важливий, якщо збірки вже живуть у кластері. У GitLab це вбудований executor, у GitHub це окремий контролер, у Gitea доводиться обходитися контейнерами на VPS.
5. Один deploy, три конфігурації
Задача однакова: викласти зібрані файли на сервер через rsync і перезапустити сервіс. Показані лише блоки деплою, збірка і тести у всіх трьох виглядають однаково.
5.1 GitHub Actions
deploy:
needs: build
runs-on: [self-hosted, linux, deploy]
environment: production
steps:
- uses: actions/download-artifact@v4
with: { name: dist, path: dist/ }
- name: Підготувати SSH
run: |
install -m 700 -d ~/.ssh
install -m 600 /dev/null ~/.ssh/deploy_key
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
- name: Викласти і перезапустити
run: |
rsync -az --delete -e "ssh -i ~/.ssh/deploy_key" \
dist/ deploy@${{ secrets.DEPLOY_HOST }}:/var/www/app/
ssh -i ~/.ssh/deploy_key deploy@${{ secrets.DEPLOY_HOST }} \
"sudo systemctl restart app.service"
- name: Прибрати ключ
if: always()
run: rm -f ~/.ssh/deploy_key
5.2 GitLab CI
deploy-production:
stage: deploy
tags: [self-hosted, shell]
environment:
name: production
url: https://app.example.com
dependencies: [build]
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: false
before_script:
- chmod 600 "$SSH_PRIVATE_KEY" # змінна типу File
after_script:
- shred -u "$SSH_PRIVATE_KEY" 2>/dev/null || rm -f "$SSH_PRIVATE_KEY"
script:
- |
rsync -az --delete \
-e "ssh -i $SSH_PRIVATE_KEY -o UserKnownHostsFile=$SSH_KNOWN_HOSTS" \
dist/ deploy@$DEPLOY_HOST:/var/www/app/
- |
ssh -i "$SSH_PRIVATE_KEY" -o UserKnownHostsFile="$SSH_KNOWN_HOSTS" \
deploy@$DEPLOY_HOST "sudo systemctl restart app.service"
5.3 Gitea Actions
deploy:
needs: build
runs-on: deploy # мітка host-режиму
steps:
- uses: actions/download-artifact@v3 # v4 потребує API GitHub
with: { name: dist, path: dist/ }
- name: Підготувати SSH
run: |
install -m 700 -d ~/.ssh
install -m 600 /dev/null ~/.ssh/deploy_key
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
- name: Викласти і перезапустити
run: |
rsync -az --delete -e "ssh -i ~/.ssh/deploy_key" \
dist/ deploy@${{ secrets.DEPLOY_HOST }}:/var/www/app/
ssh -i ~/.ssh/deploy_key deploy@${{ secrets.DEPLOY_HOST }} \
"sudo systemctl restart app.service"
- name: Прибрати ключ
if: always()
run: rm -f ~/.ssh/deploy_key
5.4 Що з цього видно
- GitHub і Gitea майже збігаються. Відмінності зводяться до мітки у
runs-on, версій дій і відсутностіenvironment. - GitLab бере на себе більше роботи з ключем. Змінна типу File створює тимчасовий файл сама, тому кроків з
echoнемає. - Ручний крок реалізується по-різному. У GitLab це
when: manualу правилі, у GitHub цеenvironmentз рецензентом, у Gitea окремий workflow зworkflow_dispatch. - Готові дії спокушають скрізь. Крок з
appleboy/ssh-actionкоротший за ручнийrsync, але у Gitea він тягнеться з GitHub, а на постійному runner будь-яка стороння дія бачить ваші секрети. Для одногоrsyncі одногоsshвигода не варта залежності. - Прибирання ключа обов'язкове всюди. Оточення на self-hosted runner постійне, і забутий ключ лишається доступним наступній задачі, з якої завгодно гілки.
6. Порівняльна таблиця
| Критерій | GitHub Actions | GitLab CI | Gitea Actions |
|---|---|---|---|
| Конфіг-файл | .github |
.gitlab-ci.yml |
.gitea |
| Структура | jobs → steps | stages → jobs → script | jobs → steps |
| Синтаксис секрету | ${{ |
$NAME |
${{ |
| Секрети групи або організації | Так | Так | Так |
| Оточення з підтвердженням | Так | Так | Ні |
| Kubernetes executor | Через ARC | Так | Ні |
| Екосистема готових кроків | Тисячі дій у Marketplace | CI Components і шаблони | Часткова сумісність з діями GitHub |
| Звіти тестів в інтерфейсі | Через сторонні дії | Вбудовано, видно у merge request | Обмежено |
| Історія деплоїв і відкат | Через Environments | Вбудовано, кнопка Rollback | Немає |
| Ресурси на сервер платформи | Не потрібні | Від 4 ГБ RAM для self-managed | Від 1 ГБ RAM |
| Де зберігається код | github.com | gitlab.com або ваш сервер | Тільки ваш сервер |
Про безкоштовні хвилини свідомо без цифр: тарифи змінюються частіше, ніж оновлюються статті. Загальна картина стабільна — GitHub дає безлімітні хвилини публічним репозиторіям і обмежену квоту приватним, GitLab дає квоту на безкоштовному тарифі, Gitea не рахує нічого, бо все виконується у вас. Актуальні цифри звіряйте на сторінках тарифів. Головне тут інше: хвилини на власному runner не рахуються у жодній з платформ, тому квота перестає бути аргументом, щойно ви поставили свій агент.
7. Кому що підходить
7.1 GitHub Actions
- Проєкт уже на GitHub. Найсильніший аргумент, який перекриває решту: переносити репозиторій заради CI майже ніколи не варто.
- Open source. Публічні репозиторії отримують хмарні збірки без обмеження хвилин, і власний агент їм не потрібен, ба більше — не рекомендований.
- Потрібні готові кроки. Marketplace закриває типові задачі одним рядком: збірка образів, публікація пакетів, звіти покриття.
- Команда без досвіду з CI. Найнижчий поріг входу: файл редагується прямо у браузері, помилки видно одразу.
Проти: на self-hosted агенті обережність з чужими діями стає обов'язковою, а частина того, що у GitLab вбудовано (звіти, історія деплоїв), збирається зі сторонніх кроків.
7.2 GitLab CI
- Команда вже на GitLab. Той самий аргумент, що і вище, дзеркально.
- Складні pipeline. Стадії, граф залежностей, дочірні pipeline і матриці дають більше контролю на конфігураціях з десятками задач.
- Багато проєктів з однаковими налаштуваннями. Змінні рівня групи і
includeз централізованого репозиторію шаблонів економлять помітно. - Потрібні аудит і прозорість. Явний порядок стадій читається людиною ззовні без розбору залежностей, а історія деплоїв показує, який коміт зараз на продакшні.
- Kubernetes прямо зараз. Вбудований executor без окремого контролера.
Проти: власна інсталяція GitLab це помітно важчий сервіс, ніж Gitea. Якщо потрібен саме закритий контур, а не складні pipeline, цей варіант дорожчий за ресурсами.
7.3 Gitea Actions
- Код не повинен покидати вашу інфраструктуру. Договірні зобов'язання, ізольована мережа, повний контроль над даними.
- Невелика команда і один сервер. Git-платформа і runner живуть на тому самому VPS, ресурсів потрібно менше, ніж GitLab.
- Досвід з GitHub Actions уже є. Синтаксис переноситься майже цілком, перевчатися не доводиться.
- Бюджет. Немає оплати за користувача, тільки вартість сервера.
Проти: немає оточень з підтвердженням, немає історії деплоїв, немає Kubernetes, а екосистема дій обмежена. Плюс усе адміністрування ваше: бекапи, оновлення, HTTPS, дзеркало дій для закритого контуру.
⚠️ Найчастіша помилка вибору: перенести репозиторій на іншу платформу заради можливостей CI. Витрати на міграцію (історія, issues, права доступу, звички команди, інтеграції) майже завжди перевищують виграш. Спершу подивіться, чи не закривається ваша задача власним runner на поточній платформі. У більшості випадків закривається.
8. Висновок
Серія починалася з питання, що таке CI/CD, і закінчується вибором з трьох робочих інструментів. Що варто забрати з неї цілком:
- Два різні питання. Self-hosted runner вирішує, де виконується код. Де він зберігається — окреме рішення, і плутанина між ними породжує більшість помилок при виборі платформи.
- Якщо код може лежати у хмарі — беріть ту платформу, на якій команда вже працює, і додайте власний агент. Це найдешевший шлях до контролю над ресурсами і доступом до закритої мережі.
- Якщо код має лишатися у вас — Gitea для невеликої команди, self-managed GitLab там, де потрібні складні pipeline і повний набір вбудованих можливостей.
- Механіка скрізь однакова. Агент опитує чергу, виконує задачу, повертає логи. Освоївши одну платформу, ви читаєте конфіги решти без словника.
- Постійне оточення вимагає дисципліни. Прибирання ключів, тайм-аути, обмеження на паралельність і обережність зі сторонніми діями. Це плата за швидкість і контроль.
Практичний план для того, хто починає з нуля: підніміть найпростіший pipeline з двох кроків на поточній платформі, доведіть його до зеленого стану, і лише потім додавайте власний runner. Деплой підключайте останнім, коли збірка і тести вже стабільні. Такий порядок дає працюючий результат за кілька днів замість кількох тижнів на налаштування всього одразу.
📚 Навігація по серії:
Ви читаєте частину 6 з 6 «GitHub Actions vs GitLab CI vs Gitea Actions у 2026: що обрати для self-hosted CI/CD».
Попередня: ← Частина 5. Gitea Actions: Git-платформа і pipeline на одному сервері
Серію завершено. Почати спочатку: Частина 1. Що таке CI/CD: від ручного deploy до автоматизованих pipelines
🚀 Сервер під ваш CI/CD, яку б платформу ви не обрали
GitHub Actions, GitLab CI чи Gitea — агент в усіх трьох випадках упирається у CPU, диск і мережу. Hostiserver дає передбачувані ресурси і приватну мережу, з якої можна безпечно ходити на продакшн.
🖥️ Виділені Сервери
- Від $90/міс, повний контроль над залізом для важких збірок і паралельних задач
- NVMe-накопичувачі: швидкий кеш залежностей і Docker-шарів між прогонами
- Без обмеження хвилин збірки: платите за сервер, а не за квоту платформи
- Приватна мережа між runner-ом і продакшн-серверами
- 24/7 підтримка: інженери допоможуть з налаштуванням runner-ів і деплою
💻 Cloud (VPS) Хостинг
- Від $19.95/міс, KVM-ізоляція, виділені vCPU і RAM
- Ідеально для першого runner-а: підняти, підключити до репозиторію, перевірити на реальному проєкті
- Легко масштабувати: окремі машини під збірки і під деплой, коли одна перестане справлятися
💬 Не впевнені, який варіант вам необхідний?
💬 Напишіть нам і ми зі всім допоможемо!
Часті питання
- Чи варто міняти платформу заради можливостей CI?
Майже ніколи. Міграція забирає історію комітів, issues, права доступу, налаштовані інтеграції і звички команди, а виграш зазвичай зводиться до кількох зручностей. Спершу перевірте, чи не закривається ваша задача власним runner на поточній платформі: ресурси, доступ до закритої мережі і зняття ліміту хвилин доступні у всіх трьох. Реальні підстави для переїзду це вимога зберігати код у себе і відмова від платформи через ліцензійні чи вартісні причини.
- Наскільки складно перенести pipeline між цими платформами?
Між GitHub Actions і Gitea Actions це майже копіювання: правляться
runs-on, версії дій, які звертаються до API GitHub, і набір інструментів у образі. Між GitHub і GitLab роботи більше: задачі стають задачами,runs-onперетворюється наtags, кроки зrunлягають уscript, а кожен крок зusesзамінюється командами або образом контейнера. Логіка запуску теж переписується:ifперетворюється наrules. Для типового pipeline це кілька годин.
- Що робити, якщо потрібне підтвердження перед деплоєм на продакшн, а платформа це Gitea?
Оточень з рецензентами у Gitea немає, тому ручний крок будується інакше. Найпростіший варіант це окремий workflow з тригером
workflow_dispatch, який запускається кнопкою і бере готовий артефакт. Другий варіант це деплой за тегом: викладка стартує лише тоді, коли хтось створив тег релізу, а право на це обмежується правилами захисту гілок і тегів. Третій це окремий runner з міткою для продакшну, доступний лише певному репозиторію.
- Чи можна тримати код у кількох місцях одночасно?
Так, і це робоча схема для закритого контуру з відкритою частиною. Gitea вміє дзеркалити репозиторії в обидва боки: тягнути з зовнішнього джерела або відправляти туди зміни. Типове застосування це основна робота у власній Gitea і дзеркало на GitHub для публічної частини. Врахуйте, що pipeline треба тримати лише на одному боці, інакше одна зміна запускатиме дві збірки, а деплой може статися двічі.
- Скільки runner потрібно на команду?
Орієнтуйтеся не на кількість людей, а на кількість одночасних збірок у пікові години. Для команди з п'яти інженерів зазвичай вистачає двох паралельних задач: одна для перевірок на pull request, друга для гілки за замовчуванням. У GitLab і Gitea це один агент з відповідним параметром у конфізі, у GitHub це два окремі агенти. Деплой краще винести на окремий runner з іншою міткою: він потребує доступу до ключів, і змішувати його зі збірками не варто.
- Чи безпечно використовувати сторонні дії на власному runner?
Обережніше, ніж у хмарі. Стороння дія це чужий код, який виконується у вашому оточенні і бачить секрети задачі, а сервер, на відміну від одноразової хмарної машини, живе далі. Робочі правила: фіксуйте дії за повним хешем коміта замість рухомого тега, обмежуйте перелік дозволених дій у налаштуваннях, а для простих кроків на кшталт
rsyncчерез SSH пишіть команди самі. Для Gitea додається ще одне: за замовчуванням дії тягнуться з github.com, що для закритого контуру потребує дзеркала.
- Що обрати, якщо вимоги ще невідомі?
Лишайтеся на платформі, де вже лежить код, і почніть з найпростішого pipeline: збірка і тести на кожен push. Цього достатньо, щоб отримати основну користь від CI, і достатньо, щоб зрозуміти власні вимоги до решти. Власний runner додається наступним кроком, коли стане зрозуміло, чого не вистачає: ресурсів, доступу до мережі чи хвилин. Питання про зберігання коду вирішується окремо і зазвичай не інженерами, а умовами договору.