Skip to main content

Command Palette

Search for a command to run...

Integrating SonarQube with Jenkins for Continuous Code Quality Analysis

Every CI/CD pipeline has a code quality gate. Without it, you are shipping code that might compile and deploy successfully but is full of bugs, security vulnerabilities, duplicated logic, and poor practices. SonarQube is the tool that fills this gap. It scans your source code, measures its quality, and reports back everything a team needs to know before approving a deployment. In this post, we will set up SonarQube on a dedicated EC2 instance, integrate it with Jenkins, and add a code quality analysis stage to our pipeline.

Updated
10 min read
Integrating SonarQube with Jenkins for Continuous Code Quality Analysis
S
Passionate about Cloud and DevOps engineering. I write structured technical notes and beginner-friendly articles on AWS, Linux, CI/CD, networking, system architecture and modern software delivery workflows.

What SonarQube Actually Does

SonarQube is an open-source platform for continuous code quality and security. When it scans your code, it looks for:

  • Bugs - code that will likely produce incorrect behavior at runtime

  • Vulnerabilities - security weaknesses that attackers could exploit

  • Code smells - maintainability issues that make the code harder to understand and modify

  • Duplications - blocks of identical or near-identical code repeated across the codebase

  • Coverage - how much of the code is covered by unit tests

After a scan, SonarQube produces a dashboard report. Each project gets a quality gate result: either Passed or Failed. A passed quality gate means the code meets the standards your team has defined. A failed quality gate signals that problems need to be fixed before the code is allowed to proceed to deployment.

SonarQube supports over 20 programming languages. It is not just for Java - Python, JavaScript, TypeScript, C#, Go, and many others are supported.

The SonarQube Architecture

SonarQube has three internal components:

  1. SonarQube Scanner - runs in the build environment (on the Jenkins server or slave node), analyzes the source code, and submits results to the server.

  2. SonarQube Server - receives the analysis data, processes it, applies quality gate rules, and makes results available through the dashboard.

  3. SonarQube Database - stores all historical scan results. This is set up automatically as part of the SonarQube installation.

You interact with the SonarQube Server through its web dashboard. Jenkins runs the Scanner as part of the pipeline.

DevSecOps Connection

SonarQube is a DevSecOps tool. DevSecOps means integrating security into the development and operations process rather than treating it as a separate, final step. Running SonarQube on every commit means security vulnerabilities are caught early in the development cycle - when they are cheapest to fix.


Infrastructure Requirements

SonarQube requires at least a t2.medium instance. Do not try to run it on a t2.micro. The process will either fail to start or be so slow it becomes unusable.

You can run SonarQube on Amazon Linux 2. The setup script (sonarqube.sh) handles the full installation automatically:

#!/bin/bash

# Change to the /opt directory
cd /opt/

# Install required packages (wget, unzip, Java 11)
dnf install wget unzip java-11-amazon-corretto -y

# Download SonarQube 8.9.6
wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-8.9.6.50800.zip

# Extract it
unzip sonarqube-8.9.6.50800.zip

# Create a dedicated user to run SonarQube
useradd sonar

# Set ownership
chown -R sonar:sonar /opt/sonarqube-8.9.6.50800

# Set permissions
chmod -R 755 /opt/sonarqube-8.9.6.50800

# Required kernel settings (SonarQube needs these to function correctly)
echo "vm.max_map_count=262144" >> /etc/sysctl.conf
echo "fs.file-max=65536" >> /etc/sysctl.conf
sysctl -p

# Increase OS limits for the sonar user
cat >> /etc/security/limits.conf <<EOF
sonar   -   nofile   65536
sonar   -   nproc    4096
EOF

# Set Java 11 as the default for this session
alternatives -set java /usr/lib/jvm/java-11-amazon-corretto.x86_64/bin/java

# Start SonarQube as the sonar user
su - sonar -c "/opt/sonarqube-8.9.6.50800/bin/linux-x86-64/sonar.sh start"

Why a dedicated user? Running SonarQube (or any service) as root is a security risk. If the process is compromised, the attacker has root access. A dedicated sonar user limits the blast radius.

Why Java 11 specifically? SonarQube 8.9.x has specific Java version requirements. It is compatible with Java 11 but not with later versions. Always check the SonarQube documentation for your version's supported JDK range.


Running the Setup

