Community
2
HostiServer
2026-08-13 09:47

GitHub Actions self-hosted runner: встановлення та перший pipeline на VPS

⏱️ Час читання: ~12 хвилин | 📅 Оновлено: серпень 2026

GitHub Actions self-hosted runner: встановлення та перший pipeline на VPS

У другій частині ми розібрали, як влаштований self-hosted runner: агент забирає завдання з черги платформи, виконує його на вашому сервері і повертає логи та статус. Там же ми дивилися, чому цей агент варто винести на власне залізо і як ізолювати виконання коду. Тепер та сама архітектура перетворюється на конкретні команди: беремо VPS, підключаємо його до репозиторію на GitHub і доводимо ланцюжок до робочого деплою на продакшн.

Ця частина практична від початку до кінця. Спершу коротко про будову GitHub Actions, щоб YAML далі читався осмислено, а не копіювався навмання. Далі встановлення агента, оформлення його як systemd-сервісу, перший workflow, робота з секретами і повний pipeline, який після push у main збирає проєкт і викладає його на сервер через SSH.

1.1 Що вже маємо і що робимо далі

Мінімум, з якого стартуємо:

  • репозиторій на GitHub, приватний або внутрішній для організації;
  • VPS з Ubuntu 22.04 або 24.04, доступ по SSH, окремий користувач без прав root;
  • права адміністратора репозиторію: без них сторінка з налаштуванням runner-ів недоступна.

Агенту не потрібна біла адреса і відкриті вхідні порти. Він сам встановлює з'єднання з GitHub по 443 порту і тримає його, чекаючи на завдання. Це важлива властивість: runner можна поставити всередині закритої мережі, куди ззовні немає доступу взагалі, і він однаково працюватиме.

1.2 GitHub Actions: з чого складається екосистема

GitHub Actions це вбудована в GitHub система автоматизації, запущена у 2019 році. Конфігурація живе у самому репозиторії, у каталозі .github/workflows/: кожен YAML-файл там це окремий workflow, який GitHub підхоплює автоматично, без реєстрації в інтерфейсі.

Друга частина екосистеми це Marketplace готових дій. Дія (action) це перевикористовуваний крок, оформлений як окремий репозиторій: actions/checkout клонує код, actions/setup-node ставить потрібну версію Node.js, docker/build-push-action збирає і публікує образ. Замість десятка рядків bash у конфізі з'являється один рядок uses.

Зручність має зворотний бік. Кожна стороння дія це чужий код, який виконується у вашому оточенні і бачить ваші секрети. На self-hosted runner-і ціна помилки вища, ніж на одноразовій хмарній машині: у вас цей сервер живе далі. Робоче правило це фіксувати дії за повним хешем коміта замість тега, принаймні для всього, що не належить організаціям actions і github:

# замість рухомого тега
- uses: some-org/some-action@v3

# фіксація на конкретний коміт
- uses: some-org/some-action@8f4b7e2c9a1d3f5b6c8e0a2d4f6b8c0e2a4d6f8b # v3.1.0

ℹ️ Термінологія: у GitHub Actions немає стадій як окремої сутності, які є у GitLab CI. Порядок виконання задається залежностями через ключ needs. Якщо ви прийшли з GitLab, це головна відмінність, до якої треба звикнути.

2. Архітектура GitHub Actions

Перед встановленням агента корисно розібрати три рівні, на яких описується робота. Далі весь YAML у статті спирається саме на них.

2.1 Workflow → Job → Step

Workflow це один YAML-файл у .github/workflows/. Він відповідає на питання «коли запускатися» і «що робити». Файлів може бути скільки завгодно: окремо перевірки на pull request, окремо нічне сканування залежностей, окремо реліз за тегом.

Job це задача всередині workflow. Кожна задача отримує власний runner і власне чисте оточення. Задачі за замовчуванням виконуються паралельно, а послідовність задається через needs. Важливий наслідок: дві задачі не бачать файлів одна одної, обмін іде через артефакти.

Step це окремий крок усередині задачі. Кроки виконуються послідовно в одному оточенні, тому файли між ними передаються звичайним чином. Крок буває двох типів: run для команд оболонки і uses для готової дії.

workflow (файл ci.yml)
├── job: build → runner №1, власне оточення
│ ├── step: checkout
│ ├── step: npm ci
│ └── step: npm run build
└── job: deploy → runner №2, чисте оточення
needs: build
├── step: download-artifact
└── step: ssh deploy

2.2 Тригери: коли запускається workflow

Тригери описуються у секції on. Чотири основні:

  • push у певну гілку або за тегом. Базовий тригер для CI і для релізів.
  • pull_request. Перевіряє результат злиття ще до того, як воно сталося. Саме на цей тригер вішають блокування злиття, поки перевірки не пройдуть.
  • schedule. Запуск за розкладом у синтаксисі cron, у часовому поясі UTC. Підходить для нічних прогонів і щоденного сканування залежностей.
  • workflow_dispatch. Запуск кнопкою в інтерфейсі або викликом через API, з опційними параметрами. Це той самий ручний крок, який відрізняє Continuous Delivery від Continuous Deployment.
on:
push:
branches: [main]
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches: [main]
schedule:
- cron: '0 3 * * *' # щодня о 03:00 UTC
workflow_dispatch:
inputs:
environment:
description: 'Куди викладати'
type: choice
options: [staging, production]
default: staging

Про розклад є нюанс, який економить години розслідувань: schedule працює лише для workflow у гілці за замовчуванням, а запуск може затриматися на кілька хвилин або зсунутися під пікове навантаження. Для точного часу потрібен зовнішній планувальник, який смикає workflow_dispatch через API.

2.3 GitHub-hosted і self-hosted runner: різниця на практиці

GitHub-hosted runner це віртуальна машина, яку GitHub створює під кожну задачу і знищує після неї. Self-hosted runner це ваш сервер з встановленим агентом, який живе постійно. Різниця виходить далеко за межі того, хто платить за залізо.

Параметр GitHub-hosted Self-hosted
Життя оточення Чиста ВМ під кожну задачу Постійне, стан накопичується
Ресурси Фіксовані за тарифом Ваші, аж до GPU і сотень ГБ RAM
Оплата За хвилини збірки За сервер, хвилини не рахуються
Передвстановлений софт Великий образ з мовами і утилітами Тільки те, що поставили ви
Доступ до закритої мережі Немає Є, runner стоїть усередині
Черга у пікові години Можлива Своя черга, передбачуваний час
Адміністрування Немає Оновлення, диск, ізоляція, моніторинг

Порівняння GitHub-hosted і self-hosted runner: тимчасове чисте оточення проти постійного сервера під вашим контролем

Рядок про постійне оточення це і головна перевага, і головна пастка. Перевага у швидкості: між збірками лишається кеш пакетів, шари Docker, склонований репозиторій, тому друга збірка часто вдвічі швидша за першу. Пастка у тому, що там лишається і все інше: глобально встановлені пакети, тимчасові файли, залишені змінні оточення, приватний ключ, який попередня задача записала у домашній каталог і забула прибрати.

⚠️ Публічні репозиторії: GitHub прямо не рекомендує підключати self-hosted runner до публічного репозиторію. Будь-хто може відкрити pull request, а разом з ним і запропонувати зміни у workflow або у коді, який цей workflow виконує. На одноразовій хмарній машині це закінчується разом із задачею. На вашому сервері чужий код отримує доступ до всього, що на ньому лежить, і до мережі, у якій він стоїть. Для open source лишайте GitHub-hosted runner-и.

3. Встановлення self-hosted runner на VPS

Далі йде послідовність, після якої агент з'явиться у списку runner-ів зі статусом Idle і переживатиме перезавантаження сервера.

3.1 Реєстрація в GitHub і отримання токена

Runner підключається на одному з трьох рівнів:

  • Репозиторій. Settings → Actions → Runners → New self-hosted runner. Найпростіший варіант для першого агента.
  • Організація. Той самий шлях у налаштуваннях організації. Один агент обслуговує кілька репозиторіїв, доступ розмежовується групами runner-ів.
  • Enterprise. Рівень для великих інсталяцій, логіка та сама.

Сторінка одразу показує готові команди під обрану ОС і архітектуру, з актуальною версією агента і контрольною сумою. Копіюйте їх звідти: версія оновлюється регулярно, і команди з чужої статті швидко застарівають.

