how-to

How to Setup SonarQube - Complete Docker, Scanner, and CI/CD Guide

Set up SonarQube with Docker Compose, SonarScanner, quality gates, and GitHub Actions CI/CD - from Community Edition to enterprise deployment.

Published:

SonarQube is the most widely adopted static analysis platform in the industry, used by over 7 million developers to catch bugs, vulnerabilities, and code smells before they reach production. Whether you are a solo developer running the free Community Edition or an enterprise team deploying across hundreds of repositories, this guide walks you through every step of setting up SonarQube - from a local Docker Compose install all the way to a production CI/CD pipeline with GitHub Actions and PR decoration.

By the end of this guide, you will have a fully functional SonarQube instance analyzing your code on every commit and blocking merges when quality standards are not met.

SonarQube static analysis tool homepage screenshot
SonarQube homepage

Prerequisites

Before you begin, make sure you have the following installed on your machine:

  • Docker and Docker Compose - version 20.10 or later for Docker, version 2.0 or later for Compose
  • Git - for cloning your project repository
  • At least 4 GB of RAM allocated to Docker - SonarQube’s embedded Elasticsearch is memory-intensive
  • A GitHub account - if you plan to set up CI/CD integration (optional for local-only use)

You will also need a project to scan. SonarQube supports over 30 languages out of the box, including Java, JavaScript, TypeScript, Python, C#, Go, PHP, Ruby, Kotlin, and Swift. If you have any codebase available, you can use it to follow along.

Step 1: Set up SonarQube with Docker Compose

The fastest and most reliable way to get SonarQube running locally is with Docker Compose. This approach bundles SonarQube and its PostgreSQL database into a single configuration file that you can start with one command.

Create the Docker Compose file

Create a new directory for your SonarQube setup and add a docker-compose.yml file:

mkdir sonarqube-setup && cd sonarqube-setup

Now create the docker-compose.yml file with the following content:

version: "3.8"

services:
  sonarqube:
    image: sonarqube:lts-community
    container_name: sonarqube
    depends_on:
      db:
        condition: service_healthy
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonarqube
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar_password
    ports:
      - "9000:9000"
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    networks:
      - sonarnet
    restart: unless-stopped

  db:
    image: postgres:16
    container_name: sonarqube-db
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar_password
      POSTGRES_DB: sonarqube
    volumes:
      - postgresql_data:/var/lib/postgresql/data
    networks:
      - sonarnet
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sonar -d sonarqube"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql_data:

networks:
  sonarnet:
    driver: bridge

This configuration defines two services. The sonarqube service uses the official LTS Community Edition image and connects to a PostgreSQL 16 database. Named volumes ensure that your data, plugins, and logs persist across container restarts. The health check on the database service prevents SonarQube from starting before PostgreSQL is ready to accept connections.

Set the kernel parameters

SonarQube uses Elasticsearch internally, which requires specific kernel settings. On Linux, run the following commands:

# Set temporarily (resets on reboot)
sudo sysctl -w vm.max_map_count=524288
sudo sysctl -w fs.file-max=131072

# Set permanently
echo "vm.max_map_count=524288" | sudo tee -a /etc/sysctl.conf
echo "fs.file-max=131072" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

On macOS with Docker Desktop, these settings are managed automatically. On Windows with WSL2, run the sysctl commands inside your WSL distribution.

Start the services

Launch SonarQube and PostgreSQL with a single command:

docker compose up -d

Watch the logs to confirm everything starts correctly:

docker compose logs -f sonarqube

You should see output indicating that the web server, compute engine, and Elasticsearch are all operational. The startup process typically takes 60 to 120 seconds. When you see SonarQube is operational, the server is ready.

Access the web interface

Open your browser and navigate to http://localhost:9000. You will see the SonarQube login page. The default credentials are:

  • Username: admin
  • Password: admin

SonarQube will immediately prompt you to change the default password. Choose a strong password and save it - you will need it for API access and scanner configuration.

Step 2: Initial SonarQube configuration

