Operations 25 min read

Jenkins SonarQube Integration: 3 Ways to Trigger Code Scans in CI/CD Pipelines

This tutorial covers the theory and three practical methods for triggering SonarQube code scans from Jenkins — Freestyle jobs, Pipeline scripts, and Shared Libraries — using a Maven project example in a Docker Compose environment with detailed configuration steps.

Cilantro Loves Linux
Cilantro Loves Linux
Cilantro Loves Linux
Jenkins SonarQube Integration: 3 Ways to Trigger Code Scans in CI/CD Pipelines

Theoretical Foundation: Why Jenkins Triggers SonarQube

In a CI pipeline, Jenkins acts as the scheduling hub deciding when and which code to scan, while SonarQube handles the analysis — detecting bugs, vulnerabilities, code smells, test coverage, duplication, complexity, and evaluating Quality Gates.

Jenkins decides scan timing; SonarQube decides scan content and standards.

Three Common Trigger Methods

SonarScanner CLI — Install sonar-scanner in the Jenkins container; run commands like:

sonar-scanner \
 -Dsonar.projectKey=k8s-dashboard \
 -Dsonar.projectName=k8s-dashboard \
 -Dsonar.sources=src \
 -Dsonar.java.binaries=target \
 -Dsonar.host.url=http://sonarqube:9000 \
 -Dsonar.login=$SONAR_TOKEN

Works for any language; flexible configuration.

Maven Sonar Plugin (for Java/Maven) — Use sonar-maven-plugin; append to Maven phase:

mvn clean verify sonar:sonar \
 -DskipTests=false \
 -Dsonar.projectKey=k8s-dashboard \
 -Dsonar.host.url=http://sonarqube:9000 \
 -Dsonar.login=$SONAR_TOKEN

Jenkins only runs mvn; plugin pushes results automatically.

Jenkins SonarQube Scanner Plugin + withSonarQubeEnv — Configure SonarQube server and credentials in Jenkins global settings; in Pipeline:

withSonarQubeEnv('sonarqube') {
 sh 'mvn clean verify sonar:sonar'
}
withSonarQubeEnv

injects SONAR_HOST_URL and SONAR_AUTH_TOKEN automatically.

Docker Compose Jenkins Environment

The article uses docker-compose with jenkins/jenkins:lts-jdk17. Tools (JDK, Maven, Git, Node) are mounted from host directories into the container. Example docker-compose.yaml snippet:

version: '3.8'
services:
  jenkins:
    image: jenkins/jenkins:lts-jdk17
    container_name: jenkins
    user: '1000:993'
    ports:
      - '8080:8080'
      - '50000:50000'
    volumes:
      - ./jenkins_data:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/bin/docker
      - ./tools/jdk:/opt/tools/jdk:ro
      - ./tools/maven:/opt/tools/maven:ro
      - ./tools/git:/opt/tools/git:ro
      - ./tools/node:/opt/tools/node:ro
      - ./casc:/var/jenkins_casc:ro
    environment:
      - JAVA_OPTS=-Duser.timezone=Asia/Shanghai
      - JENKINS_OPTS=--prefix=/jenkins
    networks:
      - ci-network
    restart: unless-stopped

networks:
  ci-network:
    driver: bridge

All build steps (e.g., mvn, sonar-scanner) execute inside the Jenkins container. To troubleshoot, run docker exec -it jenkins bash.

Making sonar-scanner Available in the Container

Two approaches:

Option 1: Mount host's existing /usr/local/sonar-scanner as /opt/tools/sonar-scanner:ro in docker-compose.yaml, then verify with

docker exec -it jenkins bash -lc '/opt/tools/sonar-scanner/bin/sonar-scanner -v'

.

Option 2: Download a specific version into ./tools/sonar-scanner/current on host, then mount that directory. Commands:

