0% прочитано

CI/CD у Laravel-проєкті: що це і як він працює у моєму реальному проєкті

CI/CD для мене - це не просто автоматичний deploy. У своєму Laravel-проєкті я використовую pipeline, який послідовно перевіряє код, запускає тести, збирає frontend і тільки після цього дозволяє рухатися до deployment.

20 серпня 2026 р. 8 хв читанняCI/CD

CI/CD у моєму Laravel-проєкті - це pipeline, який перевіряє зміни ще до того, як вони потрапляють у потрібне середовище. Я використовую його для build, перевірки якості коду, тестів і контрольованого deploy.

Що для мене означає CI/CD

Коли я тільки починала працювати з CI/CD, він легко сприймався як щось на кшталт pipeline, який запускає deploy. У реальному проєкті я дивлюся на нього трохи інакше.

CI (Continuous Integration - безперервна інтеграція) для мене - це автоматична перевірка змін: чи збирається backend і frontend, чи проходить static analysis, чи не зламані тести.

CD (Continuous Delivery / Continuous Deployment - безперервна доставка / безперервне розгортання) починається пізніше - коли вже перевірений код потрібно доставити на development або production.

Тому головна користь мого pipeline навіть не в тому, що він щось деплоїть. Для мене важливіше, що він не дозволяє рухатися далі, якщо один із потрібних етапів не пройшов.

Раніше я вже порівнювала, як для мене виглядала ця робота через Bitbucket CI Bot і через GitLab Pipeline.

Як побудований мій pipeline

Етапи CI/CD pipeline у моєму Laravel-проєкті: build, quality і test
Один із реальних Merge Request pipeline: build → quality → test.

У самому .gitlab-ci.yml у мене визначено п’ять stages:

  1. infrastructure

  2. build

  3. quality

  4. test

  5. deploy

Але це не означає, що всі п’ять я бачу в кожному pipeline. Наприклад, на зображенні вище є тільки build, quality і test. Infrastructure потрібен лише за певних змін, а deploy має власні умови запуску.

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

YAML - Stages мого pipeline
stages:  - infrastructure  - build  - quality  - test  - deploy
Не кожен pipeline проходить усі stages. Я спеціально обмежую запуск jobs залежно від типу pipeline, target branch і файлів, які змінилися.

Коли я запускаю pipeline

YAML - Основні workflow rules
workflow:  rules:    - if: '$CI_PIPELINE_SOURCE == "web" && $RUN_ALL == "true"'     - if: '$CI_PIPELINE_SOURCE == "web" && $FORCE_CI_IMAGE_BUILD == "true"'     - if: '$CI_PIPELINE_SOURCE == "web"'      when: never     - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'     - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "development"'     - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'     - when: never

Тут я свідомо не створюю pipeline на кожен звичайний push у feature branch.

Merge Request отримує свій pipeline. Push у development потрібен для deployment на dev, а push у main - для production flow. Окремо я можу вручну запустити повну перевірку через RUN_ALL або перебудувати PHP CI image через FORCE_CI_IMAGE_BUILD.

Усе інше завершується правилом when: never.

Для мене це простий спосіб не запускати jobs без причини.

Build: спочатку я перевіряю, що проєкт взагалі збирається

YAML - Скорочений build stage
backend_build:  stage: build   script:    - 'composer validate --strict'     - >      composer install      --no-interaction      --prefer-dist      --no-progress      --no-dev      --optimize-autoloader    - 'composer check-platform-reqs --no-dev'    - 'php artisan optimize'    - 'php artisan about'  frontend_build:  stage: build   image: node:24-bookworm   script:    - 'npm ci --cache "$NPM_CONFIG_CACHE" --prefer-offline'    - 'npm run build'    - 'test -s public/build/manifest.json'    - 'test -s bootstrap/ssr/ssr.js'

Backend і frontend я перевіряю окремо.

Для backend я перевіряю Composer configuration, встановлюю production dependencies, перевіряю platform requirements і запускаю Laravel optimize.

Для frontend використовую Node 24, виконую npm ci і production build.

Після frontend build я додатково перевіряю, що реально створилися public/build/manifest.json і SSR bundle. Тобто для мене успішна команда npm run build сама по собі ще не є достатньою перевіркою.

