HostiServer
2026-08-18 11:17
GitLab CI self-hosted runner: інсталяція на VPS
📚 Серія «CI/CD з нуля», частина 4 з 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
GitLab CI self-hosted runner: встановлення та перший pipeline на VPS
У третій частині ми підключили власний агент до GitHub Actions і довели ланцюжок до деплою на продакшн через SSH. Тепер той самий шлях проходимо на GitLab CI: встановлюємо GitLab Runner на VPS, реєструємо його у проєкті, пишемо перший .gitlab-ci.yml і закінчуємо тим самим сценарієм деплою. Сценарій навмисно повторюється, щоб різницю між платформами було видно на однаковій задачі, а не на двох різних прикладах.
Різниця виявиться глибшою за синтаксис. У GitLab pipeline будується на стадіях, задача за замовчуванням виконується у контейнері, а вибір executor визначає, як саме ізольовано ваш код. Ці три речі впливають на архітектуру рішення сильніше, ніж вигляд YAML.
1.1 Що потрібно на старті
- проєкт на GitLab: у хмарі на gitlab.com або у власній інсталяції;
- VPS з Ubuntu 22.04 чи 24.04 і доступом по SSH;
- роль Maintainer або Owner у проєкті: нижче за неї сторінка з runner-ами недоступна.
Як і агент GitHub, GitLab Runner не потребує відкритих вхідних портів. Він сам звертається до GitLab по 443 порту і тримає з'єднання, чекаючи на завдання. Тому runner спокійно живе всередині закритого контуру поруч з продакшном.
1.2 gitlab.com і власний runner: комбінація, яку часто пропускають
Поширене непорозуміння: щоб отримати self-hosted CI на GitLab, нібито треба піднімати весь GitLab у себе. Це не так. Хмарний gitlab.com і власний runner поєднуються без обмежень: репозиторій, інтерфейс, merge request і сторінка pipeline лишаються у хмарі, а збірка виконується на вашому сервері, з вашими ресурсами і вашим доступом до продакшн-мережі.
Три сценарії, у яких така схема доречна:
- Квота хвилин вичерпується. Хмарні хвилини рахуються і закінчуються, власний сервер оплачується фіксовано незалежно від кількості збірок.
- Потрібні нестандартні ресурси. Багато RAM під важку збірку, GPU під тести моделей, специфічне залізо, якого у хмарних планах немає.
- Деплой іде у закриту мережу. Хмарний runner туди не дістанеться, ваш стоїть усередині.
Власна інсталяція GitLab (self-managed) це окреме рішення з іншими причинами: вимоги до зберігання коду, регуляторні обмеження, робота без доступу до інтернету. Її можна робити пізніше і незалежно, бо конфігурація pipeline і runner при переїзді не змінюється.
ℹ️ Про назви: GitLab CI це сама система pipeline, вбудована у GitLab з 2015 року. GitLab Runner це окрема програма, написана на Go, яка виконує задачі. Вона встановлюється незалежно від GitLab і оновлюється своїм циклом.
2. Архітектура GitLab CI
Три поняття, на яких тримається все подальше: файл конфігурації, ієрархія pipeline і runner як окремий процес.
2.1 .gitlab-ci.yml: один файл у корені репозиторію
На відміну від GitHub Actions з каталогом .github/workflows/ і довільною кількістю файлів, GitLab шукає один файл: .gitlab-ci.yml у корені. Він описує весь pipeline проєкту.
Обмеження знімається двома механізмами. Перший це include: конфігурацію можна розбити на частини і підключати їх з інших файлів, з інших проєктів або за URL.
include:
- local: '/ci/build.yml'
- project: 'company/ci-templates'
ref: main
file: '/deploy/ssh.yml'
- template: Security/SAST.gitlab-ci.yml
Другий це шаблони задач через якорі YAML і ключ extends, які прибирають повтори всередині файлу. Разом вони дають те саме, що у GitHub досягається кількома workflow і перевикористовуваними діями, тільки точкою входу лишається один файл.
Шлях до файлу змінюється у Settings → CI/CD → General pipelines → CI/CD configuration file. Там же можна вказати файл з іншого проєкту, якщо конфігурація централізована.
2.2 Stages → Jobs → Scripts
Stage це логічна група задач і головна відмінність від GitHub Actions. Стадії виконуються послідовно у порядку, оголошеному в stages. Наступна стартує тоді, коли попередня завершилася повністю і успішно.
Job це задача всередині стадії. Задачі однієї стадії виконуються паралельно на різних runner-ах, тому стадія триває стільки, скільки найповільніша задача у ній.
Script це список команд оболонки, які задача виконує. Тут відмінність помітна: у GitHub крок буває готовою дією через uses, у GitLab задача це майже завжди команди. Замість marketplace дій використовуються образи контейнерів і підключені шаблони.
stages: [build, test, deploy]
build-app: stage: build →┐
│ стадія build
build-docs: stage: build →┘ (паралельно)
↓
unit-tests: stage: test →┐
│ стадія test
lint: stage: test →┘ (паралельно)
↓
deploy-prod: stage: deploy → стадія deploy
Сувору черговість стадій можна обійти ключем needs. Він будує граф залежностей поверх стадій: задача стартує одразу після тих, від яких залежить, не чекаючи на решту своєї стадії. На великих pipeline це помітно скорочує загальний час.
deploy-staging:
stage: deploy
needs: [build-app] # не чекає на build-docs і на всю стадію test
script:
- ./scripts/deploy.sh staging
2.3 GitLab Runner як окремий процес
GitLab Runner це самостійна програма, яка не залежить від GitLab і навіть не мусить стояти поруч з ним. Її робота виглядає так: раз на кілька секунд вона опитує GitLab через API, чи є задача для її міток і рівня реєстрації. Отримавши задачу, готує оточення, виконує команди, потоком віддає логи і повертає статус.
Реєстрація буває на трьох рівнях:
| Рівень | Хто може використати | Коли обирати |
|---|---|---|
| Project | Один проєкт | Перший runner, деплой з доступом до конкретного продакшну |
| Group | Усі проєкти групи | Спільні збірки кількох сервісів команди |
| Instance | Уся інсталяція | Тільки для self-managed GitLab |
Ключова властивість, якої немає у GitHub Actions: один runner виконує кілька задач одночасно. Параметр concurrent у конфізі задає, скільки задач процес runner виконує паралельно на всіх зареєстрованих runner-ах разом, тому один VPS замінює кілька окремих агентів.
3. Встановлення GitLab Runner на VPS
3.1 Встановлення пакету
Найзручніший шлях це офіційний репозиторій пакетів:
# Debian / Ubuntu
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt install gitlab-runner
# RHEL / Rocky / AlmaLinux
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" | sudo bash
sudo yum install gitlab-runner
Пакет одразу створює системного користувача gitlab-runner, конфігурацію у /etc/gitlab-runner/config.toml і systemd-сервіс з автозапуском. Тобто крок з оформленням сервісу, який у GitHub Actions робився окремо через svc.sh, тут уже виконано.
Альтернатива це бінарник, коли репозиторії недоступні або потрібна конкретна версія. Файл для потрібної архітектури беріть зі сторінки релізів GitLab Runner: прямі адреси S3-бакета періодично змінюються, і команди зі сторонніх інструкцій швидко застарівають.
sudo curl -L --output /usr/local/bin/gitlab-runner "<посилання зі сторінки релізів>"
sudo chmod +x /usr/local/bin/gitlab-runner
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start
Перевірка після встановлення:
gitlab-runner --version
sudo systemctl status gitlab-runner
⚠️ Сумісність версій: версія runner не повинна випереджати версію GitLab. Для gitlab.com це неактуально, бо хмара оновлюється першою, але для self-managed інсталяції правило робоче: спершу оновлюють GitLab, потім runner. Runner, старший за GitLab на кілька мажорних версій, починає втрачати підтримку нових ключів конфігурації.
3.2 Реєстрація runner
З версії 16.0 GitLab перейшов на реєстрацію через токен, створений в інтерфейсі. Шлях: Settings → CI/CD → Runners → New project runner. Там задаються мітки (tags), опція для запуску задач без міток і опційний опис, після чого GitLab показує токен виду glrt-....
sudo gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--token "glrt-XXXXXXXXXXXXXXXX" \
--executor "docker" \
--docker-image "alpine:latest" \
--description "vps-build-01"
Старий спосіб з --registration-token і ключем --tag-list у документації ще трапляється, але у сучасних версіях він не працює: мітки тепер задаються в інтерфейсі при створенні токена, а не у команді.
Після реєстрації runner з'являється у списку зі станом online, а його налаштування лягають у /etc/gitlab-runner/config.toml:
concurrent = 4
check_interval = 3
[[runners]]
name = "vps-build-01"
url = "https://gitlab.com/"
token = "glrt-XXXXXXXXXXXXXXXX"
executor = "docker"
[runners.docker]
image = "alpine:latest"
privileged = false
volumes = ["/cache"]
Файл перечитується без перезавантаження сервісу: після правки зміни застосовуються самі. Параметр concurrent задає кількість паралельних задач на весь агент, і починати варто з кількості ядер, поділеної на два.
3.3 Executor: shell чи docker
Executor визначає, де саме виконуються команди задачі. Це головне рішення при налаштуванні, і змінювати його потім незручно.
| Параметр | shell | docker |
|---|---|---|
| Де виконується | Прямо на хості | У контейнері з образу задачі |
| Ізоляція | Немає | Процеси і файлова система відокремлені |
| Оточення | Спільне, накопичує стан | Чисте під кожну задачу |
Ключ image у конфізі |
Ігнорується | Працює |
| Швидкість старту | Миттєвий | Секунди на створення контейнера |
| Що ставити на сервер | Усі мови й утиліти вручну | Лише Docker |
Розподіл виходить простий. Docker береться за замовчуванням для збірок і тестів: оточення описується образом у репозиторії, задачі не залишають слідів, версія мови змінюється одним рядком. Shell лишається для задач деплою, яким потрібен доступ до самого хоста: ключі у ~/.ssh, налаштовані rsync і ansible, доступ до локальних сокетів.
Ці два режими не конкурують. Робоча схема це два runner на одному VPS: один з executor docker і міткою build, другий з executor shell і міткою deploy. Реєструються вони окремими командами register і живуть у одному config.toml двома блоками [[runners]].
⚠️ Про privileged = true: цей режим потрібен для збірки образів через Docker-in-Docker, і водночас він дає задачі права root на хості. Для приватного репозиторію з довіреним кодом це прийнятний компроміс, для чужого коду ні. Безпечніша альтернатива це збірка образів через Kaniko або Buildah у режимі без привілеїв.
4. Перший .gitlab-ci.yml
4.1 Стадії
Файл починається з переліку стадій. Порядок у списку це і є порядок виконання:
stages:
- build
- test
- deploy
Задача без ключа stage потрапляє у стадію test. Це поведінка за замовчуванням, яка іноді дає несподіванки, тому стадію краще вказувати явно.
4.2 Базова задача
stages: [build, test, deploy]
default:
image: node:20
tags: [self-hosted, docker]
interruptible: true
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
build:
stage: build
script:
- npm ci --cache .npm --prefer-offline
- npm run build
cache:
key:
files:
- package-lock.json
paths:
- .npm/
artifacts:
paths:
- dist/
expire_in: 1 week
lint:
stage: test
script:
- npm ci --cache .npm --prefer-offline
- npm run lint
unit-tests:
stage: test
script:
- npm ci --cache .npm --prefer-offline
- npm test -- --ci
artifacts:
when: always
reports:
junit: reports/junit.xml
Що тут важливо:
tagsце аналогruns-onу GitHub. Задача піде на runner, у якого є всі перелічені мітки. Розбіжність тут це найчастіша причина задачі, що зависла у стані pending.defaultзадає спільні налаштування для всіх задач, щоб не повторюватиimageіtagsу кожній.interruptible: trueдозволяє скасовувати застарілі запуски, коли у гілку прилетів новіший коміт. Разом з увімкненою опцією проєкту це аналогconcurrencyу GitHub Actions.artifacts: reportsце вбудований механізм, якого у GitHub немає: GitLab розбирає звіт і показує результати тестів прямо у merge request.
4.3 Правила запуску: rules замість only
Ключ only/except досі працює, але розвиток зупинено, і у новій конфігурації використовується rules:
deploy-production:
stage: deploy
script:
- ./scripts/deploy.sh
rules:
# не запускати для змін лише у документації
- if: $CI_COMMIT_BRANCH == "main"
changes:
paths: ["docs/**/*", "*.md"]
when: never
# у main з ручним підтвердженням
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: false
# у решті випадків задачі немає у pipeline
- when: never
Правила перевіряються згори вниз, спрацьовує перше, що збіглося. Значення when: manual створює задачу з кнопкою запуску, а allow_failure: false робить її блокуючою: поки кнопку не натиснуто, pipeline не вважається завершеним. Це і є ручний крок Continuous Delivery.
4.4 Артефакти між задачами
Артефакти у GitLab передаються між стадіями автоматично: задача стадії deploy отримує артефакти всіх попередніх стадій без явного завантаження. У GitHub Actions для цього потрібні окремі кроки upload-artifact і download-artifact.
build:
stage: build
artifacts:
paths: [dist/]
expire_in: 1 week
deploy:
stage: deploy
script:
- ls dist/ # файли вже на місці
# обмежити перелік: забрати артефакти лише однієї задачі
dependencies: [build]
Ключ dependencies варто ставити свідомо. Без нього задача тягне артефакти всіх попередніх стадій, і на великому pipeline це десятки зайвих мегабайтів на кожну задачу.
ℹ️ Артефакти і кеш це різні речі. Артефакт це результат, потрібний наступним задачам: якщо він зникне, pipeline зламається. Кеш це оптимізація повторних запусків: якщо він зникне, збірка просто піде повільніше. Орієнтир з першої частини серії лишається чинним: кешуйте те, що можна відновити з мережі, і складайте в артефакти те, що створив саме ваш pipeline.
5. CI/CD Variables
У GitLab немає окремого сховища секретів, як Secrets у GitHub. Замість цього є змінні з прапорцями, які визначають рівень захисту.
5.1 Де живуть змінні
Шлях: Settings → CI/CD → Variables. Кожна змінна має ключ, значення, тип (Variable або File) і набір прапорців.
Тип File вирішує задачу, яка у GitHub розв'язується вручну: GitLab кладе значення у тимчасовий файл, а у змінну підставляє шлях до нього. Для SSH-ключів і конфігурацій це прибирає кроки з echo у файл і заодно проблеми з переносами рядків.
5.2 Protected і Masked: різниця
Два прапорці вирішують різні задачі, і плутають їх постійно.
| Прапорець | Що робить | Від чого захищає |
|---|---|---|
| Protected | Змінна доступна лише у задачах із захищених гілок і тегів | Від доступу до продакшн-секретів з будь-якої гілки |
| Masked | Значення замінюється зірочками у логах задачі | Від випадкового виводу у лог |
Головне тут: Masked без Protected не захищає ні від чого серйозного. Будь-хто, хто може створити гілку, пише у неї задачу з cat по змінній у base64 і отримує значення в обхід маскування. Захист дає саме Protected разом з налаштуванням захищених гілок у Settings → Repository → Protected branches.
У маскування є технічні вимоги: значення від 8 символів, без пробілів і переносів рядків, лише символи з обмеженого набору. Багаторядковий SSH-ключ замаскувати не вийде, і саме тому для нього береться тип File разом з прапорцем Protected.
⚠️ Merge request із форків: для них захищені змінні недоступні за замовчуванням, і це правильна поведінка. Окремо перевіряйте налаштування Settings → CI/CD → General pipelines → Run pipelines for merge requests from forks: у комбінації з self-hosted runner воно означає, що чужий код виконається на вашому сервері.
5.3 Змінні на рівні групи
Якщо кілька проєктів деплояться на ту саму інфраструктуру, змінні краще тримати на рівні групи: Group → Settings → CI/CD → Variables. Вони успадковуються всіма проєктами групи, а зміна робиться в одному місці.
Порядок пріоритету при однакових іменах, від найвищого до найнижчого:
- змінні, задані при ручному запуску pipeline;
- змінні проєкту;
- змінні групи (вкладена група має пріоритет над батьківською);
- змінні інстанса;
- змінні з
.gitlab-ci.yml.
Практичний висновок: спільні значення тримайте у групі, а окремі проєкти перевизначають те, що у них відрізняється, тим самим іменем. Конфігурація у .gitlab-ci.yml при цьому лишається однаковою для всіх.
Ще один рівень це Environments зі scope: одна змінна DEPLOY_HOST має різні значення для staging і production, а задача отримує потрібне через ключ environment. Логіка та сама, що у Environment secrets з попередньої частини.
6. Практичний приклад: deploy на сервер через SSH
6.1 Той самий сценарій
Умови повністю повторюють приклад з третьої частини: після push у main проєкт збирається, артефакт їде на продакшн-сервер через rsync, сервіс перезапускається, після чого перевіряється health-check. На сервері вже є користувач deploy без прав root, ключ прив'язаний параметром from до адреси runner, а перезапуск сервісу дозволений точково через sudoers.
Змінні у Settings → CI/CD → Variables:
SSH_PRIVATE_KEY— тип File, прапорець Protected. Тип File тут прибирає всю ручну роботу з переносами рядків.SSH_KNOWN_HOSTS— тип File, Protected. Вміст це вивідssh-keyscan -H your-server.example.com.DEPLOY_HOST— звичайна змінна з адресою сервера.
6.2 Повний .gitlab-ci.yml
stages: [build, test, deploy]
default:
interruptible: true
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
build:
stage: build
image: node:20
tags: [self-hosted, docker]
script:
- npm ci --cache .npm --prefer-offline
- npm run build
cache:
key:
files: [package-lock.json]
paths: [.npm/]
artifacts:
paths: [dist/]
expire_in: 1 week
test:
stage: test
image: node:20
tags: [self-hosted, docker]
script:
- npm ci --cache .npm --prefer-offline
- npm run lint
- npm test -- --ci
artifacts:
when: always
reports:
junit: reports/junit.xml
deploy-production:
stage: deploy
tags: [self-hosted, shell] # executor shell: потрібен доступ до хоста
environment:
name: production
url: https://app.example.com
dependencies: [build]
timeout: 10 minutes
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: false
before_script:
# варіант 1, тип змінної File: GitLab уже поклав ключ у тимчасовий файл
- chmod 600 "$SSH_PRIVATE_KEY"
# варіант 2, ключ у звичайній змінній — як у GitHub Actions:
# - install -m 700 -d ~/.ssh
# - install -m 600 /dev/null ~/.ssh/deploy_key
# - echo "$SSH_PRIVATE_KEY_RAW" > ~/.ssh/deploy_key
# - echo "$SSH_KNOWN_HOSTS_RAW" > ~/.ssh/known_hosts
# after_script:
# потрібно лише для варіанта 2: тип File видаляється платформою
# - rm -f ~/.ssh/deploy_key
after_script:
# shell-executor зберігає робочий каталог між задачами: прибираємо ключ явно
- 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"
- |
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
У задачі деплою навмисно показані два варіанти роботи з ключем. Перший, робочий за замовчуванням, спирається на тип змінної File: GitLab сам створює тимчасовий файл і прибирає його при очищенні робочого каталогу задачі, тому кроків з echo у файл немає. Оскільки shell-executor зберігає робочий каталог між запусками, ключ прибирається явно у after_script. Другий, закоментований, повторює підхід з третьої частини: ключ лежить у звичайній змінній, файл створюється вручну, а прибирати доводиться вже ~/.ssh/deploy_key. Він знадобиться при міграції з GitHub Actions, коли конфіг переносять як є і міняють синтаксис поступово.
Різниця тут не у можливостях платформ, а у тому, хто виконує рутинну роботу: тип File знімає створення файлу і роботу з переносами рядків, а прибирання лишається на вас у обох варіантах. На постійному runner-і це має практичну вагу, бо забутий крок очищення лишає приватний ключ доступним для наступної задачі.
Ключ environment дає ще один ефект, окрім scope змінних. GitLab веде історію деплоїв на сторінці Deployments → Environments: видно, який коміт зараз на продакшні, коли він туди потрапив і хто запустив задачу. Там же є кнопка відкату на попередній успішний деплой, яка просто перезапускає ту саму задачу зі старим комітом.
ℹ️ Дві мітки на одному сервері: задачі build і test ідуть на runner з executor docker, задача deploy на runner з executor shell. Обидва стоять на тому самому VPS, розрізняються мітками і живуть у одному config.toml. Так збірка виконується у чистому контейнері, а деплой отримує доступ до ключів і мережі хоста, без привілейованих контейнерів.
7. Висновок
Ми пройшли той самий шлях, що і у третій частині, але на GitLab CI. Головне з практики:
- Хмарний GitLab і власний runner поєднуються вільно. Піднімати self-managed GitLab заради self-hosted CI не потрібно.
- Executor це головне рішення при налаштуванні. Docker для збірок і тестів, shell для деплою, обидва на одному сервері з різними мітками.
- Стадії задають порядок,
needsйого прискорює. Граф залежностей прибирає очікування там, де воно не потрібне. - Masked без Protected це не захист. Секрет закриває саме прапорець Protected разом з захищеними гілками.
- Тип File прибирає ручну роботу з ключами. Тимчасовий файл створюється і видаляється платформою, тому забути прибрати ключ неможливо.
Різниця з GitHub Actions складається у зрозумілу картину. GitLab дає більше вбудованого: стадії, звіти тестів у merge request, історію деплоїв з відкатом, паралельні задачі на одному агенті. GitHub дає більше готового ззовні: marketplace дій закриває типові кроки одним рядком. Перше цінніше для складних pipeline, друге для швидкого старту.
Що далі у серії
У п'ятій частині ми зробимо крок, після якого назовні не виходить нічого: Gitea Actions, де git-платформа і pipeline живуть на одному вашому сервері. Синтаксис там майже повторює GitHub Actions, тому конфігурації з третьої частини переносяться майже без правок, а от сервер тепер тримає і git-платформу, і pipeline, тому вимоги до ресурсів і адміністрування інші. Порівняння всіх трьох платформ з відповіддю на питання, що обирати під конкретні умови, чекає у шостій частині.
📚 Навігація по серії:
Ви читаєте частину 4 з 6 «GitLab CI self-hosted runner: встановлення та перший pipeline на VPS».
Попередня: ← Частина 3. GitHub Actions self-hosted runner: встановлення та перший pipeline на VPS
Наступна: Частина 5. Gitea Actions: Git-платформа і pipeline на одному сервері →
🚀 VPS і виділені сервери під ваші GitLab Runner'и
Один runner виконує кілька задач одночасно, і кожна з них упирається у CPU, диск і мережу. Hostiserver дає для цього передбачувані ресурси і мережу, з якої можна безпечно ходити на продакшн.
🖥️ Виділені Сервери
- Від $90/міс, повний контроль над залізом і високий
concurrentбез черг - NVMe-накопичувачі: швидкий кеш залежностей і Docker-шарів між прогонами
- Без обмеження хвилин збірки: платите за сервер, а не за квоту GitLab
- Приватна мережа між runner-ом і продакшн-серверами
- 24/7 підтримка: інженери допоможуть з налаштуванням runner-ів і деплою
💻 Cloud (VPS) Хостинг
- Від $19.95/міс, KVM-ізоляція, виділені vCPU і RAM
- Ідеально для першого runner-а: docker для збірок і shell для деплою на одній машині
- Легко масштабувати: додати сервер під збірки, коли
concurrentперестане справлятися
💬 Не впевнені, який варіант вам необхідний?
💬 Напишіть нам і ми зі всім допоможемо!
Часті питання
- Чи потрібен власний GitLab, щоб мати self-hosted runner?
Ні. Хмарний gitlab.com підключає ваш runner без обмежень: репозиторій, merge request і сторінка pipeline лишаються у хмарі, а збірки виконуються на вашому сервері. Власна інсталяція GitLab потрібна з інших причин: вимоги до зберігання коду, робота без доступу до інтернету, повний контроль над даними. Це окреме рішення, і конфігурація pipeline при переїзді на self-managed не змінюється.
- Executor shell чи docker: що обрати для першого runner?
Docker для збірок і тестів, бо оточення описується образом у репозиторії і задачі не залишають слідів на сервері. Shell для задач деплою, яким потрібен доступ до ключів і мережі хоста. Найзручніше зареєструвати обидва на одному VPS з різними мітками:
dockerіshell. Спроба робити все через shell закінчується сервером, на який поступово поставили п'ять версій Node.js, а через docker з привілейованим режимом дає задачі права root на хості.
- Чим Protected відрізняється від Masked?
Protected обмежує доступ: змінна видима лише задачам із захищених гілок і тегів. Masked приховує значення у логах, замінюючи його зірочками. Захист від витоку дає саме Protected: без нього будь-хто, хто може створити гілку, виведе значення у base64 і обійде маскування. Для продакшн-секретів вмикайте Protected обов'язково, Masked додатково. Багаторядковий SSH-ключ замаскувати не вийде через технічні вимоги, тому для нього беруть тип File.
- Задача висить у стані pending і не стартує. Що перевіряти?
Найчастіша причина це розбіжність міток: у задачі вказані
tags, яких немає у жодного онлайн-runner. Друга за поширеністю це задача без міток: такі задачі бере лише runner з увімкненою відповідною опцією, і за замовчуванням вона вимкнена. Далі перевіряйте статус агента (sudo gitlab-runner statusіsudo gitlab-runner verify), вичерпанийconcurrentі, для захищених змінних, чи є гілка захищеною. Логи агента дивляться черезjournalctl -u gitlab-runner -f.
- Скільки задач тягне один VPS і як налаштувати
concurrent? Параметр
concurrentу/etc/gitlab-runner/config.tomlзадає кількість паралельних задач на всі зареєстровані runner-и разом. Обмежувальним тут частіше буває не процесор, а пам'ять: рахуйте її за найважчою задачею, помноженою наconcurrent, бо збірка фронтенду легко з'їдає 2 ГБ. Чотири одночасні збірки на двоядерному VPS ідуть повільніше, ніж дві послідовні. Значення змінюється у файлі і застосовується без перезапуску сервісу.
- Як перенести конфігурацію з GitHub Actions на GitLab CI?
Автоматичного перенесення немає, але відповідність пряма:
jobsстають задачами,runs-onперетворюється наtags,stepsзrunлягають уscript,needsпрацює однаково. Складніше з крокамиuses: готових дій у GitLab немає, тому кожен замінюється або командами уscript, або відповідним образом контейнера. У GitLab є вбудований імпорт проєктів з GitHub разом з історією і merge request, а от файл.gitlab-ci.ymlпишеться заново.
- Чи можна відкотити деплой засобами GitLab?
Так, якщо задача деплою має ключ
environment. На сторінці Deployments → Environments зберігається історія: який коміт зараз на продакшні, коли потрапив туди і хто запустив задачу. Кнопка Rollback перезапускає ту саму задачу зі старим комітом. Працює це рівно настільки, наскільки ваш деплой ідемпотентний: викладка статичних файлів або образу відкочується без проблем, а от міграції бази цим механізмом не відкочуються і потребують окремого плану.