SonarQube & Quality Gates
Stand up SonarQube Community Edition, create projects, and author a quality gate that actually blocks bad code.
Part 2 of 7
- Jenkins Controller Setup
- SonarQube & Quality Gates
- Wiring the Toolchain
- Jenkinsfile Anatomy
- Multi-Client Deploys
- Gate Enforcement
- Troubleshooting
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.
| Tool | Primary purpose | Security depth | Quality depth |
|---|---|---|---|
| SonarQube | Code quality + some security | Moderate | High |
| Semgrep / CodeQL | Security (SAST) | High | Low |
npm audit / Dependabot | Dependency CVEs (SCA) | Dependencies only | None |
Installation
SonarQube requires a database (PostgreSQL) and specific kernel limits. It refuses to start if those limits aren't met.
# 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
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.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.
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
# /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.
# /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).
- + (top right) → Create Project → Local project
- Display name — human readable, e.g.
Simplex KFC API Server Staging - Project key — the machine identifier, e.g.
simplex_kfc_api_server_stg - Main branch name — must match the branch the pipeline builds, e.g.
release/uat - Next → Use the global setting → Create project
- Skip the token wizard if Jenkins already has a global token
-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
| Metric | Operator | Value | Rationale |
|---|---|---|---|
| Issues | is greater than | 0 | No new bugs, vulnerabilities, or smells introduced |
| Security Hotspots Reviewed | is less than | 100% | Every flagged hotspot must be triaged by a human |
| Coverage | is less than | 80.0% | New code must be tested |
| Duplicated Lines (%) | is greater than | 3.0% | Prevents copy-paste growth |
Conditions on Overall Code
| Metric | Operator | Value |
|---|---|---|
| Coverage | is less than | 70.0% |
| Duplicated Lines | is greater than | 8 |
| Maintainability Rating | is worse than | A |
| Reliability Rating | is worse than | A |
| Security Rating | is worse than | A |
| Security Hotspots Reviewed | is less than | 100% |
"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:
| Permission | Who needs it | Why |
|---|---|---|
| Execute Analysis | The account whose token Jenkins uses | Without it, the scanner authenticates but is rejected with a 403 when submitting the report |
| Browse | Developers, stakeholders | Read 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.
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".
Reading the dashboard
Project dashboard URL pattern:
http://<sonarqube-host>:9000/dashboard?id=<project-key>
| Tab | What's there |
|---|---|
| Overview | Gate status, ratings, and headline metrics. Toggle New Code / Overall Code top right. |
| Issues | Full filterable list — by severity, type, file, assignee. This is the developer work queue. |
| Security Hotspots | Code requiring human security review. Distinct from vulnerabilities: a hotspot is "look at this", a vulnerability is "this is wrong". |
| Measures | Metric tree view — drill into coverage or duplication per directory and file. |
| Activity | History over time. Where you demonstrate that quality is trending in the right direction. |
Verification checklist
| # | Check | How |
|---|---|---|
| 1 | Service operational | grep "operational" /opt/sonarqube/logs/sonar.log |
| 2 | Not running as root | ps -o user= -C java | sort -u |
| 3 | Project key matches Jenkinsfile | Compare SonarQube UI value against SONAR_PROJECT_KEY character by character |
| 4 | Custom gate assigned, not Sonar way | Project Settings → Quality Gate |
| 5 | Project is Private | Project Settings → Permissions |
| 6 | Execute Analysis granted to token owner | Permissions matrix, rightmost column |
| 7 | Token expiry recorded somewhere | My Account → Security → note the date |