⚠️ Токен реєстрації живе близько години. Якщо між копіюванням і запуском config.sh минуло більше часу, конфігурація впаде з помилкою авторизації. Рішення просте: оновити сторінку і взяти новий токен. Цей токен потрібен лише для підключення агента, для роботи він не використовується.

3.2 Завантаження і запуск агента

Усі команди виконуються від звичайного користувача, не від root. Агент відмовиться налаштовуватися під root, і це правильна поведінка: задачі виконуються з правами того користувача, від якого запущено агента.

# окремий системний користувач під runner
sudo adduser --disabled-password --gecos "" runner
sudo usermod -aG docker runner # лише якщо збірки використовують Docker
sudo su - runner

mkdir -p ~/actions-runner && cd ~/actions-runner

# версію і посилання беремо зі сторінки New self-hosted runner
RUNNER_VERSION=2.3xx.x
curl -o actions-runner.tar.gz -L \
https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz

# звірка контрольної суми з тією, що показав GitHub
echo "<хеш зі сторінки> actions-runner.tar.gz" | shasum -a 256 -c

tar xzf ./actions-runner.tar.gz

Розпакований каталог містить config.sh для підключення, run.sh для запуску, svc.sh для оформлення сервісу і скрипт bin/installdependencies.sh, який доставляє системні бібліотеки, потрібні .NET-середовищу агента. Останній запускається один раз і потребує sudo.

sudo ./bin/installdependencies.sh

./config.sh \
--url https://github.com/OWNER/REPO \
--token <токен зі сторінки> \
--name vps-build-01 \
--labels self-hosted,linux,x64,build \
--work _work \
--unattended \
--replace

Що означають ключі:

  • --name це ім'я у списку runner-ів. Робіть його осмисленим: коли агентів стане п'ять, vps-build-01 читається краще за ubuntu-server.
  • --labels це мітки, за якими workflow обирає агента. До трьох стандартних (self-hosted, ОС, архітектура) додавайте власні: build, deploy, gpu.
  • --unattended прибирає інтерактивні питання, що потрібно для автоматизації через Ansible або cloud-init.
  • --replace перезаписує агента з тим самим іменем, якщо він уже зареєстрований.

Перевірити зв'язок можна одразу, у передньому плані:

./run.sh

# √ Connected to GitHub
# Listening for Jobs

У цей момент агент з'являється у списку зі статусом Idle. Зупиняємо його по Ctrl+C і переходимо до сервісу.

3.3 Runner як systemd-сервіс

Запуск через run.sh живе рівно до закриття сесії. Для постійної роботи агент оформлюється сервісом, і скрипт для цього вже лежить у каталозі:

sudo ./svc.sh install runner
sudo ./svc.sh start
sudo ./svc.sh status

Скрипт створює unit з іменем виду actions.runner.OWNER-REPO.vps-build-01.service і вмикає автозапуск. Далі з ним працюють звичайними засобами systemd:

systemctl status 'actions.runner.*'
journalctl -u 'actions.runner.*' -f
sudo ./svc.sh stop
sudo ./svc.sh uninstall

Детальні логи виконання задач лежать окремо, у каталозі _diag усередині папки агента. Туди варто заглядати, коли задача падає без зрозумілого повідомлення в інтерфейсі GitHub.

Два налаштування, які краще зробити одразу:

  • Ротація робочого каталогу. Каталог _work росте нескінченно: кожен репозиторій, кожен артефакт, кожен кеш. Раз на тиждень чистіть його за розкладом або стежте за вільним місцем, бо закінчення диска виглядає як хаотичні падіння збірок.
  • Автооновлення. Агент оновлюється сам, коли GitHub випускає нову версію. Якщо оновлення має відбуватися лише у вашому вікні обслуговування, додайте --disableupdate при конфігурації і оновлюйте вручну. Врахуйте, що застарілий агент з часом перестає приймати задачі.

4. Перший workflow

Агент підключений, тепер даємо йому роботу.

4.1 Структура YAML-файлу

Файл кладеться у .github/workflows/ci.yml і має три обов'язкові частини: name для відображення в інтерфейсі, on для тригерів, jobs для самої роботи.

name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test

GitHub підхоплює файл автоматично після push. Реєструвати його ніде не потрібно, а помилки синтаксису видно на вкладці Actions одразу після коміта.

4.2 runs-on: як задача знаходить ваш агент