Copy the sonarqube.sh script to your SonarQube EC2 instance and execute it:

chmod +x sonarqube.sh
sudo bash sonarqube.sh

SonarQube takes a minute or two to fully initialize. The script starts it with the sonar.sh start command. You do not need to run that again separately.


Accessing SonarQube

SonarQube runs on port 9000. Open in your browser:

http://<sonarqube-ec2-public-ip>:9000

Default credentials:

  • Username: admin

  • Password: admin

You will be prompted to change the password on first login.


Generating a SonarQube Authentication Token

Jenkins needs to authenticate with SonarQube to submit analysis results. SonarQube uses tokens for this rather than passwords.

To generate a token:

  1. Log in to SonarQube.

  2. Click Administration (or your profile icon) and select Security.

  3. Under Tokens, enter a name (for example, jenkins-token) and click Generate.

  4. Copy the generated token immediately - you will not be able to see it again after navigating away.


Installing Jenkins Plugins for SonarQube

Back in Jenkins:

  1. Go to Manage Jenkins.

  2. Click Plugins, then Available plugins.

  3. Search for and install the following:

    • SonarQube Scanner - provides the scanner integration and pipeline steps.

    • Sonar Quality Gates - allows pipelines to fail based on SonarQube quality gate results.

    • Maven Integration Plugin - if not already installed.

  4. Click Install and restart Jenkins.


Adding the SonarQube Token as a Jenkins Credential

Jenkins needs to store the SonarQube token securely:

  1. Go to Manage Jenkins, Credentials, Global credentials, Add Credentials.

  2. Fill in:

    • Kind: Secret text

    • Secret: Paste the SonarQube token you generated

    • ID: sonar-token

    • Description: SonarQube authentication token

  3. Click Create.


Configuring the SonarQube Server in Jenkins

  1. Go to Manage Jenkins.

  2. Click System.

  3. Scroll down to find the SonarQube servers section.

  4. Check Enable injection of SonarQube server configuration as build environment variables.

  5. Click Add SonarQube.

  6. Fill in:

    • Name: SonarQube (this name is referenced in pipeline scripts)

    • Server URL: http://<sonarqube-ec2-public-ip>:9000

    • Server authentication token: Select sonar-token

  7. Click Save.


Configuring the SonarQube Scanner Tool

The SonarQube Scanner is the component that actually runs code analysis. Jenkins needs to know where to find it (or download it):

  1. Go to Manage Jenkins.

  2. Click Tools.

  3. Scroll to the SonarQube Scanner section.

  4. Click Add SonarQube Scanner.

  5. Fill in:

    • Name: SonarScanner

    • Check Install automatically (Jenkins downloads it when needed)

  6. Click Save.


Adding the Code Quality Stage to Your Pipeline

Now everything is wired up. Adding SonarQube analysis to your pipeline requires a specific code block that runs the scanner with the right configuration.

The key step uses withSonarQubeEnv, which injects the SonarQube server URL and credentials into the build environment automatically:

stage('Code Quality Check') {
    steps {
        withSonarQubeEnv('SonarQube') {
            sh '''
                mvn sonar:sonar \
                    -Dsonar.projectKey=my-java-app \
                    -Dsonar.sources=src \
                    -Dsonar.java.binaries=target
            '''
        }
    }
}

The 'SonarQube' string inside withSonarQubeEnv must match the Name you gave the SonarQube server in the Jenkins system configuration.


The Complete CI Pipeline with SonarQube

Here is the full pipeline with all stages integrated:

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/your-username/java-app.git'
            }
        }
        stage('Compile') {
            steps {
                sh 'mvn compile'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Code Quality Check') {
            steps {
                withSonarQubeEnv('SonarQube') {
                    sh '''
                        mvn sonar:sonar \
                            -Dsonar.projectKey=my-java-app \
                            -Dsonar.sources=src \
                            -Dsonar.java.binaries=target
                    '''
                }
            }
        }
        stage('Package') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Upload Artifact') {
            steps {
                echo 'Uploading WAR to S3 or Nexus...'
            }
        }
        stage('Deploy to Tomcat') {
            steps {
                deploy adapters: [tomcat9(credentialsId: 'tomcat-creds',
                                           path: '',
                                           url: 'http://<tomcat-ip>:8080')],
                       contextPath: 'my-web-app',
                       war: '**/*.war'
            }
        }
    }
}