With SonarQube running, there are a few configuration steps to complete before scanning your first project.

Create a project

From the SonarQube dashboard, click Create Project and select Manually. Enter the following details:

  • Project display name - a human-readable name like “My Web App”
  • Project key - a unique identifier like my-web-app (no spaces, used in configuration files)

Click Set Up and you will be asked how you want to analyze your repository. For now, select Locally - we will configure CI/CD integration later.

Generate an authentication token

SonarQube will prompt you to generate a token for the scanner. Give the token a descriptive name like local-scanner and click Generate. Copy this token and save it securely - it is shown only once.

You can also generate tokens at any time by navigating to My Account then Security then Generate Tokens.

Configure the quality profile

Quality profiles define which rules SonarQube applies to your code. SonarQube ships with a built-in “Sonar Way” profile for each language, which is a curated set of rules that balances thoroughness with low false positive rates.

For most teams, the default Sonar Way profile is a good starting point. To review or customize it:

  1. Navigate to Quality Profiles in the top menu
  2. Select the language you want to configure
  3. Click on the profile name to see all active rules
  4. Click Activate More to add additional rules, or click the rule count to deactivate rules that generate too much noise

If you want to create a custom profile, click Create and select Copy from an existing profile. This lets you start from Sonar Way and adjust from there without modifying the default.

Step 3: Install and configure SonarScanner

SonarScanner is the client-side tool that analyzes your source code and sends the results to your SonarQube server. There are several scanner variants depending on your build system.

SonarScanner CLI (language-agnostic)

The SonarScanner CLI works with any project regardless of build system. Install it using one of the following methods:

# macOS with Homebrew
brew install sonar-scanner

# Linux - download and extract
wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-6.2.1.4610-linux-x64.zip
unzip sonar-scanner-cli-6.2.1.4610-linux-x64.zip
export PATH="$PATH:$(pwd)/sonar-scanner-6.2.1.4610-linux-x64/bin"

# Docker (no install needed)
docker run --rm \
  -e SONAR_HOST_URL="http://host.docker.internal:9000" \
  -e SONAR_TOKEN="your-token-here" \
  -v "$(pwd):/usr/src" \
  sonarsource/sonar-scanner-cli

SonarScanner for Maven

If you are working with a Java Maven project, use the built-in Maven plugin instead of the CLI:

mvn clean verify sonar:sonar \
  -Dsonar.host.url=http://localhost:9000 \
  -Dsonar.token=your-token-here

SonarScanner for Gradle

For Gradle projects, add the SonarQube plugin to your build.gradle:

plugins {
    id "org.sonarqube" version "5.1.0.4882"
}

sonar {
    properties {
        property "sonar.host.url", "http://localhost:9000"
        property "sonar.token", System.getenv("SONAR_TOKEN")
    }
}

Then run:

SONAR_TOKEN=your-token-here ./gradlew sonar

Step 4: Create the sonar-project.properties file

For the SonarScanner CLI, you need a sonar-project.properties file in your project root. This file tells the scanner what to analyze and how to connect to your SonarQube instance.

Here is a comprehensive example for a JavaScript/TypeScript project:

# Project identification
sonar.projectKey=my-web-app
sonar.projectName=My Web App
sonar.projectVersion=1.0.0

# Server connection
sonar.host.url=http://localhost:9000
sonar.token=your-token-here

# Source configuration
sonar.sources=src
sonar.tests=tests,__tests__,src/**/*.test.ts,src/**/*.spec.ts
sonar.sourceEncoding=UTF-8

