SonarQube Scanner
.mdVerified against SonarScanner CLI 8.1.0.6389, flags verified via `sonar-scanner -h` run locally against · official docs
What it is and where it fits#
SonarQube is a code-quality-and-security platform; SonarScanner CLI is its generic command-line analyzer — the one to reach for when a project isn't already covered by a language-specific scanner (Maven, Gradle, .NET, npm plugins exist too, and are usually preferred when applicable). It combines two things a lot of teams treat as separate concerns but SonarQube unifies: code quality (duplication, complexity, code smells, test coverage) and security (vulnerabilities, security hotspots requiring manual review). Where Semgrep is a fast, rule-pattern SAST engine you run standalone, SonarQube is a server-backed platform with a persistent history — trends over time, Quality Gates that block a release, and PR decoration that comments directly on a pull request. The CLI is thin by design: it packages up the project, uploads it, and the actual analysis (mostly) happens or is aggregated server-side.
Installation#
# Download the Linux x64 distribution directly (no package manager release)
curl -sLo sonar-scanner-cli.zip \
https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-8.1.0.6389-linux-x64.zip
unzip sonar-scanner-cli.zip
export PATH="$PATH:$PWD/sonar-scanner-8.1.0.6389-linux-x64/bin"
sonar-scanner -v # confirm install (also confirms Java is reachable — sonar-scanner is a JVM app)Get the current version number from the download page (URL pattern is sonar-scanner-cli-<VERSION>-linux-x64.zip)
rather than hardcoding one — SonarSource ships new scanner releases regularly. There's no official curl
install-script or apt/brew package for the CLI itself; the zip download is the documented method for Linux.
You also need a running SonarQube Server (self-hosted) or a SonarQube Cloud account — the scanner is useless
without a destination to upload analysis results to.
Core concepts#
- Project key — a unique id for the analyzed project within the SonarQube instance; every analysis run attaches to one project key.
- Quality Gate — a set of pass/fail conditions ("is my project ready for release?") evaluated after each analysis. The same Quality Gate definition is used for every analysis of a project, but the way conditions are checked differs: branch analysis evaluates both overall-code and new-code conditions; pull request analysis evaluates only new-code conditions — you're never blocked by pre-existing debt on a PR, only by what the PR itself introduces.
- New code — a configurable definition (default: since the previous version, or a fixed number of days) that scopes what counts as "new" for new-code Quality Gate conditions. This is the mechanism that lets a large legacy codebase adopt strict quality/security gates without needing to fix years of backlog first.
- Security Hotspot vs Vulnerability — SonarQube deliberately separates the two: a Vulnerability is a confirmed exploitable issue; a Security Hotspot is security-sensitive code that needs a human to review the context (e.g., a regex that might be a ReDoS risk depending on where the input comes from) before it's classified either way.
Configuration#
Every project needs either a sonar-project.properties file or the equivalent -D flags:
# sonar-project.properties
sonar.projectKey=my-project
sonar.projectName=My Project
sonar.sources=.
sonar.exclusions=**/vendor/**,**/*.min.js
sonar.host.url=http://localhost:9000
sonar.token=<SONAR_TOKEN>export SONAR_TOKEN=<token> # preferred over -Dsonar.token on the command line (avoids shell history/CI logs)
sonar-scanner # reads sonar-project.properties in the current directory
sonar-scanner -Dsonar.projectKey=my-project \
-Dsonar.sources=. \
-Dsonar.host.url=http://localhost:9000 # override/skip the properties file entirelyCommon overrides#
# Branch analysis — track quality over time on a long-lived non-main branch (needs Developer Edition+ on self-hosted)
sonar-scanner -Dsonar.branch.name=feature/x
# Pull request analysis — decorates the PR itself rather than tracking a persistent branch
sonar-scanner -Dsonar.pullrequest.key=42 \
-Dsonar.pullrequest.branch=feature/x \
-Dsonar.pullrequest.base=main
# Scope what gets analyzed
sonar-scanner -Dsonar.exclusions=**/vendor/**,**/*.min.js # exclude paths from analysis entirely
sonar-scanner -Dsonar.coverage.exclusions=**/*_test.go # exclude from coverage metrics specifically, still analyzed for issues
sonar-scanner -Dsonar.test.inclusions=**/*_test.py # tell Sonar which files are tests, not production code
# Feed in coverage/test reports from your own test runner (Sonar doesn't run tests itself)
sonar-scanner -Dsonar.python.coverage.reportPaths=coverage.xml
sonar-scanner -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
# Block CI on the Quality Gate result
sonar-scanner -Dsonar.qualitygate.wait=true
sonar-scanner -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=300 # max seconds to wait for the gate to compute (default 300)sonar.qualitygate.wait=true is what actually gates a CI pipeline on quality — without it, sonar-scanner
exits 0 the moment analysis upload succeeds, before the Quality Gate has even been evaluated server-side
(evaluation happens asynchronously after the scanner's HTTP upload returns).
Memory tuning#
export SONAR_SCANNER_JAVA_OPTS="-Xmx2048m" # scanner ≥ 6.0; increase for large monorepos that OOM during analysis packagingReal-world scenario: PR decoration with a strict new-code gate#
Connect SonarQube to GitHub/GitLab/Bitbucket/Azure DevOps once (server-side ALM integration), then every PR analysis automatically posts a summary comment, inline issue annotations on changed lines, and a status check:
sonar-scanner \
-Dsonar.pullrequest.key=$PR_NUMBER \
-Dsonar.pullrequest.branch=$HEAD_BRANCH \
-Dsonar.pullrequest.base=$BASE_BRANCH \
-Dsonar.qualitygate.wait=trueBecause PR analysis only evaluates new-code conditions, this is safe to make strict (e.g., "0 new vulnerabilities, 80% coverage on new code") even on a codebase whose overall metrics are far from that bar — the gate only judges what the PR itself changed.
Real-world scenario: GitHub Actions CI recipe#
# .github/workflows/sonar.yml
name: SonarQube
on: [pull_request]
jobs:
sonarqube:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # SonarQube needs full git history for accurate blame/new-code detection
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v4
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
- uses: SonarSource/sonarqube-quality-gate-action@v1 # separate step: fail the job explicitly on a red gate
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}fetch-depth: 0 matters more than it looks — a shallow checkout (the CI default) breaks SonarQube's ability
to correctly compute new-code / blame information, which silently skews Quality Gate results toward "nothing
is new."
Flags#
sonar-scanner -h # help
sonar-scanner -v # version
sonar-scanner -X # debug output — first thing to reach for when analysis silently produces nothing
sonar-scanner -D key=value # define/override any sonar.* property inlineCommon pitfalls#
- Forgetting
-Dsonar.qualitygate.wait=true— the scan "succeeds" (exit 0) the instant the upload finishes, regardless of whether the Quality Gate later fails. This is the single most common "why didn't this block the merge" report. - Shallow git clones breaking new-code/blame detection (see the CI recipe above).
- Missing coverage/test report paths — SonarQube never runs your tests itself; if you don't feed it a coverage report path for your language, coverage-based gate conditions will always show 0% regardless of actual test coverage.
- Branch analysis on Community Edition — branch analysis (tracking a long-lived non-main branch over time) requires Developer Edition or above on self-hosted SonarQube Server; Community Edition only analyzes the default branch plus pull requests.
When to reach for something else#
SonarQube's security coverage is real but broader/shallower than a dedicated SAST engine like Semgrep for security-specific rule depth, and it doesn't do dependency (SCA), container, or IaC scanning at all — those are separate tools (Snyk/Trivy, Trivy/Grype, Checkov/tfsec respectively) that a security-conscious pipeline runs alongside SonarQube, not instead of it.