How do you build CI/CD and safe rollbacks for Laravel apps?
Laravel Developer
answer
A production-grade Laravel CI/CD compiles a single, signed image per commit, then promotes it through environments. CI runs Pint, PHPStan/Psalm, security scans, Pest/PHPUnit, Testcontainers integration (MySQL/PostgreSQL/Redis), and optional Dusk E2E. Database changes follow expand–migrate–contract to keep rollbacks safe. Deploy with blue/green or rolling releases (Kubernetes/Deployer), warm caches (config:cache, route:cache), drain queues, and use feature flags (Pennant). Rollback is re-pinning the last good image and reverting config in Git.
Long Answer
Delivering Laravel reliably is about repeatability and reversibility. A strong pipeline standardizes how code becomes an immutable artifact, validates it with layered tests, evolves the database without risk, and ships via zero-downtime strategies that are observable and easy to roll back.
1) Build once, promote everywhere
Use a multi-stage Dockerfile. Stage 1: composer install --no-dev --prefer-dist --no-interaction with a cached Composer directory; run Pint for style and PHPStan/Psalm for static analysis. Stage 2: copy optimized vendor, compile assets (Vite), and warm framework caches: php artisan config:cache, route:cache, view:cache, event:cache. Final image: PHP-FPM (Opcache enabled) with a slim base; pair with NGINX or use a single image if needed. Generate an SBOM (CycloneDX) and sign the image (cosign). Tag by Git SHA and semantic version. This artifact is the only thing promoted between dev, staging, and prod.
2) Test pyramid and quality gates
Run fast, high-signal checks on every pull request.
- Static gates: Pint (style), PHPStan/Psalm (level tuned to team), composer audit/Snyk, secret scanning.
- Unit tests: Pest/PHPUnit covering services, policies, form requests, and jobs; mock boundaries intentionally.
- Integration tests: start Testcontainers for MySQL/PostgreSQL and Redis; run Laravel migrations against a real DB, seed factories, and assert repository/transaction behavior. Cover mail, events, queues, and notifications.
- HTTP/Contract tests: verify request validation and resource transformers; publish OpenAPI (laravel-openapi, l5-swagger) and run schema diffs or Pact provider verification to avoid client breakage.
- E2E (optional): Dusk or Playwright against an ephemeral stack (Docker Compose/Kubernetes namespace) for golden paths: signup, login, checkout, subscription.
Gate merges on zero high-severity vulns and coverage thresholds for critical modules (auth, payments, orders).
3) Safe migrations and data evolution
Laravel migrations must never trap you. Follow expand–migrate–contract:
- Expand: add nullable columns/tables, new indexes, or backfill structures that the old code can ignore.
- Migrate: deploy code that dual-writes or reads both shapes; run backfill jobs (queued, idempotent) to populate new fields. Monitor progress with metrics and logs.
- Contract: only after verification, drop legacy columns and remove dual-write.
Keep migrations idempotent, avoid long table locks (use concurrently style indexes where supported), and schedule heavy operations off peak. This preserves rollback: you can revert application code while the expanded schema remains compatible.
4) Zero-downtime deployment strategies
Pick based on platform:
- Blue/green: run green alongside blue. Warm caches, run smoke tests (/healthz, /readyz, synthetic HTTP checks), pre-migrate DB (expand), and flip the load balancer. Roll back by switching traffic back to blue.
- Rolling (Kubernetes/Octane): gradually replace pods, honoring readiness/liveness probes and preStop to drain connections.
- Queues/Horizon: set --graceful or pause Horizon to finish in-flight jobs; resume after cutover. For broadcasting, ensure sticky sessions or stateless JWT as appropriate.
5) Environment and secrets management
Treat config as code. Store Kubernetes/Helm/Kustomize manifests in a GitOps repo (Argo CD/Flux). Use .env templates committed to Git, but store real secrets in Vault/parameter store or sealed-secrets. Keep environment parity—only values differ. For assets, publish hashed files and a CDN; avoid environment-specific builds by reading env at runtime where practical.
6) Observability and promotion gates
Instrument with OpenTelemetry (Laravel OTel SDK or bridge). Emit traces with route, controller, DB query spans; expose RED metrics (rate, errors, duration) and queue lag. Centralize logs with request IDs and user IDs (non-PII). During canary/blue-green, gate promotion on SLOs: error rate <1%, API p95 latency within budget, queue depth healthy, CPU/memory below saturation. If thresholds breach, auto-pause or auto-rollback.
7) Feature flags and blast-radius control
Decouple deploy from release using Laravel Pennant or a third-party service (Unleash, LaunchDarkly). Ship dark, enable by cohort, tenant, or percentage. Maintain kill switches for risky code paths (new payment integration, heavy query). Clean up stale flags on a cadence to avoid configuration debt.
8) Rollback strategies
Never rebuild under pressure.
- Image rollback: re-pin the Deployment to the last known good, signed image.
- Config rollback: revert the GitOps commit; controllers reconcile state.
- Selective disable: turn off the offending feature flag while keeping the deploy.
- Data safety: because you followed expand–migrate–contract, rolling back code is safe even mid-backfill. Only contract after stability.
Bottom line: Laravel CI/CD succeeds when builds are immutable, tests are layered and realistic, database changes are reversible, releases are zero-downtime, and rollbacks are boring and fast.
Table
Common Mistakes
- Building different images per environment; breaks parity and rollback.
- Relying only on E2E, skipping integration tests with real DB/Redis.
- Destructive migrations first (dropping columns) that make rollback impossible.
- No queue draining; deployments kill in-flight jobs or duplicate work.
- Clearing caches in the wrong order, serving mixed views/config.
- Shipping breaking API changes without contract verification.
- Canary without SLO gates, discovering issues at 100% traffic.
- Rebuilding to roll back instead of re-pinning the last signed image.
- Leaving feature flags forever, creating configuration debt and ambiguity.
Sample Answers
Junior:
“I use Pest/PHPUnit for tests, run Pint and PHPStan, and build a Docker image. We deploy with blue/green and warm caches. If errors spike, I switch traffic back and investigate.”
Mid:
“My pipeline produces a signed, immutable image and runs unit, integration (Testcontainers for MySQL/Redis), and OpenAPI contract checks. Migrations follow expand–migrate–contract with backfills. I deploy with rolling updates and readiness probes, pause Horizon, and gate promotion on p95 latency and error rate. Rollback re-pins the previous image and disables the feature flag.”
Senior:
“I standardize build-once-promote with SBOM and cosign, enforce Pint/PHPStan, Pest, contract verification, and Dusk for golden paths. DB changes are reversible via expand–migrate–contract; backfills are observable. Production uses blue/green or canary with OpenTelemetry SLO gates; queues drain gracefully. Rollbacks are instant (image + GitOps revert), and risky paths have Pennant kill switches. This yields zero-downtime releases with predictable recovery.”
Evaluation Criteria
Look for:
- Immutable, signed images and environment parity (GitOps).
- A layered test strategy: static analysis, Pest/PHPUnit, integration with Testcontainers, optional Dusk E2E.
- Expand–migrate–contract migrations with observable backfills.
- Zero-downtime deployments (blue/green or rolling) with probes and queue draining.
- Feature flags for decoupled release and quick mitigation.
- Observability with OpenTelemetry and SLO-based promotion gates.
- Clear, fast rollback via image re-pin and config revert.
Red flags: environment-specific builds, destructive migrations first, no integration tests, manual deploys, or rollbacks that require rebuilding images.
Preparation Tips
- Convert tests to Pest; add ParaTest for parallel runs.
- Add Testcontainers for MySQL/PostgreSQL and Redis; run real migrations and factories.
- Generate OpenAPI; wire provider verification to block breaking responses.
- Write a multi-stage Dockerfile; enable Opcache; create SBOM and sign with cosign.
- Adopt GitOps (Argo CD/Flux) and sealed-secrets/Vault; keep env parity.
- Practice expand–migrate–contract with a backfill job and metrics.
- Implement blue/green and rolling strategies; add readiness/liveness probes; pause/resume Horizon.
- Instrument OpenTelemetry; build Grafana/Datadog dashboards for p95 latency, error rate, and queue lag.
- Rehearse a rollback drill: re-pin image, revert Git, toggle flag.
Real-world Context
A subscription SaaS moved to build-once-promote with cosign and GitOps; rollback time dropped under two minutes. An e-commerce team adopted expand–migrate–contract; a risky index change was rolled back safely while backfill continued. Another org added Testcontainers integration; flaky tests vanished and CI time stabilized. A marketplace implemented blue/green with Horizon pausing; no more dropped jobs during deploys. With OpenTelemetry SLO gates, a cache-key regression was caught at 5% canary and auto-rolled back before users felt it.
Key Takeaways
- Produce immutable, signed images; promote, do not rebuild.
- Use a test pyramid with realistic integration and targeted E2E.
- Make schema changes reversible via expand–migrate–contract.
- Ship zero-downtime with blue/green or rolling and queue draining.
- Control exposure with feature flags and kill switches.
- Let OpenTelemetry SLOs gate promotion and trigger auto-rollback.
- Roll back by re-pinning images and reverting GitOps changes—fast and boring.
Practice Exercise
Scenario:
You own a Laravel checkout service that adds “multi-currency pricing,” requiring a new column and changes to pricing logic. You must deploy weekly with zero downtime and instant rollback.
Tasks:
- CI: Add Pint, PHPStan level 8, composer audit, and Pest tests. Parallelize with ParaTest.
- Integration: Use Testcontainers (MySQL/Redis). Run migrations, seed factories, assert repositories and transactions.
- Contracts: Generate OpenAPI; add provider verification to prevent client breakage.
- Docker: Multi-stage build; warm caches; enable Opcache; create SBOM and sign image.
- DB Safety: Implement expand–migrate–contract: add currency_code (nullable), dual-write, queue a backfill job, expose backfill metrics.
- Deploy: Blue/green: stand up green, run smoke tests, pause Horizon, flip traffic, resume Horizon.
- Gates: Instrument OTel; define SLOs (p95 <300 ms, error <1%, queue lag < 30s). Auto-pause on breach.
- Flags: Wrap pricing logic with Pennant; enable for employees and 5% of traffic first.
- Rollback Drill: Simulate a spike at 5% canary. Auto-pause rollout, re-pin to last good image, disable the flag, keep expanded schema, confirm dashboards recover.
- Report: Post-mortem with timeline, metrics, and a new test preventing the regression.
Deliverable:
Repository and runbook showing Laravel CI/CD, safe migrations, zero-downtime deployment, SLO-gated promotion, and rollback strategies that make releases predictable and reversible.