Ключ runs-on це фільтр за мітками. Задача піде на агента, у якого є всі перелічені мітки:

# будь-який ваш агент
runs-on: self-hosted

# лише linux x64 серед ваших
runs-on: [self-hosted, linux, x64]

# лише агент з міткою gpu
runs-on: [self-hosted, linux, gpu]

# хмарний агент GitHub
runs-on: ubuntu-latest

Якщо агента з потрібним набором міток немає або він офлайн, задача не падає, а стає у чергу і чекає. За замовчуванням вона висітиме там кілька годин, після чого скасується за тайм-аутом. Це типова причина ситуації «pipeline запустився і нічого не відбувається»: перевіряйте не логи, а збіг міток і статус агента.

Мітки зручно комбінувати з групами runner-ів на рівні організації: група визначає, які репозиторії взагалі мають право слати задачі на цей агент, а мітки вже розподіляють задачі всередині дозволеного.

4.3 Робочий приклад: checkout, залежності, тести

Повний конфіг з коментарями до кожного блоку:

name: CI

on:
push:
branches: [main]
pull_request:

# нові запуски для тієї самої гілки скасовують попередні
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: [self-hosted, linux, x64]
timeout-minutes: 20
steps:
- name: Отримати код
uses: actions/checkout@v4

- name: Поставити Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Встановити залежності
run: npm ci

- name: Лінтер
run: npm run lint

- name: Тести
run: npm test -- --ci

- name: Зберегти звіт
if: always()
uses: actions/upload-artifact@v4
with:
name: test-report
path: reports/
retention-days: 7

Три деталі, які на self-hosted runner-і важливіші, ніж на хмарному:

  • timeout-minutes. Без нього зависла задача займає агента на шість годин, і всі наступні збірки стоять у черзі. На своєму агенті це блокує всю команду, бо запасних машин немає.
  • concurrency. Скасовує попередні запуски тієї самої гілки. Коли агент один, це прибирає безглузді прогони для комітів, які вже перекриті новішими.
  • if: always(). Крок виконається навіть після падіння попередніх. Для збереження звітів це обов'язково, бо цікавий саме звіт про невдалий прогін.

ℹ️ Про setup-node і подібні дії: на self-hosted runner-і вони кешують встановлені версії у каталозі агента, тому перший запуск довший, а наступні майже миттєві. Альтернатива це поставити потрібні версії у систему заздалегідь, але тоді оновлення версії перестає бути зміною одного рядка у конфізі і повертається у ручні операції на сервері.

5. Secrets та змінні

Pipeline майже завжди потребує того, чого не можна класти у репозиторій: SSH-ключів, токенів реєстру образів, паролів до бази. GitHub має для цього окреме сховище.

5.1 Де зберігаються секрети

Шлях в інтерфейсі: Settings → Secrets and variables → Actions. Там дві вкладки. У Secrets лежать значення, які маскуються у логах і не читаються назад після збереження. У Variables лежать звичайні налаштування без маскування: назва гілки, адреса staging-сервера, версія образу.

Секрети існують на трьох рівнях:

Рівень Хто бачить Коли обирати
Repository Усі workflow цього репозиторію Значення, потрібне лише цьому проєкту
Organization Обрані репозиторії організації Спільний токен реєстру, ключ моніторингу
Environment Лише задачі з відповідним environment Продакшн-доступи, які треба відділити від решти

Маскування працює на рівні тексту: якщо значення секрету потрапляє у вивід, GitHub замінює його на зірочки. Захист не абсолютний. Секрет, розібраний на частини, закодований у base64 або виведений посимвольно, у логах видно повністю. Тому окрім маскування діє звичайне правило мінімальних прав: ключ, який може лише покласти файли у каталог і перезапустити один сервіс, не стане катастрофою навіть після витоку.

5.2 Environment secrets для staging і production

Оточення (Settings → Environments) це найкорисніший механізм у цьому розділі. Воно робить дві речі одночасно: тримає власний набір секретів і накладає правила на задачі, які до нього звертаються.

  • Required reviewers. Задача зупиняється і чекає на підтвердження від конкретної людини. Це і є ручна кнопка Continuous Delivery, але з іменем відповідального у логах.
  • Deployment branches. Обмеження, з яких гілок можна викладати. Ставте main для production, і випадковий деплой з гілки з експериментом стане неможливим.
  • Wait timer. Пауза перед виконанням, під час якої деплой можна скасувати.
