Install SonarQube on VPS - Code Quality Analysis

Install SonarQube on VPS - Code Quality Analysis

June 2026 · 9 min read

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

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
Tip: For production SonarQube, always use PostgreSQL instead of Embedded Database — Embedded H2 rescans slowly on large projects and can lose data if the server crashes. PostgreSQL is stable and scalable.

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
Tip: If SonarQube is slow, check embedded Elasticsearch — open dashboard and view log for index segments. If more than 100, run optimize (Admin → System → Maintenance → Reindex old analysis).

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.