Quality: окремо перевіряю код до запуску тестів

YAML - Quality jobs у цьому pipeline
phpstan:  stage: quality   script:    - './vendor/bin/phpstan analyse --memory-limit=1G --no-progress'  php_cs_fixer:  stage: quality   script:    - >      ./vendor/bin/php-cs-fixer fix      --dry-run      --diff      --verbose      --using-cache=no

На цьому варіанті pipeline у quality stage в мене були PHPStan і PHP-CS-Fixer. PHPStan ловив проблеми static analysis, а formatter перевіряв стиль коду без автоматичного переписування файлів у CI.

Пізніше я прибрала PHP-CS-Fixer і перейшла на Laravel Pint. Сам принцип pipeline від цього не змінився: formatting check усе одно залишається окремою quality-перевіркою, просто інструмент став іншим.

Про цю міграцію я окремо написала у статті Laravel Pint уже був у проєкті: чому я вирішила прибрати PHP-CS-Fixer.

Tests: я не складаю всі тести в один job

YAML - Окремі test suites
tests_unit:  stage: test   script:    - >      php artisan test      --testsuite=Unit      --log-junit=reports/unit.xml tests_integration:  stage: test   script:    - >      php -d memory_limit=768M artisan test      --testsuite=Integration      --log-junit=reports/integration.xml tests_feature:  stage: test   script:    - >      php -d memory_limit=768M artisan test      --testsuite=Feature      --log-junit=reports/feature.xml

Unit, Integration і Feature tests у мене запускаються окремими jobs.

Для Integration і Feature я піднімаю MySQL 8.0.46, чекаю, поки база стане доступною, а потім виконую php artisan migrate:fresh --force.

Unit tests не потребують цього MySQL setup, тому немає сенсу змушувати їх чекати зайву інфраструктуру.

Ще один плюс такого поділу для мене - коли pipeline падає, я одразу бачу, в якій групі тестів проблема.

Я не запускаю всі jobs після кожної зміни

Одна з речей, яку я поступово додала в pipeline:

- rules: changes

Якщо я змінюю тільки frontend, немає сенсу без причини запускати весь backend test flow. Якщо змінюється PHP runtime, Composer dependencies, application code або test infrastructure - запускаються відповідні backend jobs.

Для release Merge Request у main або після зміни самого .gitlab-ci.yml перевірка ширша. А коли мені потрібна повна ручна перевірка незалежно від changed files, для цього є RUN_ALL.

Для мене хороший pipeline не той, який запускає якомога більше jobs. Він має запускати саме ті перевірки, які потрібні для конкретної зміни.

Якимів Альона

Deploy: development автоматичний, production - ручний

YAML - Development і production deploy
deploy_development:  rules:    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "development"'      when: on_success     - when: never  deploy_production:  rules:    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "main"'      when: manual      allow_failure: false     - when: never

Для development я дозволяю автоматичний deploy після успішного push pipeline у development. Для production у main я залишила ручний запуск.

Сам deployment у мене робить значно більше, ніж git pull:

  • збирає browser і SSR assets;

  • передає їх на сервер;

  • встановлює production Composer dependencies;

  • запускає migrations;

  • Laravel optimize;

  • restart queue;

  • перезапускає Reverb і SSR через Supervisor;

  • а в кінці виконує healthcheck.

Окремо мені довелося розібратися з доступом Composer до приватних GitLab dependencies.

Про схожу проблему з Composer, GitLab, Docker і SSH я вже писала в окремій статті.

Тому для мене CI/CD - це не просто deploy

Коли дивишся тільки на останній stage, легко подумати, що CI/CD потрібен головним чином для автоматичного deployment. У моєму проєкті найбільшу цінність я бачу раніше.

Pipeline поетапно відповідає мені: чи збирається проєкт, чи проходять quality checks, чи зелені тести і тільки потім - чи можна рухатися до deploy.

Саме тому я не намагалася зробити один великий job з десятком команд. Мені набагато зручніше бачити build, quality, test і deploy окремо та одразу розуміти, на якому етапі щось пішло не так.