jobs:
deploy-staging:
runs-on: [self-hosted, linux, deploy]
environment: staging
steps:
- run: ./scripts/deploy.sh
env:
HOST: ${{ secrets.DEPLOY_HOST }} # значення з оточення staging

deploy-production:
needs: deploy-staging
runs-on: [self-hosted, linux, deploy]
environment: production # чекає на підтвердження рецензента
steps:
- run: ./scripts/deploy.sh
env:
HOST: ${{ secrets.DEPLOY_HOST }} # те саме ім'я, інше значення

Однакове ім'я секрету у двох оточеннях це не збіг, а зручність: workflow лишається одним, а різницю між середовищами тримає GitHub.

5.3 Використання секретів у workflow

Звертання відбувається через контекст secrets:

steps:
- name: Правильно: через env
env:
API_TOKEN: ${{ secrets.API_TOKEN }}
run: ./scripts/publish.sh

- name: Небезпечно: значення потрапляє у командний рядок
run: ./scripts/publish.sh --token ${{ secrets.API_TOKEN }}

Різниця між двома кроками не косметична. У другому випадку значення підставляється у рядок команди ще до запуску оболонки, тому воно видно у ps на сервері і потрапляє в історію оболонки. Через env воно передається змінною оточення процесу, і це помітно вужча поверхня.

⚠️ Pull request із форків: для таких запусків секрети недоступні за замовчуванням, і це захист від очевидної атаки. Тригер pull_request_target цей захист знімає, бо виконує workflow у контексті базової гілки разом з секретами. Використовуйте його тільки з кодом базової гілки і ніколи не робіть у ньому checkout коду з форка.

6. Практичний приклад: deploy на сервер через SSH

Збираємо все разом. Мета: після push у main проєкт збирається на runner-і, артефакт їде на продакшн-сервер, сервіс перезапускається.

6.1 Ключ для деплою

Ключ генерується окремий, тільки під цю задачу, і ніколи не збігається з особистим ключем інженера:

ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./gh_deploy -N ""

На продакшн-сервері створюємо користувача без прав root і додаємо йому публічну частину з обмеженнями:

# на продакшн-сервері
sudo adduser --disabled-password --gecos "" deploy
sudo install -o deploy -g deploy -m 700 -d /home/deploy/.ssh

# /home/deploy/.ssh/authorized_keys
from="203.0.113.10",no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAA... github-actions-deploy

Параметр from прив'язує ключ до адреси вашого runner-а: навіть з викраденим приватним ключем зайти з іншої машини не вийде. Права на перезапуск сервісу видаються точково, через sudoers, а не загальним доступом до sudo:

# /etc/sudoers.d/deploy
deploy ALL=(root) NOPASSWD: /bin/systemctl restart app.service

6.2 Секрети у репозиторії

Додаємо три значення в оточення production:

  • SSH_PRIVATE_KEY це вміст файлу gh_deploy повністю, разом з рядками BEGIN і END та кінцевим переносом рядка.
  • SSH_KNOWN_HOSTS це вивід ssh-keyscan -H your-server.example.com.
  • DEPLOY_HOST це адреса сервера.

Другий пункт часто пропускають і замінюють на StrictHostKeyChecking=no. Так робити не варто: ця опція вимикає перевірку, яка захищає від підміни сервера, а її наявність у конфізі означає, що deploy поїде куди завгодно, аби там відповідали по SSH.

6.3 Повний workflow

name: Build and deploy

on:
push:
branches: [main]

concurrency:
group: deploy-production
cancel-in-progress: false

jobs:
build:
runs-on: [self-hosted, linux, x64, build]
timeout-minutes: 20
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- run: npm ci
- run: npm run lint
- run: npm test -- --ci
- run: npm run build

- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7

deploy:
needs: build
runs-on: [self-hosted, linux, deploy]
environment: production
timeout-minutes: 10
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/

- name: Перезапустити сервіс
run: |
ssh -i ~/.ssh/deploy_key deploy@${{ secrets.DEPLOY_HOST }} \
"sudo systemctl restart app.service"

