Setting up CI/CD for PHP Applications (Composer)

Every commit turns into a long wait for the build, and developers lose time on idle pauses. We set up CI/CD for PHP applications, optimizing Composer, tests, and Docker builds. Our team delivers the project turnkey—from pipeline audit to implementation and ongoing support, ensuring stable and fast operation without missed deadlines.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

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 modifications

validate_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

  1. Audit current pipeline and infrastructure. Identify bottlenecks (cache, tool versions, configs).
  2. Design the pipeline: choose CI system (GitHub Actions, GitLab CI), define stages, write Dockerfile.
  3. Implementation — configure caches, static analysis, tests, image build.
  4. Testing — run pipeline on staging, fix errors.
  5. 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.