Безшовна інтеграція GitHub Actions у Laravel/PHP проєкти

Коли працюєш над Laravel-проєктом у команді, рано чи пізно доходить до питання автоматизації. Хочеться, щоб тести запускалися самі, код перевірявся на стандарти, а деплоймент відбувався без ручного втручання. GitHub Actions — це саме той інструмент, який робить CI/CD доступним для будь-якого проєкту. Розповім, як я налаштовую GitHub Actions для Laravel та PHP проєктів, і чому ця інтеграція стала для мене стандартом.

Чому саме GitHub Actions для Laravel проєктів

Ринок CI/CD-інструментів величезний: Jenkins, GitLab CI, CircleCI, Travis CI та десятки інших. Але GitHub Actions має кілька переваг, які роблять його особливо зручним для Laravel розробки. По-перше, якщо твій код вже на GitHub, то все в одному місці — не потрібно налаштовувати зовнішні сервіси. По-друге, безкоштовні 2000 хвилин на місяць для приватних репозиторіїв (для публічних взагалі необмежено). По-третє, величезний marketplace готових action’ів, які економлять купу часу.

Звісно, є й недоліки. Налаштування може здаватися складнішим за GUI-інтерфейси інших платформ. Іноді виникають затримки у черзі, особливо на безкоштовних runner’ах. Та й дебаг workflows через YAML не завжди приємний. Але для більшості Laravel проєктів переваги значно переважують мінуси.

Базовий workflow для Laravel: GitHub Actions крок за кроком

Почнемо з найпростішого, але робочого workflow. Створюємо файл .github/workflows/laravel.yml у корені репозиторію:

name: Laravel CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  laravel-tests:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4
    
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.2'
        extensions: mbstring, xml, ctype, json, bcmath, pdo_mysql
        coverage: none
    
    - name: Copy .env
      run: php -r "file_exists('.env') || copy('.env.example', '.env');"
    
    - name: Install Dependencies
      run: composer install --no-interaction --prefer-dist --optimize-autoloader
    
    - name: Generate key
      run: php artisan key:generate
    
    - name: Run tests
      run: php artisan testCode language: JavaScript (javascript)

Що тут відбувається? Workflow запускається при push або pull request до гілок main та develop. Він налаштовує PHP 8.2 з необхідними розширеннями, копіює .env файл, встановлює залежності через Composer і запускає тести. Це базовий скелет, який можна розширювати під свої потреби.

Додавання бази даних до GitHub Actions workflow

Більшість Laravel проєктів працюють з базою даних, тому тести теж потребують БД. GitHub Actions дозволяє запускати сервіси як Docker-контейнери:

jobs:
  laravel-tests:
    runs-on: ubuntu-latest
    
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: password
          MYSQL_DATABASE: testing
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
    
    steps:
    # ... попередні кроки
    
    - name: Prepare Laravel Application
      run: |
        cp .env.example .env
        php artisan key:generate
        php artisan migrate --force
      env:
        DB_CONNECTION: mysql
        DB_HOST: 127.0.0.1
        DB_PORT: 3306
        DB_DATABASE: testing
        DB_USERNAME: root
        DB_PASSWORD: passwordCode language: PHP (php)

Секція services запускає MySQL-контейнер паралельно з основним job’ом. Параметр options із health-check гарантує, що MySQL повністю готовий до роботи перед запуском міграцій. Це критично, бо інакше можна отримати помилки підключення.

Візуалізація GitHub Actions workflow для Laravel
Схема роботи GitHub Actions з Laravel проєктом

Кешування залежностей: оптимізація GitHub Actions для PHP

Кожного разу встановлювати всі Composer-залежності — це марнування часу та ресурсів. GitHub Actions підтримує кешування, яке прискорює workflow на 50-70%:

- name: Get Composer Cache Directory
  id: composer-cache
  run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT

- name: Cache Composer dependencies
  uses: actions/cache@v3
  with:
    path: ${{ steps.composer-cache.outputs.dir }}
    key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
    restore-keys: |
      ${{ runner.os }}-composer-

