Install SonarQube on VPS - Code Quality Analysis
SonarQube is an enterprise-grade code quality analysis platform that automatically detects bugs, security vulnerabilities, code smells, and code duplication in your software projects. It supports nearly 30 programming languages (Java, Python, PHP, JavaScript, Go, C++, C#, TypeScript) and deploys easily on any VPS using Docker Compose. This guide covers setting up SonarQube Server + SonarScanner from scratch for DevOps teams that need continuous quality gates in CI/CD pipelines, with integrations for Jenkins and GitLab CI.
SonarQube Architecture
- SonarQube Server: Stores data, displays dashboard, enforces quality gates — runs on port 9000
- SonarScanner: CLI tool that analyzes source code and uploads results to the Server
- Database: PostgreSQL or Embedded H2 (testing only) — stores issues, metrics, and users
- Plugins: Language analyzers (Java, Python, PHP, JS, etc.) — built into SonarQube
Install SonarQube with Docker Compose
mkdir -p /opt/sonarqube/{data,extensions,logs} && cd /opt/sonarqube
# Configure sysctl to allow Elasticsearch (bundled in SonarQube) to run
sudo sysctl -w vm.max_map_count=262144
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
# Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
db:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: sonarqube
POSTGRES_USER: sonar
POSTGRES_PASSWORD: CHANGE_ME_DB_PASSWORD
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "sonar"]
interval: 10s
timeout: 5s
retries: 5
sonarqube:
image: sonarqube:10-community
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:9000:9000"
environment:
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonarqube
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: CHANGE_ME_DB_PASSWORD
SONAR_PYTHON_COVERAGE_REPORT_PATHS: coverage.xml
SONAR_COVERAGE_EXCLUSIONS: "**/tests/**,**/node_modules/**"
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/api/system/health"]
interval: 15s
timeout: 10s
retries: 5
volumes:
postgres_data:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
EOF
# Start all services
docker-compose up -d
# Wait for SonarQube to be ready (~60 seconds)
echo "Waiting for SonarQube to be ready..."
sleep 60
Configure Nginx Reverse Proxy + HTTPS
sudo nano /etc/nginx/sites-available/sonarqube
server {
listen 80;
server_name sonarqube.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name sonarqube.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/sonarqube.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/sonarqube.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
}
location ~ ^/api/ce/info$ {
proxy_pass http://127.0.0.1:9000;
proxy_cache_valid 200 10m;
proxy_cache_bypass $http_pragma $http_authorization;
add_header X-Cache-Status $upstream_cache_status;
}
}
# Enable the configuration
sudo ln -s /etc/nginx/sites-available/sonarqube /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Create SSL certificate
sudo certbot --nginx -d sonarqube.yourdomain.com
Add SonarQube Token and Configure Authentication
# Open https://sonarqube.yourdomain.com
# Login: admin / admin (dangerous default password — change immediately)
# Change admin password:
# Admin → Security → Change password
# Create token for Scanner:
# Admin → Security → Users → admin (click) → Generate Tokens
# Name: "jenkins-scanner" → Copy token (use with Jenkins/GitLab CI)
# Set up Quality Gate:
# Quality Gates → Create
# Add gate conditions:
# - Coverage on New Code >= 80%
# - Reliability Rating A or better
# - Security Rating A or better
# - Bugs = 0 (on new code)
# Configure webhook to notify Jenkins/GitLab on analysis completion:
# Administration → Configuration → Webhooks
# URL: https://jenkins.yourdomain.com/sonarqube-webhook/
# Events: Project Analysis Completed
Install SonarScanner on Build Server
# For Jenkins or Linux build agent, install SonarScanner CLI
cd /opt
sudo wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-5.0.1.3006-linux-x64.zip
sudo unzip sonar-scanner-cli-*.zip
sudo rm sonar-scanner-cli-*.zip
sudo mv sonar-scanner-* sonar-scanner
sudo chown -R nobody:nogroup /opt/sonar-scanner
# Add to PATH
echo "export PATH=/opt/sonar-scanner/bin:\$PATH" | sudo tee -a /etc/profile.d/sonar-scanner.sh
source /etc/profile.d/sonar-scanner.sh
# Configure Server URL in sonar-scanner.properties
sudo nano /opt/sonar-scanner/conf/sonar-scanner.properties
# Add lines:
sonar.host.url=https://sonarqube.yourdomain.com
sonar.login=YOUR_SONARQUBE_TOKEN
# Verify installation
sonar-scanner --version
Scan a Sample Project
# Create sample project
mkdir -p /tmp/myapp && cd /tmp/myapp
# Create Python file with intentional issues
cat > app.py << 'PYEOF'
def calculate_discount(price, discount=0):
result = price * (1 - discount)
# bug: unused variable
unnecessary = 123
return result
def check_user(user):
# bug: potential null pointer
return user.name.upper()
class Config:
# code smell: hardcoded password
PASSWORD = "hardcoded123456"
PYEOF
# Create sonar-project.properties
cat > sonar-project.properties << 'PROPSEOF'
sonar.projectKey=myapp
sonar.projectName=My App
sonar.projectVersion=1.0.0
sonar.sources=.
sonar.sourceEncoding=UTF-8
sonar.exclusions=**/node_modules/**,**/tests/**,**/.env
PROPSEOF
# Run analysis
sonar-scanner \
-Dsonar.host.url=https://sonarqube.yourdomain.com \
-Dsonar.login=YOUR_TOKEN \
-Dsonar.projectKey=myapp
# Wait a moment then open dashboard:
# https://sonarqube.yourdomain.com/projects
Integrate with Jenkins Pipeline
# In Jenkins: Manage Jenkins → Plugins → Search "SonarQube Scanner for Jenkins"
# Install this plugin
# Configure SonarQube Server in Jenkins:
# Manage Jenkins → System Configuration → SonarQube Servers
# Name: SonarQube
# Server URL: https://sonarqube.yourdomain.com
# Server authentication token: (paste your token)
# In Jenkinsfile:
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('SonarQube Analysis') {
environment {
SONAR_TOKEN = credentials('sonarqube-token')
}
steps {
sh '''
sonar-scanner \
-Dsonar.host.url=https://sonarqube.yourdomain.com \
-Dsonar.projectKey=myproject \
-Dsonar.login=$SONAR_TOKEN \
-Dsonar.sources=src \
-Dsonar.exclusions=**/tests/**
'''
}
}
stage('Quality Gate') {
steps {
waitForQualityGate abortPipeline: true
}
}
}
}
Integrate with GitLab CI
# In .gitlab-ci.yml
stages:
- build
- sonarqube
variables:
SONAR_HOST_URL: https://sonarqube.yourdomain.com
SONAR_TOKEN: $SONARQUBE_TOKEN
sonarqube-check:
stage: sonarqube
image: sonarsource/sonar-scanner-cli:latest
script:
- sonar-scanner
-Dsonar.projectKey=$CI_PROJECT_NAME
-Dsonar.sources=.
-Dsonar.host.url=$SONAR_HOST_URL
-Dsonar.login=$SONAR_TOKEN
-Dsonar.gitlab.project_id=$CI_PROJECT_PATH
-Dsonar.gitlab.commit_sha=$CI_COMMIT_SHA
-Dsonar.gitlab.ref_name=$CI_COMMIT_REF_NAME
only:
- merge_requests
- develop
- main
# Add SONARQUBE_TOKEN in GitLab CI/CD variables:
# Settings → CI/CD → Variables
# Value: YOUR_SONARQUBE_TOKEN (created earlier)
Configure Custom Rules and Quality Profiles
# Quality Profiles are rule sets used to analyze code
# Admin → Quality Profiles → Select language (Python, Java, JavaScript, etc.)
# Create custom profile for your project:
# Quality Profiles → Create → Name "MyCompanyRules-Python"
# Parent Profile: "Sonar Way" (default)
# Activate Rules: Select rules you need (OWASP Top 10, CWE, Bug-prone, etc.)
# Set rule severity:
# - Blocker: Build failure (blocks quality gate)
# - Critical: Security vulnerabilities
# - Major: Bugs affecting logic
# - Minor: Code style, non-critical issues
Verify SonarQube is Working Correctly
# Check SonarQube Server health status
curl -s http://localhost:9000/api/system/health | python3 -m json.tool
# Should return status: UP
# Check all containers
docker-compose ps
# View SonarQube logs
docker-compose logs sonarqube -f --tail=50
# Check PostgreSQL database
docker-compose exec db psql -U sonar -d sonarqube -c "SELECT COUNT(*) FROM projects;"
# Test through reverse proxy
curl -I https://sonarqube.yourdomain.com
# Should return HTTP/1.1 200 OK
Troubleshoot Common Issues
Error: "Elasticsearch [elasticsearch.errors.ConnectionError]"
Bundled Elasticsearch has wrong IP listener or port is taken by another process.
# Check vm.max_map_count must be 262144 or higher
sysctl vm.max_map_count
# If wrong, set it
sudo sysctl -w vm.max_map_count=262144
# Remove old containers and volumes, then restart
docker-compose down -v
docker-compose up -d
Error: "Database connection error" connecting to PostgreSQL
Username, password, or database URL is incorrect, or PostgreSQL container is not ready yet.
# Check PostgreSQL is running
docker-compose ps db
# Should show "Up (healthy)"
# Test connection from SonarQube container
docker-compose exec sonarqube psql -h db -U sonar -d sonarqube -c "SELECT 1"
# If wrong, fix environment variables in docker-compose.yml and restart
docker-compose down
docker-compose up -d
Error: "403 Forbidden" or "Unauthorized" pushing analysis
Token is expired, revoked, or lacks sufficient permissions.
# Check token is still valid
curl -u "admin:SONARQUBE_TOKEN" \
https://sonarqube.yourdomain.com/api/user_tokens/list
# Create new token:
# Admin → Security → Users → admin → Generate Tokens
# Use new token in sonar-scanner command or Jenkins/GitLab CI
Error: "Quality Gate failed" even though code looks clean
Default Quality Gate is too strict. Check which condition failed.
# View project's Quality Gate in dashboard
# Project → Quality Gate → Check Failed Conditions
# Recommendation: adjust gate for team maturity
# Example: use "Sonar Way - Recommended" instead of strict built-in
# Change Quality Gate:
# Project Settings → Quality Gate → Select different gate
SonarScanner is slow or times out on analysis
Project is too large, SonarQube lacks resources, or network latency is high.
# Increase memory for SonarQube in docker-compose.yml
environment:
SONAR_JAVA_OPTS: "-Xmx2g -Xms1g"
# Or increase scanner timeout:
sonar-scanner \
-Dsonar.host.url=... \
-Dsonar.login=... \
-Dsonar.ws.timeout=120
# Check resource usage
docker stats sonarqube
# If CPU/memory maxed, upgrade VPS or analyze incrementally
# (analyze only changed files instead of entire project)
Frequently Asked Questions
What are the limitations of SonarQube Community Edition?
Community Edition is free but lacks branch analysis (separate main/develop/feature branch metrics), Portfolio dashboard for multiple projects, and advanced security features (SAST vulnerability detection). Developer and Enterprise editions have these, but cost money. For small to medium VPS, Community is sufficient. If you need branch analysis, try Self-Managed Edition (Community+ tier for a fee).
How long can I keep analysis results?
SonarQube stores full analysis history indefinitely if database has space. For cleanup of old data, set retention policy in Admin → Housekeeping — deletes analysis older than specified days (default 30 days) to save disk space. Raw data typically uses 10-50 MB per project per week.
Can I analyze code on specific branches?
Yes, use parameter `-Dsonar.branch.name=feature-xyz` when running sonar-scanner. (Note: Community Edition requires Developer+ Edition for full branch analysis.) GitLab CI auto-detects branches; Jenkins requires manual parameter in Jenkinsfile: `-Dsonar.branch.name=$GIT_BRANCH`. Main branch shows branch-aware metrics.
For a Singapore VPS that handles heavy DevOps workloads, check out asiagb Singapore VPS — low latency, 99% uptime, ideal for development teams. Before choosing your server, audit your domain health at dnsxray.com.