mkdir -p ./tools/sonar-scanner
cd ./tools/sonar-scanner
curl -fsSLO https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-<version>-linux-x64.zip
unzip -o sonar-scanner-cli-<version>-linux-x64.zip
mv sonar-scanner-<version>-linux-x64 current
chmod -R a+rX ./current

Then mount ./tools/sonar-scanner/current:/opt/tools/sonar-scanner:ro.

For Freestyle shell steps, use a fixed path:

SONAR_SCANNER_HOME=/opt/tools/sonar-scanner; ${SONAR_SCANNER_HOME}/bin/sonar-scanner -v

.

SonarQube Pre-Configuration

Create a project (e.g., projectKey=k8s-dashboard).

Generate a CI token: User → My Account → Security → Generate Token (e.g., jenkins-k8s-dashboard). Example token: sqp_99a9e261e0bfce5f58f037e95171209a84401e0d.

Store token in Jenkins as Secret text credential with ID sonarqube-token.

Jenkins Global Configuration

System Configuration → SonarQube servers :

Name: sonarqube Server URL: http://192.168.145.129:9000 (or http://sonarqube:9000 if on same Docker network)

Authentication token: select sonarqube-token credential

Global Tool Configuration → SonarQube Scanner :

Name: sonar-scanner Installation: Fixed local SonarScanner (not auto-install)

Local path: /opt/tools/sonar-scanner Required plugins: SonarQube Scanner for Jenkins (provides withSonarQubeEnv and waitForQualityGate), Pipeline Maven Integration (optional), Credentials Binding (for Freestyle).

Practice 1: Freestyle Job

Suitable for demos, simple or one-off projects. Steps:

New Freestyle job named k8s-dashboard-sonar-freestyle.

Source Code Management: Git, repo ssh://[email protected]:2222/root/k8s-dashboard.git, branch main, credentials gitlab-ssh-cred.

Build Environment: Enable Credentials Binding → Secret text → Variable SONAR_TOKEN, Credential sonarqube-token.

Build Step 1: Invoke top-level Maven targets → Maven version (e.g., Maven3.9.10) → Goals: clean verify -DskipTests=false (runs tests for coverage).

Build Step 2: Execute shell — run SonarScanner CLI with variables:

echo "===== Current dir & build output ====="
pwd
ls -lh target
test -d target/classes

SONAR_SCANNER_HOME=/opt/tools/sonar-scanner
${SONAR_SCANNER_HOME}/bin/sonar-scanner -v

SONAR_HOST_URL="http://192.168.145.129:9000"
SONAR_PROJECT_KEY="k8s-dashboard"
SONAR_PROJECT_NAME="k8s-dashboard"

${SONAR_SCANNER_HOME}/bin/sonar-scanner \
 -Dsonar.projectKey=${SONAR_PROJECT_KEY} \
 -Dsonar.projectName=${SONAR_PROJECT_NAME} \
 -Dsonar.sources=src \
 -Dsonar.java.binaries=target/classes \
 -Dsonar.host.url=${SONAR_HOST_URL} \
 -Dsonar.login=${SONAR_TOKEN}

Trigger build; watch console for INFO: ANALYSIS SUCCESSFUL. View results in SonarQube project dashboard.

Practice 2: Pipeline Job (Recommended)

Pipeline stores Jenkinsfile in SCM, enabling version control and team collaboration. Example Jenkinsfile combining Maven Sonar Plugin + withSonarQubeEnv:

pipeline {
  agent any

  tools {
    jdk 'JDK11'
    maven 'Maven3.9.10'
  }

  environment {
    GIT_URL = 'ssh://[email protected]:2222/root/k8s-dashboard.git'
    BRANCH_NAME = 'main'
    IMAGE_TAG = ''
    SONARQUBE_SERVER = 'sonarqube'
    SONAR_PROJECT_KEY = 'k8s-dashboard'
  }

  stages {
    stage('📥 Checkout') {
      steps {
        checkout([
          $class: 'GitSCM',
          branches: [[name: "refs/heads/${BRANCH_NAME}"]],
          userRemoteConfigs: [[url: "${GIT_URL}", credentialsId: 'gitlab-ssh-cred']],
          extensions: [
            [$class: 'CheckoutOption', timeout: 30],
            [$class: 'LocalBranch', localBranch: "${BRANCH_NAME}"],
            [$class: 'CloneOption', depth: 1, noTags: false, reference: '', timeout: 10]
          ]
        ])
        script {
          env.IMAGE_TAG = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
        }
        sh '''
          git config --list | grep remote.origin.url
          git branch -v
          git status
        '''
      }
    }

    stage('🔨 Maven Build') {
      steps {
        sh 'mvn clean package -DskipTests'
      }
    }

    stage('🔍 SonarQube Scan') {
      steps {
        withSonarQubeEnv("${SONARQUBE_SERVER}") {
          sh """
            mvn sonar:sonar \
             -Dsonar.projectKey=${SONAR_PROJECT_KEY} \
             -Dsonar.projectName=${SONAR_PROJECT_KEY} \
             -Dsonar.host.url=$SONAR_HOST_URL \
             -Dsonar.login=$SONAR_AUTH_TOKEN
          """
        }
      }
    }

    stage('⏱ Quality Gate Check (optional)') {
      steps {
        timeout(time: 2, unit: 'MINUTES') {
          script {
            // Requires SonarQube Scanner for Jenkins plugin and Webhook configured in SonarQube
            waitForQualityGate abortPipeline: true
          }
        }
      }
    }

    // Subsequent deploy stages (Nexus, Harbor) commented out for this experiment
  }

  post {
    success {
      echo "✅ SonarQube scan succeeded: project ${SONAR_PROJECT_KEY}, commit ${IMAGE_TAG}"
    }
    failure {
      echo "❌ Build or scan failed, see log: ${env.BUILD_URL}console"
    }
  }
}

Key Points

withSonarQubeEnv('sonarqube')

name must match global config.

It injects SONAR_HOST_URL and SONAR_AUTH_TOKEN. IMAGE_TAG is set after checkout via script { env.IMAGE_TAG = sh(...).trim() } — not in environment block because repo isn't cloned yet. waitForQualityGate needs the plugin and a SonarQube Webhook pointing to http://192.168.145.129:8080/jenkins/sonarqube-webhook/ (due to JENKINS_OPTS=--prefix=/jenkins). Fails pipeline if gate fails.

Deploy stages (Nexus, Docker) should come after successful Quality Gate.

Docker Compose Networking Notes

If Jenkins can't resolve sonarqube hostname, put both services in same ci-network or use fixed IP in global config.

If mvn not found, ensure Maven is installed in Jenkins image or via Dockerfile.

Practice 3: Shared Library for Enterprise Standardization

When multiple projects need Sonar scans, centralize logic in a Jenkins Shared Library ( my-shared-lib@main, repo

ssh://[email protected]:2222/root/jenkins-shared-library.git

).

1. Create vars/sonarScan.groovy

/**
 * Unified SonarQube scan function
 * @param config Map with:
 *   projectKey  - required
 *   projectName - defaults to projectKey
 *   sonarServer - defaults to 'sonarqube'
 *   mavenGoals  - defaults to 'clean verify'
 *   extraOptions - optional extra sonar params
 */
def call(Map config = [:]) {
  def projectKey  = config.projectKey
  def projectName = config.projectName ?: projectKey
  def sonarServer = config.sonarServer ?: 'sonarqube'
  def mavenGoals  = config.mavenGoals ?: 'clean verify'
  def extraOptions = config.extraOptions ?: ''

  if (!projectKey) {
    error 'sonarScan: projectKey is required'
  }

  stage("SonarQube Scan - ${projectKey}") {
    withSonarQubeEnv(sonarServer) {
      sh """
        mvn ${mavenGoals} sonar:sonar \
         -Dsonar.projectKey=${projectKey} \
         -Dsonar.projectName=${projectName} \
         -Dsonar.host.url=$SONAR_HOST_URL \
         -Dsonar.login=$SONAR_AUTH_TOKEN ${extraOptions}
      """
    }
  }
}
Note: This function defaults to Maven Sonar Plugin, which is simpler for Maven projects than calling sonar-scanner directly.

