
September 10, 2026
11 min read
By Kokil Thapa | Last reviewed: September 2026
Pull requests that pass tests can still ship SQL injection, duplicated logic, and unmaintainable debt. Static Code Analysis in CI with SonarQube scans every commit before merge and enforces rules your team agrees on. I run this pattern on production Laravel apps deployed through GitLab CI pipelines for Laravel. The scanner finds issues PHPUnit never touches. This guide walks through server setup, scanner config, quality gates, and a working pipeline you can copy today.
What is Static Code Analysis in CI with SonarQube and why use it?
SonarQube reads source code without executing it. It flags bugs, security hotspots, code smells, duplication, and test coverage gaps. Wired into CI, it becomes a gate—not a report nobody reads.
On client projects I maintain with Deployer 7 and GitLab CI, SonarQube sits between unit tests and deploy. A green test suite plus a red quality gate still blocks the merge. That single rule has prevented several regressions that would have reached staging.
Static analysis complements—not replaces—tools like PHPStan. Pair SonarQube with PHPStan level 9 analysis for type safety SonarQube does not fully cover. SonarQube adds security rules, duplication tracking, and a dashboard non-developers can read.
The return on investment is straightforward. Fixing a SQL injection in a pull request costs minutes. Fixing it in production costs hours and client trust. For teams offering testing and optimization services, SonarQube gives measurable quality metrics before handoff.
How do you install and configure SonarQube for CI pipelines?
SonarQube runs as a long-lived server. CI jobs only run the lightweight SonarScanner CLI and upload results. You do not need SonarQube on every developer laptop.
Server deployment options
For small teams, run SonarQube Community Edition on a dedicated Ubuntu 24.04 VM with at least 4 GB RAM. Docker Compose works well for staging. Production setups should use a managed PostgreSQL 18 database—not the embedded H2 database SonarQube ships for trials.
A minimal Docker Compose stack looks like this:
services:
sonarqube:
image: sonarqube:community
ports:
- "9000:9000"
environment:
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: ${SONAR_DB_PASSWORD}
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_logs:/opt/sonarqube/logs
- sonarqube_extensions:/opt/sonarqube/extensions
db:
image: postgres:18
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: ${SONAR_DB_PASSWORD}
POSTGRES_DB: sonar After first boot, log in at port 9000 with the default admin credentials. Change the password immediately. Generate a project token under My Account → Security. Store it in your CI secret manager—never commit it to Git.
Project and branch setup
Create one SonarQube project per repository. Enable branch analysis if you use Developer Edition or above. Community Edition supports main-branch analysis only; feature branches still scan, but gating on branch conditions requires a paid tier or a single long-lived integration branch strategy.
For Laravel 12 or 13 apps on PHP 8.3+, install the PHP plugin from Administration → Marketplace if it is not bundled. Confirm PHP, JavaScript, and CSS analyzers are active for full-stack projects like Adventure Third Pole Trek.
How do you configure sonar-project.properties for Laravel and PHP?
Place a sonar-project.properties file in your repository root. This file tells SonarScanner what to scan and where coverage lives.
sonar.projectKey=my-laravel-app
sonar.projectName=My Laravel App
sonar.projectVersion=${CI_COMMIT_SHORT_SHA}
sonar.sources=app,routes,resources/js
sonar.tests=tests
sonar.sourceEncoding=UTF-8
sonar.exclusions=/vendor/,/storage/,/bootstrap/cache/,\
/node_modules/,/public/build/
sonar.php.coverage.reportPaths=storage/coverage/clover.xml
sonar.php.tests.reportPath=storage/coverage/junit.xml
sonar.host.url=${SONAR_HOST_URL}
sonar.token=${SONAR_TOKEN} Generate Clover coverage during PHPUnit or Pest runs. Without coverage data, SonarQube cannot enforce coverage gates. See code coverage gates in CI for the PHPUnit flags that produce clover.xml reliably.
Exclusions that matter for Laravel
Always exclude vendor/, compiled views, and Vite build output. Including them inflates line counts and produces false duplication alerts. For Blade templates, add resources/views to sources only if you want HTML duplication checks—many teams exclude views and rely on PHP analysis in controllers and Livewire components instead.
Validate your config locally before pushing:
docker run --rm \
-e SONAR_HOST_URL=https://sonar.example.com \
-e SONAR_TOKEN=squ_xxxx \
-v "$(pwd):/usr/src" \
sonarsource/sonar-scanner-cli Official reference: the SonarScanner documentation covers CLI flags and environment variable overrides.
How do you wire SonarQube into GitLab CI for Laravel projects?
GitLab CI is my default for Laravel deployments. The scan job should run after tests produce coverage artifacts. Use needs: to avoid waiting for unrelated stages.
stages:
- test
- quality
- deploy
unit-tests:
stage: test
image: php:8.3-cli
script:
- composer install --no-interaction --prefer-dist
- php artisan test --coverage-clover=storage/coverage/clover.xml \
--log-junit=storage/coverage/junit.xml
artifacts:
paths:
- storage/coverage/
expire_in: 1 day
sonarqube:
stage: quality
image: sonarsource/sonar-scanner-cli:latest
needs: [unit-tests]
variables:
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
GIT_DEPTH: "0"
script:
- sonar-scanner
allow_failure: false Set GIT_DEPTH: "0" for accurate blame and new-code metrics. Shallow clones break leak-period calculations. Store SONAR_TOKEN and SONAR_HOST_URL as masked CI variables.
For pull-request decoration, configure a webhook from SonarQube to GitLab. Developers then see inline comments on merge requests—a pattern I use alongside build verification and quality gates on sister legal-tech sites sharing the same Deployer pipeline.
GitHub Actions alternative
Teams on GitHub can use the official SonarCloud or self-hosted action. The scanner invocation stays identical; only the YAML wrapper changes. Compare runner models in our GitHub Actions vs GitLab CI guide if you are choosing a platform.
What quality gate thresholds should you set for production PHP apps?
Start with SonarQube's built-in "Sonar way" profile, then tighten gradually. Enforcing 90% coverage on day one will freeze your team. A phased approach works better on real client timelines.
I recommend this rollout sequence:
- Week 1: Run scans in report-only mode with
allow_failure: true. Fix critical and blocker issues only. - Week 2–3: Enable the gate on new code—zero new bugs, zero new vulnerabilities, security hotspots reviewed.
- Month 2: Add coverage on new code at 70%, then raise to 80% once the team adapts.
- Ongoing: Track technical debt ratio and duplication; schedule cleanup sprints when debt exceeds five days.
Detailed gate tuning belongs in your SonarQube quality and security gates reference. Align thresholds with your test strategy in Laravel Pest CI/CD testing.
| Tool | Primary strength | CI gate support | Best paired with SonarQube? |
|---|---|---|---|
| SonarQube | Security, duplication, coverage dashboard | Native quality gate | — |
| PHPStan | Static typing, generics, level 9 rules | Exit code in CI script | Yes — complementary |
| PHPUnit / Pest | Runtime behaviour verification | Test failure blocks build | Yes — feeds coverage |
| Gitleaks | Secret detection in Git history | Exit code on leak found | Yes — see secrets scanning with Gitleaks |
| ESLint / Pint | Style and lint for JS/PHP | Formatter exit code | Partial overlap on smells |
SonarQube is not a replacement for secret scanning or dependency audit tools. Run composer audit in a separate job. Combine layers rather than expecting one scanner to catch everything.
How do you troubleshoot common SonarQube CI failures?
Most pipeline failures fall into a handful of categories. Knowing which saves hours of log spelunking.
Coverage shows 0% despite passing tests
The clover.xml path in sonar-project.properties must match the artifact path exactly. Confirm the test job uploads coverage before the scan job runs. A missing needs: dependency causes the scanner to run against an empty directory.
Quality gate fails on legacy code you did not touch
Switch conditions to "on new code" only. SonarQube calculates a leak period from your previous release or a set number of days. Legacy debt stays visible on the dashboard but does not block merges.
Scanner timeout or out-of-memory on large repos
Increase the CI job memory limit. Exclude generated assets aggressively. Use CI build caching for Composer and npm so the test stage finishes faster and the scan stage starts sooner.
Authentication errors
Regenerate the project token if it was rotated. Confirm SONAR_HOST_URL has no trailing slash mismatch. Self-signed TLS certificates require importing the CA into the scanner image or using SONAR_SCANNER_OPTS for trust configuration—prefer proper Let's Encrypt certs on the server instead.
For infrastructure issues on the SonarQube VM itself—disk space, PostgreSQL connection limits, Java heap—basic Linux server administration skills apply. SonarQube logs live under logs/sonar.log inside the container or install directory.
When debugging JSON config or webhook payloads during setup, a local JSON formatter saves time validating API responses from SonarQube's Web API.
Key Takeaways
- Run SonarQube as a persistent server; CI jobs only execute SonarScanner and upload results.
- Place
sonar-project.propertiesin the repo root with correct Laravel exclusions and Clover coverage paths. - Wire the scan job after tests with
needs:and setGIT_DEPTH: "0"for accurate new-code metrics. - Roll out quality gates gradually—new-code conditions first, coverage thresholds second.
- Pair SonarQube with PHPStan, secret scanning, and
composer auditfor layered defence. - Store
SONAR_TOKENin CI secrets and follow CI/CD secrets management best practices.
People Also Ask
Is SonarQube free for CI pipelines?
SonarQube Community Edition is free and supports main-branch analysis with quality gates. Branch and pull-request decoration on multiple long-lived branches requires Developer Edition or SonarCloud. For a single integration branch workflow, Community Edition is enough for most small Laravel teams.
Does SonarQube replace PHPUnit or Pest?
No. SonarQube performs static analysis—it reads code without running it. PHPUnit and Pest verify runtime behaviour. SonarQube consumes coverage reports that PHPUnit or Pest generate, then enforces coverage thresholds on new code through the quality gate.
How long does a SonarQube scan take in CI?
A typical Laravel app with 50,000 lines of PHP scans in two to five minutes after tests complete. First scans take longer because SonarQube builds its issue index. Exclude vendor and build directories to keep scan times predictable on shared GitLab runners.
Can SonarQube analyze WordPress or WooCommerce plugins?
Yes. Point sonar.sources at your plugin directory and exclude wp-content/uploads and vendor paths. WooCommerce 11.1 custom themes benefit from the same gate pattern. For WordPress-specific workflows, see our WordPress development services page for project context.
Ship cleaner code on every merge
Static Code Analysis in CI with SonarQube turns code quality from a subjective review comment into a measurable gate. Install the server once, add a scanner job to your pipeline, and tighten thresholds as the team matures. The setup pays for itself the first time it blocks a vulnerability before production.
Need help wiring SonarQube into an existing Laravel or legal-tech codebase? Explore our custom software development work—including platforms like Mijar Law Associates—or contact us to audit your CI pipeline and quality gates.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

