Your CI pipeline wastes 90 seconds just on composer install. A developer commits, goes for coffee, comes back — and the build hasn't even reached tests yet. As DevOps engineers with 7+ years of experience, we've seen this dozens of times. The solution is vendor caching and correct Composer flags. With them, the same operation drops to 8 seconds. A 10x time saving means a team of ten gets feedback an hour faster every day. Over a month, that translates to significant cost savings on cloud runners — tens of about $9–13 in savings. In this article, we'll show how to make CI/CD for PHP fly and which pitfalls are unique to PHP projects.
Why most PHP projects waste time in CI?
The most common problems are missing Composer cache, running tests without static analysis, and ignoring Docker optimizations. Let's look at each with numbers.
Composer without cache — 60–120 seconds per commit on a typical Laravel project. With cache — 5–10 seconds. A 10x saving in one step.
PHPStan without a baseline — on legacy projects, the analyzer fails with 500+ errors, and developers just disable it. A proper baseline pins current errors and only blocks new ones.
Docker images without multistage — weigh 1–2 GB, clutter the registry, and slow down deployment. Multistage build yields a 100–200 MB image, reducing transfer time and storage costs.
How we set up CI/CD for PHP end-to-end
Our approach is to build a pipeline that completes in 2–3 minutes and delivers a release with confidence. Let's break down each stage.
Composer: cache and correct flags
According to Composer documentation, the --prefer-dist flag speeds up dependency installation by downloading archives. In CI, always use:
---
- uses: actions/cache@v4
with:
path: ~/.composer/cache
key: ${{ runner.os }}-composer-${{ hashFiles('composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- run: composer install --no-interaction --no-progress --prefer-dist --optimize-autoloader
-----optimize-autoloader generates a classmap instead of PSR-4 lookups — faster class loading in production. composer.lock must be in the repository; without it, installation resolves versions from scratch.
Why Composer caching is critical
Without cache, each commit spends a minute downloading dependencies. With cache, seconds. On a project with 50 commits per day, the saving is nearly an hour of CPU time — directly converting to budget savings. The cache is keyed by composer.lock; it invalidates automatically when dependencies change.
Which database to use in tests?
If tests use PostgreSQL-specific features (jsonb, full-text search, arrays), a real service is needed. Otherwise, SQLite in-memory is sufficient — it's faster and requires no extra containers. Configuring a service via services in CI takes 5 lines.
Static analysis tools for PHP
PHPStan — static analyzer with levels 0–9. For legacy projects, our recipe:
./vendor/bin/phpstan analyse --level=5 --configuration=phpstan.neon --generate-baseline The baseline pins current errors in phpstan-baseline.neon, and only new errors are blocked. Gradually raise the level to 6–9.
PHP_CodeSniffer or PHP CS Fixer — to enforce PSR-12. We use PHP CS Fixer in CI with --dry-run, and locally with auto-fix.
Laravel-specific CI
Artisan in CI: php artisan config:clear && php artisan migrate --force --no-interaction && php artisan db:seed --class=TestSeeder --no-interaction. --force is mandatory — without it, Laravel prompts for confirmation.
Queues and Redis. If tests use Queue::fake(), Redis is unnecessary. If testing real processing, add a Redis service in CI.
Docker for PHP: multistage build end-to-end
FROM php:8.3-fpm-alpine AS builder
RUN apk add --no-cache git unzip libzip-dev \
&& docker-php-ext-install pdo pdo_pgsql zip opcache
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --optimize-autoloader
COPY . .
RUN composer run-script post-autoload-dump
FROM php:8.3-fpm-alpine
COPY --from=builder /var/www /var/www
OPcache is mandatory in production. opcache.ini config:
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
; in production — do not check file modificationsvalidate_timestamps=0 is important: OPcache doesn't check disk on every request. On deploy, restart FPM or call opcache_reset().
| Optimization | Effect |
|---|---|
| Composer cache | Cuts install time from 60-120s to 5-10s |
| Multistage Docker | Reduces image from 1-2GB to 100-200MB |
| PHPStan baseline | Pins current errors, blocks new ones |
| OPcache validate_timestamps=0 | Speeds up PHP requests by 30-50% |
Deployment options: from VPS to Kubernetes
Kubernetes + Nginx sidecar — PHP-FPM pod with Nginx sidecar, shared volume. ConfigMap for nginx.conf and php.ini.
VPS via Deployer — vendor/bin/dep deploy production — zero-downtime with atomic symlink. Laravel Forge / Ploi — deployment platforms for PHP — great when Kubernetes isn't needed.
What's included in end-to-end CI/CD setup
- Design and implementation of CI/CD pipeline (GitHub Actions / GitLab CI / Jenkins).
- Caching Composer and node_modules (if frontend present).
- Configuration of PHPUnit, PHPStan, PHP CS Fixer.
- Multistage Docker build with OPcache.
- Deployment to Kubernetes (via ArgoCD or Helm) or VPS (via Deployer).
- Integration with monitoring (Grafana + Loki, or Datadog).
- Pipeline documentation and incident runbook.
- 2 weeks of post-launch support.
Our CI/CD process
- Audit current pipeline and infrastructure. Identify bottlenecks (cache, tool versions, configs).
- Design the pipeline: choose CI system (GitHub Actions, GitLab CI), define stages, write Dockerfile.
- Implementation — configure caches, static analysis, tests, image build.
- Testing — run pipeline on staging, fix errors.
- Handover — documentation, team training, 30-day warranty support.
Estimated timelines
| Task | Timeline |
|---|---|
| Basic CI (Composer + tests + PHPStan) | 1 day |
| + Docker build and image | +0.5 day |
| Laravel with migrations, PostgreSQL service | 1–2 days |
| Kubernetes deployment with Nginx sidecar | 2–3 days |
| Full implementation with documentation | 3–5 days |
Order CI/CD setup — and your team will stop waiting for builds. Contact us for a consultation. Our experience: 7+ years, 50+ CI/CD implementations for PHP, guaranteed results.







