Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124


Коли працюєш над Laravel-проєктом у команді, рано чи пізно доходить до питання автоматизації. Хочеться, щоб тести запускалися самі, код перевірявся на стандарти, а деплоймент відбувався без ручного втручання. GitHub Actions — це саме той інструмент, який робить CI/CD доступним для будь-якого проєкту. Розповім, як я налаштовую GitHub Actions для Laravel та PHP проєктів, і чому ця інтеграція стала для мене стандартом.
Ринок CI/CD-інструментів величезний: Jenkins, GitLab CI, CircleCI, Travis CI та десятки інших. Але GitHub Actions має кілька переваг, які роблять його особливо зручним для Laravel розробки. По-перше, якщо твій код вже на GitHub, то все в одному місці — не потрібно налаштовувати зовнішні сервіси. По-друге, безкоштовні 2000 хвилин на місяць для приватних репозиторіїв (для публічних взагалі необмежено). По-третє, величезний marketplace готових action’ів, які економлять купу часу.
Звісно, є й недоліки. Налаштування може здаватися складнішим за GUI-інтерфейси інших платформ. Іноді виникають затримки у черзі, особливо на безкоштовних runner’ах. Та й дебаг workflows через YAML не завжди приємний. Але для більшості Laravel проєктів переваги значно переважують мінуси.
Почнемо з найпростішого, але робочого 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 і запускає тести. Це базовий скелет, який можна розширювати під свої потреби.
Більшість 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 повністю готовий до роботи перед запуском міграцій. Це критично, бо інакше можна отримати помилки підключення.


Кожного разу встановлювати всі 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 хвилини.
Якщо використовуєш 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-середовищі.
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 зі змінами. Але будь обережний: не всі автофікси коректні, особливо у складних випадках.
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). Матриця можна розширювати: додати різні версії бази даних, операційні системи тощо. Але не переборщуй — кожна додаткова комбінація збільшує час виконання та споживання хвилин.
За замовчуванням 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.
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. Вони видимі в логах, але зручніші для налаштувань, які не є секретними.
Після того як тести пройшли, логічний крок — автоматичний деплоймент. Ось базовий приклад деплою на сервер через 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 }}Простий 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. Якщо щось пішло не так, можна швидко відкотитися на попередню версію.
Корисно отримувати повідомлення про результати 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-нотифікації.
Коли у тебе кілька 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 я зіткнувся з купою підводних каменів. Одна з найчастіших проблем — таймаути при встановленні залежностей. Рішення: використовуй 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 змінив мій підхід до розробки. Тепер кожен commit автоматично тестується, код перевіряється на якість, а деплоймент відбувається без моєї участі. Це не просто автоматизація — це впевненість, що нічого не зламається в production. Звісно, початкове налаштування займає час. Треба продумати структуру workflow, налаштувати секрети, відтестувати деплой. Але воно того варте.
Мій порадник: починай з простого. Спочатку додай базові тести, потім code quality checks, і лише після цього автоматичний деплой. Не намагайся охопити все одразу — це призведе до складних, непідтримуваних workflow. Використовуй готові action’и з marketplace, але розумій, що вони роблять. І найголовніше — тестуй workflow на тестових гілках перед тим, як застосовувати до production.
GitHub Actions для Laravel — це не просто CI/CD. Це філософія: автоматизуй рутину, фокусуйся на коді, довіряй процесу. Якщо ще не спробував — самий час почати. Створи простий workflow для тестів, подивись, як воно працює, і поступово розширюй функціонал. Через місяць не уявиш, як працював без цього.
Якщо вас цікавлять інші аспекти Laravel розробки та автоматизації, рекомендую переглянути ці статті: