qasim@wiki:~$

SonarQube & Quality Gates

Stand up SonarQube Community Edition, create projects, and author a quality gate that actually blocks bad code.

What SonarQube does — and doesn't

SonarQube is a code quality platform with meaningful but secondary security coverage. It measures bugs, code smells, duplication, complexity, and test coverage, then evaluates them against a quality gate — a pass/fail rule set.

ToolPrimary purposeSecurity depthQuality depth
SonarQubeCode quality + some securityModerateHigh
Semgrep / CodeQLSecurity (SAST)HighLow
npm audit / DependabotDependency CVEs (SCA)Dependencies onlyNone
These are complements, not substitutes. A common request from security stakeholders is "we have SonarQube, are we covered?" The honest answer is no — SonarQube will not find an injection chain the way a dedicated SAST engine will, and it says nothing about vulnerable transitive dependencies.

Installation

SonarQube requires a database (PostgreSQL) and specific kernel limits. It refuses to start if those limits aren't met.

Step 1 — Kernel and limits
# Elasticsearch (embedded in SonarQube) requires these
sudo sysctl -w vm.max_map_count=524288
sudo sysctl -w fs.file-max=131072

# Persist across reboots
cat <<'EOF' | sudo tee /etc/sysctl.d/99-sonarqube.conf
vm.max_map_count=524288
fs.file-max=131072
EOF

# Per-user limits for the sonarqube account
cat <<'EOF' | sudo tee /etc/security/limits.d/99-sonarqube.conf
sonarqube   -   nofile   131072
sonarqube   -   nproc    8192
EOF
Most common install failure. If vm.max_map_count is too low, SonarQube starts, then dies seconds later. systemctl status shows it as failed with no useful reason — the actual error is buried in logs/es.log, not sonar.log.
Step 2 — PostgreSQL
sudo apt install -y postgresql postgresql-contrib

sudo -u postgres psql <<'EOF'
CREATE USER sonarqube WITH ENCRYPTED PASSWORD 'REPLACE_ME';
CREATE DATABASE sonarqube OWNER sonarqube;
GRANT ALL PRIVILEGES ON DATABASE sonarqube TO sonarqube;
EOF

SonarQube does not support MySQL or MariaDB. PostgreSQL is the only supported open-source option.

Step 3 — SonarQube service account and install
sudo useradd -r -m -d /opt/sonarqube -s /bin/bash sonarqube

cd /opt
sudo unzip sonarqube-10.5.1.90531.zip
sudo mv sonarqube-10.5.1.90531 sonarqube
sudo chown -R sonarqube:sonarqube /opt/sonarqube
Never run SonarQube as root. The embedded Elasticsearch refuses to start as root and will fail with a message about "can not run elasticsearch as root". This is a safety feature, not an obstacle to work around.
Step 4 — Configuration
# /opt/sonarqube/conf/sonar.properties
sonar.jdbc.username=sonarqube
sonar.jdbc.password=REPLACE_ME
sonar.jdbc.url=jdbc:postgresql://localhost:5432/sonarqube

sonar.web.host=0.0.0.0
sonar.web.port=9000

# Heap sizing — tune to available RAM
sonar.web.javaOpts=-Xmx1024m -Xms512m
sonar.ce.javaOpts=-Xmx2048m -Xms512m
sonar.search.javaOpts=-Xmx1024m -Xms1024m

The Compute Engine (sonar.ce) is what processes uploaded analysis reports. On a large codebase it is the component most likely to run out of memory — a 494-file TypeScript project produced a 10.4 MB report that took ~11 minutes to process.

Step 5 — systemd unit
# /etc/systemd/system/sonarqube.service
[Unit]
Description=SonarQube
After=network.target postgresql.service

[Service]
Type=forking
User=sonarqube
Group=sonarqube
ExecStart=/opt/sonarqube/bin/linux-x86-64/sonar.sh start
ExecStop=/opt/sonarqube/bin/linux-x86-64/sonar.sh stop
LimitNOFILE=131072
LimitNPROC=8192
Restart=on-failure
TimeoutStartSec=180

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now sonarqube

# Startup takes 60-120s. Watch the log rather than trusting systemctl.
sudo tail -f /opt/sonarqube/logs/sonar.log
# Look for: "SonarQube is operational"

Creating a project

Log in at http://<host>:9000 (default admin/admin, forced change on first login).

  1. + (top right) → Create ProjectLocal project
  2. Display name — human readable, e.g. Simplex KFC API Server Staging
  3. Project key — the machine identifier, e.g. simplex_kfc_api_server_stg
  4. Main branch name — must match the branch the pipeline builds, e.g. release/uat
  5. Next → Use the global settingCreate project
  6. Skip the token wizard if Jenkins already has a global token
The project key is an exact-match contract. It appears in three places that must agree: the SonarQube project, the -Dsonar.projectKey= scanner argument, and the SONAR_PROJECT_KEY environment variable in the Jenkinsfile. It is case-sensitive. A mismatch silently creates a second empty project rather than erroring.

Naming convention

With one project per client per environment, a convention prevents chaos:

<org>_<client>_<component>_<env>

simplex_kfc_api_server_stg
simplex_kfc_api_server_uat
simplex_dominos_api_server_uat
simplex_br_api_server_uat

Authoring a quality gate

Quality Gates (top nav) → Create. The built-in Sonar way gate is a reasonable default but cannot be edited; copy its intent into your own gate so you can tune it.

Conditions on New Code

MetricOperatorValueRationale
Issuesis greater than0No new bugs, vulnerabilities, or smells introduced
Security Hotspots Reviewedis less than100%Every flagged hotspot must be triaged by a human
Coverageis less than80.0%New code must be tested
Duplicated Lines (%)is greater than3.0%Prevents copy-paste growth

Conditions on Overall Code

MetricOperatorValue
Coverageis less than70.0%
Duplicated Linesis greater than8
Maintainability Ratingis worse thanA
Reliability Ratingis worse thanA
Security Ratingis worse thanA
Security Hotspots Reviewedis less than100%
New Code vs Overall Code — the single most misunderstood thing in SonarQube.

"New Code" is a moving window defined per project (previous version, number of days, or a reference branch). Conditions on New Code apply only to lines added or changed inside that window. Conditions on Overall Code apply to the whole codebase.

On a legacy codebase, Overall Code conditions will fail immediately and permanently. On the first scan, there is no baseline yet, so New Code conditions frequently evaluate as passing even when the code is objectively poor. A green gate on scan #1 means very little — it becomes meaningful from scan #2 onwards.

Assigning the gate to projects

Per project: Project Settings → Quality Gate → Always use a specific Quality Gate → select your gate.

Or set your gate as the instance default so every new project inherits it: Quality Gates → <your gate> → Set as Default.

Permissions

Two permissions matter for CI:

PermissionWho needs itWhy
Execute AnalysisThe account whose token Jenkins usesWithout it, the scanner authenticates but is rejected with a 403 when submitting the report
BrowseDevelopers, stakeholdersRead the dashboard and drill into issues

Project Settings → Permissions. Set visibility to Private, then tick Execute Analysis (rightmost column) on the row for the token-owning account.

Finding which account owns a token. Avatar (top right) → My Account → Security. Global tokens are listed with creation and expiry dates and a "last used" column. That last-used timestamp is the fastest way to confirm a pipeline actually authenticated — if it still reads Never after a build, the scan never reached the server with that token.

Do not use "Apply Permission Template" for this

The template dialog looks like the right place to grant a permission but it overwrites the project's entire permission set with the template's. To grant one permission to one account, tick the checkbox directly in the permissions matrix.

Token management

# Avatar → My Account → Security → Generate Tokens
# Type: Global Analysis Token
# Name: jenkins-controller-2026
# Expiry: set one. Never choose "no expiry".
Diarise token expiry. An expired analysis token produces a 403 in the SonarQube Scan stage and every pipeline in the organisation fails at once. Set a calendar reminder for two weeks before expiry. Tokens with no expiry avoid this failure mode but are a standing audit finding — prefer dated tokens with a rotation reminder.

Reading the dashboard

Project dashboard URL pattern:

http://<sonarqube-host>:9000/dashboard?id=<project-key>
TabWhat's there
OverviewGate status, ratings, and headline metrics. Toggle New Code / Overall Code top right.
IssuesFull filterable list — by severity, type, file, assignee. This is the developer work queue.
Security HotspotsCode requiring human security review. Distinct from vulnerabilities: a hotspot is "look at this", a vulnerability is "this is wrong".
MeasuresMetric tree view — drill into coverage or duplication per directory and file.
ActivityHistory over time. Where you demonstrate that quality is trending in the right direction.

Verification checklist

#CheckHow
1Service operationalgrep "operational" /opt/sonarqube/logs/sonar.log
2Not running as rootps -o user= -C java | sort -u
3Project key matches JenkinsfileCompare SonarQube UI value against SONAR_PROJECT_KEY character by character
4Custom gate assigned, not Sonar wayProject Settings → Quality Gate
5Project is PrivateProject Settings → Permissions
6Execute Analysis granted to token ownerPermissions matrix, rightmost column
7Token expiry recorded somewhereMy Account → Security → note the date