Every stage in the CI/CD sequence is now present: Checkout, Compile, Test, Code Quality Check, Package, Upload Artifact, Deploy.


Reading the SonarQube Dashboard

After the pipeline runs, open the SonarQube dashboard:

http://<sonarqube-ip>:9000

Click on your project to see the analysis results. The dashboard shows:

  • Quality Gate status: Passed or Failed

  • Bugs count

  • Vulnerabilities count

  • Code Smells count with severity levels

  • Duplications percentage

  • Lines of code analyzed

One common finding on fresh projects is commented-out code. SonarQube flags this as a code smell:

"Remove this commented-out code."

This is a best-practice enforcement. Commented code should not live in the main branch. If you need to refer to old code, version control (Git) stores the entire history. You can always check out an older commit.

The DevOps engineer's job here is to set up SonarQube, run the scan, and give the dashboard access to developers. If the quality gate fails, it is the developer's responsibility to fix the issues and push a new commit. The pipeline runs again, scans again, and the cycle continues until the quality gate passes.


OWASP Dependency Check: An Additional Security Layer

SonarQube is not the only security tool you can add to a Jenkins pipeline. OWASP Dependency Check is another commonly used tool that falls under DevSecOps. It scans your project's declared dependencies (the libraries listed in pom.xml) and checks them against a public database of known vulnerabilities (CVEs - Common Vulnerabilities and Exposures).

This is different from SonarQube:

  • SonarQube checks your code for quality and security issues.

  • OWASP Dependency Check checks your dependencies (third-party libraries) for known vulnerabilities.

Both checks are necessary for a comprehensive security posture.

To add OWASP Dependency Check:

  1. Install the OWASP Dependency-Check Plugin from Jenkins plugins.

  2. In the pipeline, add a stage:

stage('OWASP Dependency Check') {
    steps {
        dependencyCheck additionalArguments: '-scan ./ -format HTML', odcInstallation: 'OWASP-DC'
        dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
    }
}

Note: The first run downloads the vulnerability database, which takes significant time (15+ minutes). Subsequent runs are much faster since the database is cached locally. For learning purposes, you can start the run and then abort it if you just want to see that it works.


Summary

SonarQube completes the testing phase of your CI/CD pipeline. Here is a recap of everything covered:

  • SonarQube analyzes code for bugs, vulnerabilities, code smells, and duplications.

  • It requires a t2.medium instance (minimum) and a dedicated user account.

  • Jenkins integrates with SonarQube via the SonarQube Scanner plugin.

  • Authentication uses a SonarQube-generated token stored in Jenkins credentials.

  • Server and scanner configuration lives in Manage Jenkins, System and Tools respectively.

  • The withSonarQubeEnv pipeline step injects credentials and runs the analysis.

  • The quality gate result (Passed/Failed) tells you whether the code meets your team's standards.

  • OWASP Dependency Check is a complementary tool that scans third-party library vulnerabilities.

With SonarQube integrated, your pipeline now covers all five stages: code (GitHub), build (Maven), test (SonarQube), artifact (S3/Nexus), deploy (Tomcat). That is a complete, production-grade Jenkins CI/CD pipeline.


The Complete Jenkins CI/CD Pipeline: Putting It All Together

Here is what the final combined pipeline looks like when every integration is in place:

pipeline {
    agent any
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/your-org/java-app.git'
            }
        }
        stage('Compile') {
            steps {
                sh 'mvn compile'
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }
        stage('Code Quality - SonarQube') {
            steps {
                withSonarQubeEnv('SonarQube') {
                    sh 'mvn sonar:sonar -Dsonar.projectKey=java-app'
                }
            }
        }
        stage('Package') {
            steps {
                sh 'mvn clean package'
            }
        }
        stage('Upload to Nexus') {
            steps {
                nexusArtifactUploader(
                    nexusVersion: 'nexus3',
                    protocol: 'http',
                    nexusUrl: '<nexus-ip>:8081',
                    groupId: 'com.example',
                    version: '1.0-SNAPSHOT',
                    repository: 'hotstar-repo',
                    credentialsId: 'nexus-creds',
                    artifacts: [[
                        artifactId: 'myapp',
                        classifier: '',
                        file: 'target/myapp.war',
                        type: 'war'
                    ]]
                )
            }
        }
        stage('Deploy to Tomcat') {
            steps {
                deploy adapters: [tomcat9(credentialsId: 'tomcat-creds',
                                           path: '',
                                           url: 'http://<tomcat-ip>:8080')],
                       contextPath: 'my-web-app',
                       war: '**/*.war'
            }
        }
    }
}