- name: Перевірити, що піднявся
run: |
for i in $(seq 1 10); do
code=$(curl -s -o /dev/null -w "%{http_code}" https://app.example.com/health || true)
[ "$code" = "200" ] && exit 0
sleep 3
done
echo "Сервіс не відповідає після деплою"
exit 1

- name: Прибрати ключ
if: always()
run: rm -f ~/.ssh/deploy_key

Схема deploy через SSH: runner збирає проєкт, копіює файли на продакшн-сервер і перезапускає сервіс

Останній крок з if: always() це не формальність, а прямий наслідок постійного оточення. На хмарному runner-і машина зникає разом із ключем. Ваш агент живе далі, і приватний ключ у домашньому каталозі лишиться доступним будь-якій наступній задачі, включно з тією, що прийшла з іншої гілки.

Крок з перевіркою здоров'я перетворює деплой на дію з результатом. Без нього workflow зеленіє після рестарту сервісу, навіть якщо застосунок одразу впав у краш-цикл, і про аварію ви дізнаєтеся від клієнтів.

ℹ️ Коли SSH не потрібен взагалі: якщо runner стоїть на тому самому сервері, що й застосунок, деплой зводиться до копіювання файлів локально. Спокуса зрозуміла, але збірка це навантаження на CPU, диск і пам'ять, і воно конкуруватиме з продакшном у найгірший момент. Робочий компроміс це окрема машина під runner і приватна мережа між нею і продакшном: збірка ізольована, а трафік деплою не виходить у публічний інтернет.

7. Висновок

Self-hosted runner для GitHub Actions встановлюється за кілька команд, і основна робота починається після них. Що варто винести з цієї частини:

  • Агент не потребує відкритих портів. Він сам іде до GitHub по 443 порту, тому спокійно живе у закритій мережі поруч з продакшном.
  • Мітки це механізм маршрутизації. runs-on шукає агента з усіма переліченими мітками, і задача, що висить у черзі, майже завжди означає розбіжність саме тут.
  • Постійне оточення прискорює і накопичує. Кеш між збірками це виграш, залишені файли і ключі це ризик. Прибирання після себе стає частиною workflow.
  • Оточення важливіші за окремі секрети. Environment з рецензентами і обмеженням гілок дає і розділення доступів, і ручний крок перед продакшном.
  • Деплой закінчується перевіркою. Зелений pipeline без health-check означає лише те, що команди виконалися, а не те, що застосунок працює.

Мінімальний робочий набір виглядає так: окремий користувач під агента, systemd-сервіс, мітки під типи задач, timeout-minutes у кожній задачі, секрети в оточенні production і прибирання ключів у кроці з if: always().

Що далі у серії

У четвертій частині ми зробимо те саме на GitLab CI: встановимо GitLab Runner на VPS, розберемо executors (shell, docker, docker+machine) і пройдемо шлях від першого .gitlab-ci.yml до деплою на продакшн. Порівняння з GitHub Actions буде наскрізним, бо архітектурні рішення там відрізняються сильніше, ніж синтаксис.

📚 Навігація по серії:
Ви читаєте частину 3 з 6 «GitHub Actions self-hosted runner: встановлення та перший pipeline на VPS».
Попередня: ← Частина 2. Self-hosted CI/CD: концепція, архітектура і runner'и
Наступна: Частина 4. GitLab CI self-hosted runner: встановлення та перший pipeline на VPS →

🚀 VPS і виділені сервери під ваші GitHub Actions runner'и

Runner упирається у CPU під час збірки, у диск під час встановлення залежностей і у мережу під час деплою. Hostiserver дає для цього передбачувані ресурси і мережу, з якої можна безпечно ходити на продакшн.

🖥️ Виділені Сервери

  • Від $90/міс, повний контроль над залізом для важких збірок і паралельних runner-ів
  • NVMe-накопичувачі: швидкий кеш npm, Maven і Docker-шарів між прогонами
  • Без обмеження хвилин збірки: платите за сервер, а не за час pipeline
  • Приватна мережа між runner-ом і продакшн-серверами
  • 24/7 підтримка: інженери допоможуть з налаштуванням агента і деплою

💻 Cloud (VPS) Хостинг

  • Від $19.95/міс, KVM-ізоляція, виділені vCPU і RAM
  • Ідеально для першого runner-а: підняти, підключити до репозиторію, перевірити на реальному проєкті
  • Легко масштабувати: окремий VPS під build і окремий під deploy, з різними мітками

💬 Не впевнені, який варіант вам необхідний?
💬 Напишіть нам і ми зі всім допоможемо!

Часті питання

Чи можна поставити runner на той самий сервер, де живе продакшн?

Технічно так, і для невеликого проєкту це працює. Проблема у конкуренції за ресурси: збірка з'їдає CPU і пам'ять саме тоді, коли викладається реліз, тобто у момент найбільшого навантаження на застосунок. Друга проблема це права: агент, що стоїть поруч з продакшном, зазвичай отримує доступ до файлів застосунку і до перезапуску сервісів, а разом з ним цей доступ отримує будь-який код, який виконується у pipeline. Компроміс це окремий дешевий VPS під runner і приватна мережа до продакшну.

Скільки ресурсів потрібно VPS під один runner?

Сам агент споживає близько 150-250 МБ RAM у простої, решта залежить від збірок. Для типового веб-проєкту на Node.js або Python вистачає 2 vCPU і 4 ГБ RAM, для збірок з Docker краще 4 vCPU і 8 ГБ. Диск важливіший, ніж здається: 40 ГБ це мінімум, бо _work, кеш пакетів і Docker-шари ростуть швидко. Один агент виконує одну задачу за раз, тому для паралельних збірок або додають ще агентів, або беруть сервер потужніший.

Runner показує статус Offline, хоча сервіс запущений. Що перевіряти?

Спершу логи: journalctl -u 'actions.runner.*' -n 100 і каталог _diag у папці агента. Найчастіші причини це блокування вихідних з'єднань на 443 порт до github.com з боку фаєрвола або корпоративного проксі, застаріла версія агента, яку GitHub уже не приймає, і закінчене місце на диску. Окремий випадок це видалений runner на боці GitHub: агент продовжує працювати, але його токен більше не дійсний, і допомагає лише повторна конфігурація з новим токеном.

Чи безпечно вмикати self-hosted runner у публічному репозиторії?

Ні, і GitHub про це прямо попереджає. Будь-хто може відкрити pull request, і його код виконається на вашому сервері. Оточення при цьому постійне, тому наслідки не зникають після завершення задачі: залишений процес, змінений системний пакет, прочитані файли з робочого каталогу. Для публічних проєктів беріть GitHub-hosted runner-и. Якщо власний агент потрібен саме там, лишається варіант з ephemeral-агентами у одноразових контейнерах і обов'язковим підтвердженням запуску для зовнішніх контриб'юторів.

Як запустити кілька runner-ів на одному сервері?

Кожен агент живе у власному каталозі з власною конфігурацією. Створюєте ~/actions-runner-1, ~/actions-runner-2, у кожному запускаєте config.sh з унікальним --name, потім svc.sh install. Вийде два незалежні systemd-сервіси і дві паралельні задачі. Розумна межа це кількість ядер, поділена на два: агенти конкурують за CPU і диск, і чотири одночасні збірки на двоядерному VPS будуть повільнішими, ніж дві послідовні.

Чи потрібен actions/cache, якщо оточення і так постійне?

Здебільшого ні. На постійному агенті node_modules, каталог ~/.m2 або ~/.cache/pip лишаються між прогонами, тому actions/cache додає зайвий цикл вивантаження і завантаження через мережу GitHub. Виняток це кілька агентів: тоді спільний кеш вирівнює час збірки незалежно від того, кому дісталася задача. Для ephemeral-агентів кеш потрібен обов'язково, бо оточення щоразу нове.

Що робити, коли на runner-і немає потрібної версії мови або утиліти?

Два шляхи. Перший це дії на кшталт setup-node, setup-python, setup-java: вони самі завантажують потрібну версію і кешують її на агенті, а версія лишається описаною у репозиторії. Другий це виконувати кроки у контейнері через ключ container на рівні задачі, і тоді оточення повністю описується образом. Ручне встановлення пакетів на сервері працює теж, але повертає ту саму проблему, з якої ми починали серію: оточення відоме лише тому, хто його налаштовував.

Contents

Поділіться цією статтею

VPS з підтримкою від

$19 95 / міс

Виділені сервери від

$80 / міс

CDN починаючи від

$0 / міс

 

Користуючись цим сайтом, ви погоджуєтеся на використання файлів cookies відповідно до нашої Політики Конфіденційності.