Laravel GitHub Actions Workflow Template
Last updated
This is a working .github/workflows/ci.yml for a Laravel app tested against MySQL. It installs PHP with setup-php, caches Composer downloads, creates the .env, migrates the database and runs Pint and the test suite.
The workflow
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 20
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: laravel_test
ports:
- 3306:3306
options: >-
--health-cmd "mysqladmin ping -h 127.0.0.1 -proot"
--health-interval 5s
--health-timeout 5s
--health-retries 20
env:
APP_ENV: testing
DB_CONNECTION: mysql
DB_HOST: 127.0.0.1
DB_PORT: 3306
DB_DATABASE: laravel_test
DB_USERNAME: root
DB_PASSWORD: root
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: mbstring, pdo_mysql, bcmath
coverage: none
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: composer-${{ runner.os }}-${{ hashFiles('composer.lock') }}
restore-keys: |
composer-${{ runner.os }}-
- run: composer install --no-interaction --prefer-dist --no-progress
- run: cp .env.example .env
- run: php artisan key:generate
- run: vendor/bin/pint --test
- run: php artisan migrate --force
- run: php artisan test --parallel
What the non-obvious parts do
shivammathur/setup-php@v2installs the PHP version and extensions you list.coverage: noneturns off Xdebug and PCOV, which makes tests faster; setcoverage: pcovonly when you collect coverage.- The Composer cache steps save Composer's download cache, not
vendor/. The directory is asked from Composer itself, so the path is right on any runner. The key is the hash ofcomposer.lock. - MySQL health check uses
127.0.0.1and the root password. MySQL takes several seconds to initialise, so the long retry count avoids early connection errors. DB_HOST: 127.0.0.1is deliberate. Withlocalhost, PHP's MySQL driver tries a Unix socket that does not exist and fails.cp .env.example .envandkey:generategive the app a valid environment andAPP_KEYwithout any real secrets.pint --testreports style problems without rewriting files, so CI fails instead of silently fixing.artisan test --parallelneeds thebrianium/paratestpackage, and creates a database per process. Drop the flag if you do not use it.
Variants
PHP version matrix
Add matrix.php: ["8.2", "8.3", "8.4"] and use ${{ matrix.php }}. Keep versions quoted. See matrix builds.
Frontend assets
Add actions/setup-node@v4 with cache: npm, then npm ci and npm run build before the tests if feature tests render Vite assets.
Postgres or SQLite
Swap the service for postgres:16 with pg_isready and set DB_CONNECTION: pgsql. In-memory SQLite needs no service and is faster, but hides engine differences.
Deploy job
Add a second job with needs: test on main only, and use OIDC for cloud access.
Run it on faster machines
Change one line:
runs-on: [self-hosted, Linux, compiler-ci]
The job runs on a fresh 8 vCPU machine from compiler.dev, with automatic fallback to your original runner. Parallel tests make good use of the extra cores. See speed up Laravel on GitHub Actions and pricing.
FAQ
Should I cache the vendor directory?
Cache Composer's download cache instead. composer install from a warm cache is quick and always consistent with composer.lock.
Why does MySQL refuse connections at the start?
It is still starting. Keep the health check and use 127.0.0.1.
How do I run Dusk browser tests?
Dusk needs Chrome and a running app server. Run it in a separate job so slow browser tests do not delay unit tests, and upload screenshots as an artifact.
Can I use Sail in CI?
You can, but it adds Docker start-up time. Plain setup-php plus service containers is usually faster.
Related
- Speed Up Laravel in GitHub ActionsSpeed up Laravel CI in GitHub Actions: Composer cache, Pest or PHPUnit --parallel, a tuned MySQL service, and Vite builds. Working YAML to copy.
- .NET GitHub Actions Workflow TemplateCopy-paste GitHub Actions CI for .NET with NuGet caching, restore, build and test steps, a SQL Server service option, concurrency and permissions.
- Django GitHub Actions Workflow TemplateCopy-paste GitHub Actions CI for Django with a Postgres service container, pip caching, migrations check and tests, plus concurrency and permissions.
- Docker Build GitHub Actions Workflow TemplateCopy-paste GitHub Actions workflow to build Docker images with Buildx, GitHub cache layers and a push to GHCR on main, with minimal permissions.