GitHub Actions CI für Laravel + Statamic: 5-Minuten-Setup
Eine ci.yml, die Pint, PHPUnit oder Pest, Stache-Warmup und ein Build-Artefakt in unter drei Minuten durchzieht. Bereit für Laravel-Cloud-Deploys.
Warum CI von Tag 1
Die Pipeline ist kein Optional, sondern Teil des Stacks
In jedem Projekt, das wir aufsetzen, ist die CI vor dem ersten Feature da. Nicht aus Dogma, sondern weil sich Pint, PHPUnit und ein erfolgreich gebauter Statamic-Stache schon beim ersten Pull-Request lohnen. Wer die Pipeline später nachzieht, schiebt eine wachsende Schuldenliste vor sich her.
Schnell genug, um nicht zu nerven
Eine CI, die fünf Minuten braucht, wird bei jedem Commit gepusht. Eine CI, die zwanzig Minuten braucht, wird umgangen. Wir zielen auf unter drei Minuten Gesamtlaufzeit - mit Composer-Cache, NPM-Cache und parallelem Lint-/Test-Job ist das in den meisten Projekten realistisch.
Statamic-Stache als zusätzlicher Smoke-Test
Statamic 6 hält den Index aller Entries im Stache. Wenn ein Blueprint kaputt ist oder ein Entry verwaisten YAML-Code enthält, fällt das spätestens beim Warmup auf. Wir lassen den Stache deshalb in CI bauen - das ist der schnellste Content-Smoke-Test, den es für Statamic gibt.
Trigger und Concurrency
Wir laufen auf push und pull_request, beschränkt auf main, develop und Feature-Branches mit feature/-Prefix. Concurrency-Gruppe pro Branch verhindert, dass alte Runs neue blockieren - wer fünfmal hintereinander pusht, lässt nur den letzten Run weiterlaufen.
PHP-Version pinnen, nicht matrixen
Bibliotheken testen über mehrere PHP-Versionen - Applikationen nicht. Wir pinnen auf PHP 8.5, exakt wie Production. Wenn Laravel 14 erscheint und 8.6 bringt, hebt ein einziger Edit die Version. Das spart pro Run wertvolle Minuten und macht Debugging deterministisch.
name: CI
on:
push:
branches: [main, develop, 'feature/**']
pull_request:
branches: [main, develop]
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Pint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
coverage: none
tools: composer:v2
- name: Cache Composer
uses: actions/cache@v4
with:
path: ~/.composer/cache
key: composer-${{ hashFiles('composer.lock') }}
- run: composer install --prefer-dist --no-interaction --no-progress
- run: vendor/bin/pint --test --format=github
test:
name: PHPUnit + Stache
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: testing
POSTGRES_USER: testing
POSTGRES_PASSWORD: testing
ports: ['5432:5432']
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-retries 10
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.5'
extensions: intl, pdo_pgsql, redis
coverage: none
- name: Cache Composer
uses: actions/cache@v4
with:
path: ~/.composer/cache
key: composer-${{ hashFiles('composer.lock') }}
- run: composer install --prefer-dist --no-interaction --no-progress
- run: cp .env.ci .env
- run: php artisan key:generate --ansi
- run: php artisan migrate --force --no-interaction
- run: php artisan stache:warm
- run: php artisan test --compact --parallel
build:
name: Vite Build
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: vite-build
path: public/build
retention-days: 7
CI im Bestandsprojekt nachrüsten?
Wir analysieren Ihr Repo, schreiben Pint-Regeln passend zum Code-Stand und liefern die Pipeline lauffähig - in der Regel an einem Tag.
Jobs im Detail
Lint, Test und Build genauer angesehen
Drei Jobs, drei Aufgaben. Jeder hat einen klaren Fail-Fast-Pfad, damit Sie nicht acht Minuten auf einen Build warten, bevor Sie sehen, dass Pint sich beschwert hat.
Pint mit GitHub-Format
vendor/bin/pint --test --format=github gibt Annotations direkt im Pull-Request aus - Reviewer sehen die fehlerhafte Zeile inline, ohne Logs zu öffnen. Lokal läuft Pint im Pre-Commit-Hook, in CI nur als Sicherheitsnetz. Wer den Hook umgeht, wird auf GitHub geblockt.
Tests parallel, Stache als Smoke
php artisan test --parallel halbiert in den meisten Projekten die Test-Laufzeit. Davor läuft php artisan stache:warm gegen die committeten Statamic-Inhalte - wenn ein Blueprint-Handle fehlt oder ein Entry kaputtes YAML hat, scheitert dieser Schritt schneller als jeder Feature-Test es könnte.
APP_ENV=testing
APP_DEBUG=true
APP_URL=http://localhost
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=testing
DB_USERNAME=testing
DB_PASSWORD=testing
CACHE_STORE=array
QUEUE_CONNECTION=sync
SESSION_DRIVER=array
MAIL_MAILER=log
STATAMIC_STACHE_WATCHER=false
STATAMIC_LICENSE_KEY=${{ secrets.STATAMIC_LICENSE_KEY }}
Composer- und NPM-Cache richtig schlüsseln
Cache-Keys hängen am Hash der jeweiligen Lock-Datei - composer.lock für Composer, package-lock.json für npm. So wird der Cache invalidiert, sobald Dependencies sich ändern, und bleibt sonst stabil. Sub-Minute-Builds bei reinen Code-Changes sind damit normal, nicht außergewöhnlich.
Deploy als nachgelagerter Job
Den Deploy fügen wir als separaten Job mit needs: [build] hinzu, der nur auf main läuft. Auf Laravel Cloud reicht ein curl auf den Deploy-Webhook - die Cloud zieht sich den Build selbst. Secrets gehören in die Repository-Secrets, nicht in die Datei.
deploy:
name: Deploy to Laravel Cloud
runs-on: ubuntu-latest
needs: [build]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Trigger Laravel Cloud deploy hook
run: |
curl -fsSL -X POST \
-H "Authorization: Bearer ${{ secrets.LARAVEL_CLOUD_TOKEN }}" \
"${{ secrets.LARAVEL_CLOUD_DEPLOY_HOOK }}"
- name: Smoke-Test nach Deploy
run: |
for i in 1 2 3 4 5; do
if curl -fsSL "${{ secrets.APP_URL }}/health" >/dev/null; then
echo "Deploy ok"
exit 0
fi
sleep 10
done
echo "Health check failed"
exit 1
Artikel teilen
Hat das geholfen? Wir machen Audit, Workshop oder Coaching auch live
Pakete direkt im Shop. Unser Standard-Tagessatz liegt bei 1.500 € netto.