Every commit to the repository triggers this pipeline. Code is checked out, compiled, tested, quality-checked, packaged into a WAR, stored in Nexus, and deployed to Tomcat - all automatically, all without a human doing anything manually after the initial setup.

That is DevOps.

More from this blog

Ansible Playbooks Explained: From First YAML File to Managing Real Servers

If you have already run a few Ansible ad hoc commands and seen how they work, you already understand the core idea: one command, many servers. But ad hoc commands only take you so far. When you need to install software, start services, create users, copy files, and print confirmation messages, all in one automated run across multiple servers, that is when you move to playbooks. Playbooks are where Ansible truly earns its place in a DevOps workflow. Everything you do in Ansible at scale, you do through playbooks.

Jun 19, 202613 min read
Ansible Playbooks Explained: From First YAML File to Managing Real Servers

You Have 400 Servers to Configure. Now What? Let Ansible Do the Work.

Picture this. You have four EC2 instances running in your AWS account, and someone asks you to install Apache on all four of them. What do you do? The obvious answer most people go with is SSH into each machine, run the install command, repeat. Simple enough when it is four servers. But what happens when it is forty? Or four hundred? In a real enterprise environment, that number is not exaggerated at all. That is exactly the problem that Ansible was built to solve. And once you understand what it does and how it thinks, you will wonder how anyone managed large infrastructure without it.

Jun 18, 202612 min read
You Have 400 Servers to Configure. Now What? Let Ansible Do the Work.

Building Production-Ready Docker Deployments with Secrets, Stacks, and Distroless Images

This post wraps up the core Docker Swarm curriculum by covering four important topics that complete the picture of production-ready containerized deployments. We start with Docker Secrets, which solves the real-world problem of passing sensitive credentials into containers without hardcoding them. We then look at Docker Stack, which is how you run multi-service Docker Compose files across a Swarm cluster instead of a single host. After that we cover the distinction between replicated and global services, which is a concept that appears in Kubernetes as well. We close with a look at Portainer for those who prefer a visual interface, and a brief introduction to distroless images. Each of these topics builds on everything covered so far. If you have your Swarm cluster running, you can follow along with every command shown here.

Jun 17, 202614 min read
Building Production-Ready Docker Deployments with Secrets, Stacks, and Distroless Images

Beyond One Server: Solving Docker Scaling with Swarm and Container Networks

This post covers three separate but deeply connected topics. We start by finishing what was started with Docker Hub, pushing all four bank service images to a remote registry so they survive beyond any single machine. From there, we identify a real architectural problem with single-host Docker deployments and introduce Docker Swarm as the solution. Finally, we close with Docker networking, explaining how containers communicate with each other both on the same host and across different hosts. By the end of this article, you will understand how to push and pull images from Docker Hub, how to set up a multi-node Docker Swarm cluster, how to create and scale services across that cluster, what self-healing means in practice, and how Docker networking works under the hood.

Jun 17, 202618 min read
Beyond One Server: Solving Docker Scaling with Swarm and Container Networks

Docker Compose in Action: Multi-Container Apps, Nginx Load Balancing & Docker Hub

We built four containerized microservices for an bank application in the last post. Internet banking, mobile banking, insurance, and loans, each running in its own container, each exposed on a separate port. The setup worked. But the process of building and running each container individually by hand was repetitive, error-prone, and simply not practical at scale. This post introduces Docker Compose, and by the end, you will understand not just how to use it, but why it exists, what its real limitations are, how to combine it with Nginx to build a working high availability architecture, and how to push your images to Docker Hub so they are available beyond your local machine. There is also a hands-on project included here that builds a Flask-based Python application behind an Nginx load balancer, which you are expected to complete as a practical exercise.

Jun 16, 202617 min read
Docker Compose in Action: Multi-Container Apps, Nginx Load Balancing & Docker Hub
S

Sai Praneeth's Blogs

37 posts

From SDLC and Agile to DevOps and CI/CD, this blog is where I share structured technical notes, concepts and practical insights in Cloud and DevOps engineering.