- name: Install Dependencies
  run: composer install --no-interaction --prefer-dist --optimize-autoloaderCode language: PHP (php)

Кеш створюється на основі хешу composer.lock, тому при зміні залежностей він автоматично інвалідується. Параметр restore-keys дозволяє використовувати частковий кеш навіть при незначних змінах у залежностях. Я помітив, що на середньому проєкті це зменшує час виконання з 3-4 хвилин до 1-1.5 хвилини.

Кешування Node.js залежностей для Laravel Mix

Якщо використовуєш Laravel Mix або Vite для фронтенду, варто кешувати і npm-залежності:

- name: Setup Node.js
  uses: actions/setup-node@v4
  with:
    node-version: '18'
    cache: 'npm'

- name: Install NPM dependencies
  run: npm ci

- name: Build assets
  run: npm run buildCode language: JavaScript (javascript)

Action setup-node автоматично кешує npm-пакети, якщо вказати параметр cache: 'npm'. Використовуй npm ci замість npm install — він швидший і детерміністичніший у CI-середовищі.

Code quality checks: інтеграція PHP CS Fixer та PHPStan

GitHub Actions не лише для тестів. Ти можеш автоматизувати перевірку якості коду. Я завжди додаю PHP CS Fixer для стилю коду та PHPStan для статичного аналізу:

jobs:
  code-quality:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '8.2'
        tools: php-cs-fixer, phpstan
    
    - name: Install dependencies
      run: composer install --no-interaction
    
    - name: Run PHP CS Fixer
      run: vendor/bin/php-cs-fixer fix --dry-run --diff --verbose
    
    - name: Run PHPStan
      run: vendor/bin/phpstan analyse --memory-limit=2GCode language: JavaScript (javascript)

Параметр --dry-run у PHP CS Fixer не змінює файли, а лише показує, що треба виправити. Якщо хочеш автоматично фіксити код, можна створити окремий workflow, який робить commit зі змінами. Але будь обережний: не всі автофікси коректні, особливо у складних випадках.

Матрична стратегія: тестування на різних версіях PHP

Laravel підтримує кілька версій PHP, і добре б перевірити, що код працює на всіх. GitHub Actions дозволяє запускати тести паралельно на різних версіях за допомогою матриці:

jobs:
  tests:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        php-version: ['8.1', '8.2', '8.3']
        laravel-version: ['10.*', '11.*']
        exclude:
          - php-version: '8.1'
            laravel-version: '11.*'
    
    name: PHP ${{ matrix.php-version }} - Laravel ${{ matrix.laravel-version }}
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ matrix.php-version }}
    
    - name: Install Laravel
      run: composer require "laravel/framework:${{ matrix.laravel-version }}" --no-update
    
    - name: Install dependencies
      run: composer update --prefer-dist --no-interaction
    
    - name: Run tests
      run: php artisan testCode language: JavaScript (javascript)

Це створює 5 паралельних job’ів (3 версії PHP × 2 версії Laravel, мінус одна комбінація через exclude). Матриця можна розширювати: додати різні версії бази даних, операційні системи тощо. Але не переборщуй — кожна додаткова комбінація збільшує час виконання та споживання хвилин.

Fail-fast vs continue-on-error: стратегії обробки помилок

За замовчуванням GitHub Actions зупиняє всі job’и матриці при першій помилці (fail-fast: true). Іноді це не те, що потрібно:

strategy:
  fail-fast: false  # продовжувати навіть при помилці
  matrix:
    php-version: ['8.1', '8.2', '8.3']

steps:
  - name: Run experimental tests
    continue-on-error: true  # не провалювати job при помилці
    run: php artisan test --testsuite=experimentalCode language: PHP (php)

Параметр fail-fast: false корисний, коли хочеш побачити результати всіх комбінацій. continue-on-error на рівні кроку дозволяє позначити експериментальні тести, які можуть падати без провалу всього workflow.

Секрети та змінні середовища у GitHub Actions

