GitLab CI vs GitHub Actions: что выбрать для автоматизации в 2026
CI/CD в 2026: почему выбор платформы всё ещё важен
Сколько раз слышал: "они же одинаковые, просто YAML по-разному". Не одинаковые. GitLab CI - часть платформы, где живёт весь рабочий процесс. GitHub Actions вырос из экосистемы с упором на Marketplace. Для VPS и dedicated-серверов эта разница ощущается конкретно: в том, как настраиваются раннеры, что происходит с секретами и насколько больно будет мигрировать через год.
Здесь только практика - конфиги, которые реально работают, и ситуации, где один инструмент выигрывает у другого.
GitLab CI: как устроена система
GitLab CI не отдельный сервис - он встроен в платформу. Конфигурация живёт в .gitlab-ci.yml в корне репозитория. Пайплайн - это набор стадий (stages), каждая содержит джобы (jobs), их выполняют раннеры.
Архитектура раннеров
Здесь и есть суть - то, чем GitLab CI отличается на практике. Два типа раннеров:
- Shared runners - инфраструктура GitLab.com. Бесплатный план даёт 400 минут в месяц, и это мало - средний Node.js проект с тестами сжирает 150-200 минут за неделю.
- Self-hosted runners - агент на вашем сервере. Никаких лимитов, данные не уходят наружу.
Установка self-hosted раннера занимает минут десять, не больше: скачиваете бинарник gitlab-runner, регистрируете токеном из настроек репозитория, выбираете executor - shell, docker или kubernetes. Раннер работает по polling-протоколу, то есть сам опрашивает GitLab. Входящих соединений не нужно, только исходящий HTTPS - это важно, когда VPS за NAT или файрволом.
Синтаксис и возможности
Из коробки GitLab CI умеет needs для DAG-зависимостей между джобами, include для внешних конфигов, extends для наследования, rules для условной логики. Всё нативно - без плагинов. Встроенный container registry, пакетный реестр, Kubernetes-интеграция идут в комплекте с платформой.
GitHub Actions: как устроена система
Конфиги лежат в .github/workflows/*.yml. Концепции простые: workflow - рабочий процесс, job - набор шагов, step - отдельное действие. Шаги либо берут готовое действие из Marketplace, либо выполняют произвольные команды через run:. Порог входа ниже, чем у GitLab - если раньше не работал с CI/CD, GitHub Actions освоишь быстрее.
Marketplace как главное преимущество
В 2026 году в GitHub Marketplace больше 20 000 готовых actions. Деплой на AWS, Terraform, уведомления в Slack, сканирование уязвимостей - для большинства стандартных задач что-то уже есть. Обратная сторона: ты зависишь от сторонних авторов. Action популярного репо могут перестать поддерживать, и придётся искать замену или форкать.
Раннеры GitHub Actions
Hosted runners - ubuntu-latest, windows-latest, macos-latest. Для публичных репозиториев минуты не ограничены. Для приватных на бесплатном тарифе - 2000 минут в месяц, что уже лучше, чем у GitLab. Self-hosted раннеры есть, но они менее гибко настраиваются - GitLab в этом плане даёт больше контроля над executor-ами.
Прямое сравнение
| Параметр | GitLab CI | GitHub Actions |
|---|---|---|
| Бесплатные минуты (приватные репо) | 400 мин/мес (GitLab.com Free) | 2000 мин/мес (GitHub Free) |
| Self-hosted runners | Отличная поддержка, gitlab-runner daemon, множество executor-ов | Поддерживаются, меньше опций конфигурации |
| Self-hosted платформа целиком | Да, GitLab CE/EE - полный self-host | Только через GitHub Enterprise Server |
| Управление секретами | CI/CD Variables, маскировка, защищённые переменные по веткам | Secrets на уровне репо/org, environments с защитой |
| Container Registry | Встроен в платформу | GitHub Container Registry (ghcr.io) |
| Синтаксис пайплайна | stages + jobs, DAG через needs, YAML anchors | jobs + steps, матричные сборки, reusable workflows |
| Маркетплейс/плагины | Нет маркетплейса, всё через встроенные фичи | 20 000+ actions в Marketplace |
| Интеграция с issue/MR/PR | Нативная, включая Auto DevOps | Нативная через GitHub API |
| Артефакты и кэш | Встроены, хранятся на GitLab | actions/cache, actions/upload-artifact |
| Kubernetes-интеграция | Встроенная, GitLab Agent for Kubernetes | Через сторонние actions или kubectl |
Практические примеры: деплой на VPS по SSH
Задача стандартная: пуш в main - сборка - тесты - деплой на VPS через SSH. Вот оба варианта в реальных конфигах.
GitLab CI - деплой на VPS
Node.js-приложение. Приватный SSH-ключ хранится в переменной SSH_PRIVATE_KEY, IP сервера в DEPLOY_HOST, пользователь в DEPLOY_USER.
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
NODE_ENV: production
test:
stage: test
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
script:
- npm ci
- npm run test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
build:
stage: build
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache openssh-client rsync
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts
script:
- rsync -az --delete dist/ ${DEPLOY_USER}@${DEPLOY_HOST}:/var/www/app/
- ssh ${DEPLOY_USER}@${DEPLOY_HOST} "
cd /var/www/app &&
pm2 restart app --update-env ||
pm2 start ecosystem.config.js"
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
needs:
- job: build
artifacts: true
needs запускает деплой сразу после сборки, не ждёт остальных джобов на той же стадии. rules - замена старому only/except, логика гибче. Артефакт dist/ передаётся между джобами автоматически, прописывать ничего лишнего не нужно.
GitHub Actions - тот же деплой
Секреты - Settings, раздел Secrets and variables, вкладка Actions.
# .github/workflows/deploy.yml
name: Test, Build and Deploy
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
build:
runs-on: ubuntu-latest
needs: test
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install and build
run: |
npm ci
npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 1
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://example.com
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts
- name: Deploy via rsync
run: |
rsync -az --delete dist/ \
${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/var/www/app/
- name: Restart application
run: |
ssh ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} "
cd /var/www/app &&
pm2 restart app --update-env ||
pm2 start ecosystem.config.js"
Синтаксис похож, но есть принципиальное отличие: GitHub Actions требует явно загружать и скачивать артефакты через отдельные шаги. Секреты передаются через ${{ secrets.NAME }}. SSH-агент из коробки не настраивается - либо пишете руками, либо берёте сторонний action типа webfactory/ssh-agent. Именно это ломает ощущение "просто настроить" в первый раз.
Self-hosted runner в GitLab: деплой без исходящего SSH
Если GitLab Runner стоит прямо на целевом сервере или в той же сети - SSH вообще не нужен. Деплой упрощается до минимума.
# .gitlab-ci.yml (runner запущен на целевом сервере)
deploy:
stage: deploy
tags:
- production-vps
script:
- cp -r dist/* /var/www/app/
- systemctl restart myapp
rules:
- if: $CI_COMMIT_BRANCH == "main"
needs:
- job: build
artifacts: true
Тег production-vps отправляет джоб на конкретный раннер. По-честному, это одно из главных архитектурных преимуществ GitLab CI, когда работаешь с собственной инфраструктурой.
Когда выбирать GitLab CI
Главная причина - self-hosted инфраструктура. Если код не должен уходить на серверы GitLab.com, GitLab CE разворачивается на вашем VPS и закрывает весь стек: git-хостинг, CI/CD, container registry, issue tracker. GitHub аналогичного без Enterprise лицензии не даёт, это не мнение - просто факт из прайса.
Ещё один повод - интенсивные пайплайны на приватных репо. 400 минут в месяц от GitLab.com заканчиваются примерно к середине второй недели при активной разработке. Self-hosted раннер решает это раз и навсегда.
Сложные пайплайны с needs, динамические child pipelines, встроенный registry - всё это в GitLab CI реализовано нативно, без плагинов и workaround-ов. Если команда уже живёт внутри GitLab-экосистемы - смена CI просто ничего не даст.
Когда выбирать GitHub Actions
Open source - без вопросов. Неограниченные минуты на публичных репо плюс огромное сообщество, которое уже написало actions на все случаи жизни. Деплой на AWS, сканирование зависимостей, публикация npm-пакета - находишь нужный action, вставляешь в workflow, готово.
Для небольших команд с проектами на GitHub и несложными пайплайнами - GitHub Actions явно проще в старте. Не нужно поднимать отдельный сервер для раннера, не нужно разбираться в executor-ах. Написал workflow за вечер, и он работает.
Матричные сборки читаются лучше, чем в GitLab - это субъективно, но факт. Тестировать на трёх версиях Node и двух ОС в GitHub Actions настраивается декларативно и понятно.
Безопасность деплоя на VPS
Одно правило, которое одинаково работает на обеих платформах: отдельный SSH-ключ для деплоя с правами только на нужную директорию и перезапуск конкретного сервиса через sudoers. Не root-доступ, не полный sudo - только то, что нужно деплой-скрипту.
В GitLab CI деплой-переменные помечайте как Protected - тогда они видны только в защищённых ветках. В GitHub Actions для production используйте environments с required reviewers. Self-hosted раннер под root не запускайте - Docker executor с изоляцией надёжнее.
Deploy-ключи нужно ротировать. Обе платформы об этом не напоминают вообще никак.
Практическая рекомендация
Если у вас собственные серверы и приватный код - GitLab CI с self-hosted раннером. GitLab CE на VPS от 4 GB RAM, и всё необходимое под рукой без зависимости от чужих сервисов. Миграция с GitLab.com на self-hosted GitLab - это одна команда для экспорта проекта.
Если проект на GitHub и CI/CD не сложнее сборки и деплоя - GitHub Actions. Для open source особенно: 2000 минут в месяц на приватных репо и безлимит на публичных при нулевой настройке инфраструктуры.
Мигрировать между платформами неприятно: YAML несовместим, секреты переносить вручную, раннеры перенастраивать. Поэтому выбор лучше делать под долгосрочный план, а не под то, что проще поднять сегодня за час.