# Exclusions - files to skip during analysis
sonar.exclusions=\
  **/node_modules/**,\
  **/dist/**,\
  **/build/**,\
  **/coverage/**,\
  **/*.min.js,\
  **/*.bundle.js,\
  **/vendor/**,\
  **/migrations/**

# Test exclusions - don't flag issues in test files
sonar.test.exclusions=\
  **/*.test.ts,\
  **/*.spec.ts,\
  **/*.test.js,\
  **/*.spec.js,\
  **/tests/**,\
  **/__tests__/**

# Coverage report paths
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.typescript.lcov.reportPaths=coverage/lcov.info

# Language-specific settings
sonar.typescript.tsconfigPaths=tsconfig.json

Here is an equivalent example for a Python project:

# Project identification
sonar.projectKey=my-python-app
sonar.projectName=My Python App
sonar.projectVersion=1.0.0

# Server connection
sonar.host.url=http://localhost:9000
sonar.token=your-token-here

# Source configuration
sonar.sources=src
sonar.tests=tests
sonar.sourceEncoding=UTF-8

# Exclusions
sonar.exclusions=\
  **/venv/**,\
  **/.venv/**,\
  **/migrations/**,\
  **/__pycache__/**,\
  **/static/**

# Coverage
sonar.python.coverage.reportPaths=coverage.xml

# Python version
sonar.python.version=3.12

And for a Java project using the CLI scanner:

# Project identification
sonar.projectKey=my-java-app
sonar.projectName=My Java App
sonar.projectVersion=1.0.0

# Server connection
sonar.host.url=http://localhost:9000
sonar.token=your-token-here

# Source and binary configuration
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.java.binaries=target/classes
sonar.java.test.binaries=target/test-classes
sonar.sourceEncoding=UTF-8

# Coverage
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml

# Exclusions
sonar.exclusions=\
  **/generated/**,\
  **/dto/**/*DTO.java

Run your first scan

With the properties file in place, navigate to your project root and run the scanner:

sonar-scanner

The scanner will analyze your source code, upload the results, and print a URL where you can view the analysis. Open that URL or go to your SonarQube dashboard to see the results. You should see metrics for bugs, vulnerabilities, code smells, coverage, and duplication.

Step 5: Configure quality gates

Quality gates are the heart of SonarQube’s value in a CI/CD pipeline. A quality gate defines the conditions your code must meet to pass. When integrated with your CI, a failing quality gate blocks the merge - ensuring that code that does not meet your standards never reaches production.

Understanding the default quality gate

SonarQube ships with a built-in quality gate called Sonar Way that focuses on new code. Its conditions are:

  • Zero new bugs
  • Zero new vulnerabilities
  • Zero new security hotspots reviewed as unsafe
  • Coverage on new code is at least 80%
  • Duplication on new code is less than 3%

This “Clean as You Code” approach is intentional. Rather than forcing teams to fix every issue in their entire codebase before they can deploy, Sonar Way only evaluates code that changed. Existing technical debt does not block your pull requests.

Create a custom quality gate

For many teams, the default gate works well. But if you need to adjust thresholds - for example, if 80% coverage on new code is unrealistic for your team right now - you can create a custom gate:

  1. Navigate to Quality Gates in the top menu
  2. Click Create
  3. Name it something descriptive like “Team Standard”
  4. Click Add Condition and configure your thresholds:

Here are recommended conditions for a balanced quality gate:

MetricOperatorValue
New Bugsis greater than0
New Vulnerabilitiesis greater than0
New Security Hotspots Reviewedis less than100%
Coverage on New Codeis less than70%
Duplicated Lines on New Codeis greater than5%
Maintainability Rating on New Codeis worse thanA
  1. Click Set as Default to apply this gate to all projects, or assign it to specific projects via the project settings

Assign a quality gate to a project

If you have multiple projects with different standards, assign gates individually:

  1. Open the project in SonarQube
  2. Navigate to Project Settings then Quality Gate
  3. Select the desired quality gate from the dropdown

Step 6: Set up GitHub Actions CI/CD

Running SonarQube in your CI/CD pipeline ensures every pull request and push is analyzed automatically. This section covers two approaches: using SonarQube Cloud (no server to manage) and using a self-hosted SonarQube Server instance.

Option A: GitHub Actions with SonarQube Cloud

SonarQube Cloud is the easiest path - no server infrastructure to maintain. Start by creating an account at sonarcloud.io and importing your repository.

Add these secrets to your GitHub repository settings under Settings then Secrets and variables then Actions:

  • SONAR_TOKEN - your SonarQube Cloud token (generate at My Account then Security)

Create the workflow file at .github/workflows/sonarqube.yml:

name: SonarQube Analysis

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  sonarqube:
    name: SonarQube Scan
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Full history required for accurate blame data

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests with coverage
        run: npm test -- --coverage --coverageReporters=lcov

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

      - name: SonarQube Quality Gate
        uses: SonarSource/sonarqube-quality-gate-action@v1
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

For SonarQube Cloud, your sonar-project.properties file should include the organization:

sonar.projectKey=your-org_your-repo
sonar.organization=your-org
sonar.sources=src
sonar.tests=tests
sonar.exclusions=**/node_modules/**,**/dist/**
sonar.javascript.lcov.reportPaths=coverage/lcov.info

Notice that sonar.host.url and sonar.token are omitted from the properties file - the GitHub Action passes them automatically via environment variables.

Option B: GitHub Actions with self-hosted SonarQube Server

If you are running SonarQube on your own infrastructure (the Docker Compose setup from earlier, or a dedicated VM), the workflow is similar but requires an additional secret for the server URL.

Add these secrets to your GitHub repository:

  • SONAR_TOKEN - your SonarQube server token
  • SONAR_HOST_URL - your SonarQube server URL (for example, https://sonarqube.yourcompany.com)

Create the workflow file at .github/workflows/sonarqube.yml:

name: SonarQube Analysis

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  sonarqube:
    name: SonarQube Scan
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests with coverage
        run: npm test -- --coverage --coverageReporters=lcov

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

      - name: SonarQube Quality Gate
        uses: SonarSource/sonarqube-quality-gate-action@v1
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

For a Python project, replace the Node.js and test steps:

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov

      - name: Run tests with coverage
        run: pytest --cov=src --cov-report=xml:coverage.xml

For a Java Maven project:

      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'
          cache: 'maven'

      - name: Build and test with coverage
        run: mvn clean verify

      - name: SonarQube Scan
        run: mvn sonar:sonar -Dsonar.token=${{ secrets.SONAR_TOKEN }} -Dsonar.host.url=${{ secrets.SONAR_HOST_URL }}

Understanding the quality gate action

The sonarqube-quality-gate-action step is what makes SonarQube useful in CI/CD. After the scan completes, this action polls the SonarQube server for the quality gate result. If the gate passes, the GitHub check succeeds and the PR can be merged. If it fails, the check fails and the merge is blocked (assuming you have configured branch protection to require this check).

The timeout-minutes: 5 setting prevents the action from hanging indefinitely if SonarQube is slow to compute the quality gate result. In practice, results are usually available within 30 to 60 seconds after the scan.

Step 7: Configure PR decoration

PR decoration is the feature that posts SonarQube analysis results directly on your pull request as a comment and inline annotations. This gives developers immediate feedback without needing to open the SonarQube dashboard.

Requirements

PR decoration requires one of the following:

  • SonarQube Cloud (any tier including free)
  • SonarQube Developer Edition or higher (self-hosted)
  • The Community Edition does not support PR decoration

Set up GitHub integration in SonarQube Server

For self-hosted SonarQube Developer Edition and above:

  1. Navigate to Administration then Configuration then General Settings then DevOps Platform Integrations then GitHub
  2. Click Create Configuration
  3. Fill in the details:
    • GitHub API URL - https://api.github.com/ for GitHub.com
    • GitHub App ID - create a GitHub App for SonarQube (instructions below)
    • Client ID and Client Secret - from your GitHub App
    • Private Key - upload the private key generated for your GitHub App

Create a GitHub App for SonarQube

  1. Go to GitHub Settings then Developer Settings then GitHub Apps then New GitHub App
  2. Set the following:
    • App name - “SonarQube - YourCompany”
    • Homepage URL - your SonarQube server URL
    • Callback URL - https://your-sonarqube-url/oauth2/callback/github
    • Webhook URL - leave blank
    • Permissions:
      • Repository permissions:
        • Checks: Read & Write
        • Contents: Read-only
        • Metadata: Read-only
        • Pull requests: Read & Write
        • Commit statuses: Read-only
  3. Generate a private key and download it
  4. Install the App on your GitHub organization
  5. Enter the App ID, Client ID, Client Secret, and Private Key in SonarQube

Pass PR parameters in your CI workflow

For PR decoration to work with a self-hosted instance, the scanner needs to know it is analyzing a pull request. Update your GitHub Actions workflow to pass PR parameters:

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
        with:
          args: >
            -Dsonar.pullrequest.key=${{ github.event.pull_request.number }}
            -Dsonar.pullrequest.branch=${{ github.head_ref }}
            -Dsonar.pullrequest.base=${{ github.base_ref }}
        if: github.event_name == 'pull_request'

      - name: SonarQube Scan (branch)
        uses: SonarSource/sonarqube-scan-action@v5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
        with:
          args: >
            -Dsonar.branch.name=${{ github.ref_name }}
        if: github.event_name == 'push'

This conditional setup ensures that PR events pass pull request parameters while push events pass branch parameters. SonarQube uses this information to generate PR-specific reports and post the decoration on the correct pull request.

What PR decoration looks like

Once configured, SonarQube will post a summary comment on each pull request that includes:

  • Quality gate status - passed or failed, with specific conditions that triggered the failure
  • Issue counts - number of new bugs, vulnerabilities, code smells, and security hotspots
  • Coverage - coverage percentage on new code versus the threshold
  • Duplication - duplication percentage on new code
  • Inline annotations - specific issues annotated directly on the changed lines in the diff

This gives developers all the information they need to address issues before requesting human review.

Step 8: Branch protection and merge gating

To enforce quality standards, configure GitHub branch protection rules so that the SonarQube quality gate check must pass before a pull request can be merged.

  1. Navigate to your GitHub repository Settings then Branches
  2. Click Add branch protection rule (or edit the existing rule for main)
  3. Configure the following:
Branch name pattern: main

[x] Require a pull request before merging
    [x] Require approvals: 1

[x] Require status checks to pass before merging
    [x] Require branches to be up to date before merging
    Required status checks:
      - SonarQube Scan
      - SonarQube Quality Gate

[x] Require conversation resolution before merging

With this configuration, every pull request targeting main must pass the SonarQube quality gate. If SonarQube detects new bugs, vulnerabilities, or insufficient coverage, the merge button will be disabled until the issues are resolved.

Step 9: Advanced configuration

Exclude files and directories

Fine-tuning exclusions is essential for reducing false positives and scan times. Here are common exclusion patterns:

# Generated code
sonar.exclusions=\
  **/generated/**,\
  **/*.generated.ts,\
  **/proto/**/*.go,\
  **/*.pb.go

# Third-party and vendored code
sonar.exclusions=**/vendor/**,**/third_party/**

# Build artifacts
sonar.exclusions=**/dist/**,**/build/**,**/out/**

# Configuration and non-code files
sonar.exclusions=**/*.json,**/*.yml,**/*.yaml,**/*.xml,**/*.md