2. Configure Shared Library in Jenkins

Manage Jenkins → System Configuration → Global Pipeline Libraries

Name: my-shared-lib, Default version: main, Retrieval: Modern SCM → Git → URL

ssh://[email protected]:2222/root/jenkins-shared-library.git

, Credentials: gitlab-ssh-cred.

3. Project Jenkinsfile Using Shared Library

@Library('my-shared-lib@main') _

pipeline {
  agent any
  tools {
    jdk 'JDK11'
    maven 'Maven3.9.10'
  }
  environment {
    GIT_URL = 'ssh://[email protected]:2222/root/k8s-dashboard.git'
    BRANCH_NAME = 'main'
    SONAR_PROJECT_KEY = 'k8s-dashboard'
  }
  stages {
    stage('📥 Standard Checkout') {
      steps {
        script {
          gitCheckout(
            repoUrl: GIT_URL,
            branch: BRANCH_NAME,
            credentialsId: 'gitlab-ssh-cred',
            depth: 1
          )
        }
      }
    }
    stage('🔨 Standard Maven Build') {
      steps {
        script {
          mavenBuild(goals: 'clean verify', skipTests: false)
        }
      }
    }
    stage('🔍 Call Shared Library Sonar Scan') {
      steps {
        script {
          sonarScan(
            projectKey: SONAR_PROJECT_KEY,
            projectName: 'k8s-dashboard',
            sonarServer: 'sonarqube',
            mavenGoals: 'clean verify'
          )
        }
      }
    }
    stage('⏱ Quality Gate') {
      steps {
        timeout(time: 2, unit: 'MINUTES') {
          script {
            waitForQualityGate abortPipeline: true
          }
        }
      }
    }
  }
  post {
    success { echo """...""" }
    failure { echo """...""" }
  }
}

Benefits of Shared Library

Unified standards — all projects use same scan logic.

Centralized upgrades — change URL, auth, parameters in one place ( sonarScan.groovy).

Lower barrier — project Jenkinsfile stays concise, only business-specific config.

Summary & Selection Guide

Freestyle + sonar-scanner

Best for: Demos, temporary, single-project

Pros: Simple, quick start

Cons: Logic scattered in UI, hard to reuse

Pipeline + Maven Sonar Plugin

Best for: Team projects, version-controlled pipelines

Pros: Pipeline-as-code, auditable, rollbackable

Cons: Requires Jenkinsfile & plugin knowledge

Shared Library ( my-shared-lib@main )

Best for: Multi-project reuse, enterprise standardization

Pros: Single maintenance, consistent, clean project Jenkinsfiles

Cons: Needs shared library repo & version management

You now have a stable, Docker-Compose-based Jenkins setup that triggers SonarQube scans for Java projects like k8s-dashboard, establishing a visible, traceable, enforceable code-quality baseline. Next steps: bind Quality Gate to deployment (no-pass-no-deploy), push results to IM (DingTalk, Lark), and extend shared library with coverage thresholds, hotfix branch policies, etc.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CI/CDDevOpsMavenCode QualityPipelineSonarQubeShared LibraryJenkinsdocker-composeQuality Gate
Cilantro Loves Linux
Written by

Cilantro Loves Linux

Focused on Linux system operations and maintenance technology exchange, regularly publishing in-depth technical articles covering core areas such as system architecture, performance optimization, and security hardening. Whether you are a beginner or a seasoned expert, you can find valuable resources to enhance your skills and solve problems here. Follow us to explore the mysteries of Linux operations!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.