Laravel проєкти часто потребують API-ключів, токенів та інших чутливих даних. GitHub Actions має вбудовану систему секретів:

steps:
  - name: Run tests with secrets
    env:
      APP_KEY: ${{ secrets.APP_KEY }}
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      STRIPE_SECRET: ${{ secrets.STRIPE_SECRET }}
    run: php artisan testCode language: JavaScript (javascript)

Секрети додаються через Settings → Secrets and variables → Actions у репозиторії. Вони ніколи не відображаються в логах (GitHub автоматично маскує їх). Важливий момент: секрети недоступні у pull request’ах з fork’ів — це захист від витоку даних.

Для менш чутливих змінних можна використовувати environment variables на рівні репозиторію або workflow. Вони видимі в логах, але зручніші для налаштувань, які не є секретними.

Deployment через GitHub Actions: автоматизація розгортання Laravel

Після того як тести пройшли, логічний крок — автоматичний деплоймент. Ось базовий приклад деплою на сервер через SSH:

jobs:
  deploy:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    needs: tests  # запускається лише після успішних тестів
    
    steps:
    - name: Deploy to production
      uses: appleboy/ssh-action@v1.0.0
      with:
        host: ${{ secrets.SERVER_HOST }}
        username: ${{ secrets.SERVER_USER }}
        key: ${{ secrets.SSH_PRIVATE_KEY }}
        script: |
          cd /var/www/myapp
          git pull origin main
          composer install --no-dev --optimize-autoloader
          php artisan migrate --force
          php artisan config:cache
          php artisan route:cache
          php artisan view:cache
          php artisan queue:restart
          sudo systemctl reload php8.2-fpmCode language: PHP (php)

Умова if гарантує, що деплой відбувається лише з головної гілки при push (не при pull request). Параметр needs: tests створює залежність — деплой не запуститься, якщо тести впали. SSH-ключ зберігається в секретах, що безпечніше за паролі.

Для деплою в Laravel Forge, Envoyer чи інші платформи є готові action’и. Наприклад, для Forge:

- name: Deploy to Laravel Forge
  uses: jbrooksuk/laravel-forge-action@v1.0.3
  with:
    trigger_url: ${{ secrets.FORGE_TRIGGER_URL }}

Zero-downtime deployment: стратегія розгортання без простоїв

Простий git pull створює короткий простій під час встановлення залежностей та міграцій. Для production краще використовувати symlink-strategy:

script: |
  cd /var/www
  # Клонуємо в нову директорію з timestamp
  RELEASE_DIR="releases/$(date +%Y%m%d%H%M%S)"
  git clone --depth 1 -b main git@github.com:user/repo.git $RELEASE_DIR
  cd $RELEASE_DIR
  
  # Встановлюємо залежності
  composer install --no-dev --optimize-autoloader
  
  # Ділимося storage та .env
  rm -rf storage
  ln -s /var/www/storage storage
  ln -s /var/www/.env .env
  
  # Запускаємо міграції
  php artisan migrate --force
  php artisan cache:clear
  
  # Атомарно переключаємо symlink
  ln -sfn /var/www/$RELEASE_DIR /var/www/current
  
  # Видаляємо старі релізи (залишаємо останні 3)
  cd /var/www/releases && ls -t | tail -n +4 | xargs rm -rfCode language: PHP (php)

Цей підхід створює нову директорію для кожного релізу, а потім атомарно переключає symlink. Якщо щось пішло не так, можна швидко відкотитися на попередню версію.

Нотифікації та звіти: інтеграція зі Slack та іншими сервісами

Корисно отримувати повідомлення про результати workflow. Інтеграція зі Slack — класика:

- name: Notify Slack on success
  if: success()
  uses: slackapi/slack-github-action@v1.24.0
  with:
    webhook-url: ${{ secrets.SLACK_WEBHOOK }}
    payload: |
      {
        "text": "✅ Deploy to production successful!",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Deploy successful*\nCommit: ${{ github.sha }}\nAuthor: ${{ github.actor }}"
            }
          }
        ]
      }