# Test data and fixtures
sonar.exclusions=**/testdata/**,**/fixtures/**,**/__fixtures__/**

Configure New Code definition

The New Code definition determines what SonarQube considers “new code” for quality gate evaluation. There are three options:

  1. Previous Version - compares against the last analyzed version tag (best for release-based workflows)
  2. Number of Days - considers all code changed in the last N days as new (best for continuous delivery)
  3. Reference Branch - compares against a specific branch like main (best for feature branch workflows)

Set it at the project level via Project Settings then New Code or globally via Administration then Configuration then General Settings then New Code.

For most teams using feature branches and pull requests, the Reference Branch option set to main is the best choice. This means every PR is evaluated only on the code it introduces relative to main.

Multi-module projects

For monorepos or multi-module projects, you can define separate modules in a single sonar-project.properties file:

sonar.projectKey=my-monorepo
sonar.projectName=My Monorepo

# Define modules
sonar.modules=api,web,shared

# API module
api.sonar.projectName=API Service
api.sonar.sources=api/src
api.sonar.tests=api/tests
api.sonar.exclusions=api/src/generated/**

# Web module
web.sonar.projectName=Web Frontend
web.sonar.sources=web/src
web.sonar.tests=web/tests
web.sonar.javascript.lcov.reportPaths=web/coverage/lcov.info

# Shared library
shared.sonar.projectName=Shared Library
shared.sonar.sources=shared/src
shared.sonar.tests=shared/tests

Integrate coverage reports

SonarQube does not run your tests - it reads coverage reports generated by your test framework. Ensure your CI pipeline generates coverage before the SonarQube scan runs. Here are the coverage report properties for common languages:

# JavaScript/TypeScript (Istanbul/NYC)
sonar.javascript.lcov.reportPaths=coverage/lcov.info

# Python (pytest-cov or coverage.py)
sonar.python.coverage.reportPaths=coverage.xml

# Java (JaCoCo)
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml

# Go (go test)
sonar.go.coverage.reportPaths=coverage.out

# C# (.NET)
sonar.cs.opencover.reportsPaths=**/coverage.opencover.xml

Troubleshooting common issues

Even with a correct configuration, you may run into issues during setup. Here are the most common problems and their solutions.

SonarQube fails to start with “max virtual memory areas” error

Symptom: The SonarQube container exits immediately with a log message about vm.max_map_count.

Fix: Set the kernel parameter as described in Step 1:

sudo sysctl -w vm.max_map_count=524288

On Docker Desktop for Mac, open the Docker Desktop settings, go to Resources then Advanced, and ensure at least 4 GB of memory is allocated.

Scanner cannot connect to SonarQube server

Symptom: The scanner exits with a connection refused or timeout error.

Fix: Verify the server is running and accessible:

curl -s http://localhost:9000/api/system/status

If you are running the scanner inside Docker while SonarQube is also in Docker, use the Docker network hostname or host.docker.internal instead of localhost:

sonar.host.url=http://host.docker.internal:9000

Quality gate shows “Not Computed”

Symptom: The quality gate status on the project dashboard shows “Not Computed” instead of Passed or Failed.

Fix: This usually means the quality gate conditions reference metrics that are not available. The most common cause is missing coverage data. Ensure your test framework is generating a coverage report in the expected format and that the sonar.*.reportPaths property points to the correct file. Run your tests before running the scanner.

PR decoration is not appearing

Symptom: The SonarQube scan runs successfully but no comment appears on the pull request.

Fix: Check the following in order:

  1. Verify you are using Developer Edition or higher (or SonarQube Cloud) - Community Edition does not support PR decoration
  2. Confirm the GitHub App integration is configured correctly in Administration then DevOps Platform Integrations
  3. Ensure the PR parameters are being passed in your CI workflow (sonar.pullrequest.key, sonar.pullrequest.branch, sonar.pullrequest.base)
  4. Check the SonarQube server logs at /opt/sonarqube/logs/web.log for any errors related to GitHub API calls

Scan takes too long

Symptom: The SonarQube scan takes 10 minutes or more for a medium-sized project.

Fix: Review your exclusion configuration. The most common cause of slow scans is analyzing files that should be excluded - node_modules, vendored dependencies, generated code, and build artifacts. Add comprehensive exclusion patterns to sonar-project.properties. Also check that sonar.java.binaries (for Java projects) points to the correct directory - scanning source files without compiled binaries forces SonarQube into a slower analysis mode.

Out of memory during analysis

Symptom: The scanner crashes with java.lang.OutOfMemoryError.

Fix: Increase the scanner’s heap size:

export SONAR_SCANNER_OPTS="-Xmx2048m"
sonar-scanner

In a GitHub Actions workflow:

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
          SONAR_SCANNER_OPTS: "-Xmx2048m"

Duplicate key error when creating a project

Symptom: SonarQube returns an error saying the project key already exists.

Fix: Project keys must be unique across the entire SonarQube instance. If you are re-creating a project, delete the old one first via Project Settings then Deletion. For new projects, use a namespaced key format like org-name_repo-name to avoid collisions.

Complete working example

Here is a complete, copy-ready setup for a TypeScript project that ties together everything covered in this guide.

sonar-project.properties:

sonar.projectKey=my-org_my-app
sonar.projectName=My App
sonar.sources=src
sonar.tests=src
sonar.test.inclusions=**/*.test.ts,**/*.spec.ts
sonar.exclusions=**/node_modules/**,**/dist/**,**/coverage/**
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.typescript.tsconfigPaths=tsconfig.json
sonar.sourceEncoding=UTF-8

.github/workflows/sonarqube.yml:

name: SonarQube Analysis

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  pull-requests: read

jobs:
  sonarqube:
    name: SonarQube
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Run tests with coverage
        run: npm test -- --coverage --coverageReporters=lcov

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

      - name: Quality Gate
        uses: SonarSource/sonarqube-quality-gate-action@v1
        timeout-minutes: 5
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

docker-compose.yml (for the self-hosted SonarQube server):

version: "3.8"

services:
  sonarqube:
    image: sonarqube:lts-community
    depends_on:
      db:
        condition: service_healthy
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonarqube
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar_password
    ports:
      - "9000:9000"
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar_password
      POSTGRES_DB: sonarqube
    volumes:
      - postgresql_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sonar -d sonarqube"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql_data:

Next steps

With SonarQube running in your CI/CD pipeline, you have a solid foundation for automated code quality enforcement. Here are the recommended next steps to get even more value from your setup:

  • Connect SonarLint to your IDE for real-time feedback as you code, using the same rules as your SonarQube server
  • Add coverage thresholds to your quality gate once your team has a baseline coverage measurement
  • Write custom rules using SonarQube’s rule templates for organization-specific coding standards
  • Set up webhooks to notify your team’s Slack or Microsoft Teams channel when a quality gate fails
  • Review the security hotspots regularly - these are potential vulnerabilities that require human judgment to classify as safe or unsafe
  • Pair SonarQube with an AI review tool like CodeRabbit or PR-Agent for contextual feedback that static analysis cannot provide

SonarQube handles the deterministic, rule-based layer of code review exceptionally well. Combined with linting and AI-powered review, it forms a comprehensive automated review stack that catches the vast majority of issues before a human reviewer ever opens the pull request.

Frequently Asked Questions

How do I setup SonarQube locally with Docker?

Run SonarQube locally with Docker Compose by creating a docker-compose.yml that defines a SonarQube service and a PostgreSQL database service. Map port 9000 for the web interface, set the JDBC connection environment variables to point SonarQube at the PostgreSQL container, and use named volumes for persistent data. Run docker compose up -d, then open http://localhost:9000 and log in with admin/admin.

What are the system requirements for SonarQube?

SonarQube requires at least 2 GB of RAM for the Community Edition and 4 GB or more for Developer and Enterprise editions. It needs Java 17 runtime, a supported database (PostgreSQL 13 to 16 recommended), and a Linux or macOS host with vm.max_map_count set to at least 524288 and fs.file-max set to at least 131072. The Docker image handles the Java requirement automatically.

How do I connect SonarQube to GitHub Actions?

Add the SonarSource/sonarqube-scan-action to your GitHub Actions workflow file. Store your SonarQube token as a GitHub secret named SONAR_TOKEN and your server URL as SONAR_HOST_URL. Create a sonar-project.properties file in your repository root with your project key and source directories. The action runs the scanner and reports results back to SonarQube for quality gate evaluation.

What is the difference between SonarQube Community and Developer Edition?

SonarQube Community Edition is free and open source but only supports main branch analysis with no pull request decoration or branch analysis. Developer Edition adds branch analysis, PR decoration, secrets detection, taint analysis, and support for 35 or more languages. Community Edition supports around 20 languages. For teams that need PR-level feedback in their CI/CD workflow, Developer Edition or SonarQube Cloud is required.

How do I configure quality gates in SonarQube?

Navigate to Quality Gates in the SonarQube web interface. Either customize the built-in Sonar Way gate or create a new one. Add conditions such as zero new bugs, zero new vulnerabilities, at least 80 percent coverage on new code, and less than 3 percent duplication on new code. Assign the gate to your project. When a scan runs, SonarQube evaluates the code against these conditions and passes or fails the gate accordingly.

How do I scan a project with SonarScanner?

Install SonarScanner CLI, then create a sonar-project.properties file in your project root specifying sonar.projectKey, sonar.sources, sonar.host.url, and sonar.token. Run the sonar-scanner command from the project root. The scanner uploads the analysis to SonarQube, where you can view results in the web interface. For Maven or Gradle projects, use the built-in scanner plugin instead of the CLI.

Why does SonarQube show 'not enough memory' or crash on startup?

SonarQube's embedded Elasticsearch requires the vm.max_map_count kernel parameter to be set to at least 524288. On Linux, run sysctl -w vm.max_map_count=524288 to set it temporarily, or add vm.max_map_count=524288 to /etc/sysctl.conf for persistence. On Docker Desktop for Mac or Windows, this setting is managed automatically. Also ensure your Docker engine is allocated at least 4 GB of memory.

How do I enable PR decoration in SonarQube?

PR decoration requires SonarQube Developer Edition or higher, or SonarQube Cloud. In SonarQube, navigate to Administration, then DevOps Platform Integrations, and configure your GitHub connection with an app or personal access token. In your CI workflow, pass the PR number and branch name as scanner parameters using sonar.pullrequest.key, sonar.pullrequest.branch, and sonar.pullrequest.base. SonarQube will post analysis results as a comment on the pull request.

Can I use SonarQube with monorepos?

Yes. Create a separate SonarQube project for each service or module in the monorepo. Each project gets its own sonar-project.properties file with a unique sonar.projectKey and its own sonar.sources path pointing to the relevant directory. In your CI pipeline, use path filters to trigger scans only for the modules that changed. This avoids scanning the entire monorepo on every commit.

How do I fix the SonarQube quality gate failing on legacy code?

Use SonarQube's New Code definition to focus quality gates only on newly written or modified code. Navigate to Project Settings, then New Code, and set the reference to Previous Version or a specific date. This way, pre-existing issues in legacy code will not block your pull requests. You can address legacy issues gradually through a separate cleanup effort without disrupting active development.

What is the difference between SonarQube Server and SonarQube Cloud?

SonarQube Server (formerly SonarQube) is self-hosted - you install and manage the infrastructure yourself. SonarQube Cloud (formerly SonarCloud) is a SaaS platform hosted by SonarSource. Cloud is easier to set up with no infrastructure management, offers native GitHub and GitLab integration, and is free for public repositories. Server gives you full control over data, is required for air-gapped environments, and supports the Community Edition free tier with no line-of-code limits.

How long does a SonarQube scan take?

Scan time depends on project size, language complexity, and hardware. A small project with 10,000 lines of code typically scans in 30 to 90 seconds. Medium projects around 100,000 lines take 2 to 5 minutes. Large projects with 500,000 or more lines can take 10 to 30 minutes. Running the scanner on an SSD, allocating sufficient memory, and excluding generated or vendor code significantly reduces scan times.

How do I integrate SonarQube with SonarLint in my IDE?

Install the SonarLint extension for your IDE - it supports VS Code, IntelliJ IDEA, Eclipse, and Visual Studio. Open SonarLint settings and select Connected Mode. Add your SonarQube server URL and authentication token. Bind your local project to the matching SonarQube project. SonarLint will then use the same rules and quality profile as your server, giving you consistent feedback as you type without waiting for a CI scan.

Explore More

Tool Reviews

Free Newsletter

Stay ahead with AI dev tools

Weekly insights on AI code review, static analysis, and developer productivity. No spam, unsubscribe anytime.

Join developers getting weekly AI tool insights.

Related Articles