- name: Notify Slack on failure
  if: failure()
  uses: slackapi/slack-github-action@v1.24.0
  with:
    webhook-url: ${{ secrets.SLACK_WEBHOOK }}
    payload: |
      {
        "text": "❌ Deploy to production failed!",
        "blocks": [
          {
            "type": "section",
            "text": {
              "type": "mrkdwn",
              "text": "*Deploy failed*\nCheck logs: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
            }
          }
        ]
      }Code language: JavaScript (javascript)

Умови if: success() та if: failure() дозволяють відправляти різні повідомлення залежно від результату. Webhook для Slack створюється в налаштуваннях workspace. Аналогічно можна інтегрувати Discord, Telegram чи email-нотифікації.

Реюзабельні workflows: DRY принцип для GitHub Actions

Коли у тебе кілька Laravel проєктів, копіювати один і той же YAML стає нудно. GitHub дозволяє створювати reusable workflows:

# .github/workflows/reusable-laravel-tests.yml
name: Reusable Laravel Tests

on:
  workflow_call:
    inputs:
      php-version:
        required: true
        type: string
      laravel-version:
        required: false
        type: string
        default: '*'

jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ inputs.php-version }}
      # ... решта кроків

# Використання в іншому workflow
jobs:
  call-tests:
    uses: ./.github/workflows/reusable-laravel-tests.yml
    with:
      php-version: '8.2'
      laravel-version: '10.*'Code language: PHP (php)

Це дозволяє централізовано керувати спільною логікою та легко оновлювати її для всіх проєктів. Можна навіть створити окремий репозиторій зі спільними workflows і посилатися на них через user/repo/.github/workflows/workflow.yml@main.

Типові проблеми та їх вирішення при інтеграції GitHub Actions

За роки роботи з GitHub Actions я зіткнувся з купою підводних каменів. Одна з найчастіших проблем — таймаути при встановленні залежностей. Рішення: використовуй composer install --no-scripts і запускай post-install скрипти окремо. Іноді Composer висить на інтерактивних промптах — додай --no-interaction.

Друга проблема — нестабільні тести, які іноді падають. Не ігноруй їх! Використовуй php artisan test --parallel з обережністю — паралельні тести можуть викривати race conditions. Краще виправити тести, ніж додавати continue-on-error.

Третя — перевитрата хвилин на безкоштовному плані. Оптимізуй workflow: використовуй кешування, не запускай job’и для незначних змін (наприклад, оновлень README), додай paths фільтри до тригерів. Наприклад:

on:
  push:
    branches: [ main ]
    paths-ignore:
      - '**.md'
      - 'docs/**'Code language: JavaScript (javascript)

Підсумки: GitHub Actions як частина Laravel development workflow

GitHub Actions змінив мій підхід до розробки. Тепер кожен commit автоматично тестується, код перевіряється на якість, а деплоймент відбувається без моєї участі. Це не просто автоматизація — це впевненість, що нічого не зламається в production. Звісно, початкове налаштування займає час. Треба продумати структуру workflow, налаштувати секрети, відтестувати деплой. Але воно того варте.

Мій порадник: починай з простого. Спочатку додай базові тести, потім code quality checks, і лише після цього автоматичний деплой. Не намагайся охопити все одразу — це призведе до складних, непідтримуваних workflow. Використовуй готові action’и з marketplace, але розумій, що вони роблять. І найголовніше — тестуй workflow на тестових гілках перед тим, як застосовувати до production.

GitHub Actions для Laravel — це не просто CI/CD. Це філософія: автоматизуй рутину, фокусуйся на коді, довіряй процесу. Якщо ще не спробував — самий час почати. Створи простий workflow для тестів, подивись, як воно працює, і поступово розширюй функціонал. Через місяць не уявиш, як працював без цього.

Рекомендуємо прочитати

Якщо вас цікавлять інші аспекти Laravel розробки та автоматизації, рекомендую переглянути ці статті:

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *