Skip to content

CI/CD Best Practices with Jenkins and SonarQube

CI/CD Best Practices with Jenkins and SonarQube

Section titled “CI/CD Best Practices with Jenkins and SonarQube”

Having implemented CI/CD pipelines for multiple enterprise applications, I’ve learned valuable lessons about building robust, automated deployment workflows. This article shares my experience with Jenkins and SonarQube integration.

A well-structured Jenkinsfile is crucial for maintainable CI/CD:

pipeline {
agent any
tools {
maven 'Maven-3.8.6'
jdk 'OpenJDK-17'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit 'target/surefire-reports/*.xml'
}
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('SonarQube') {
sh 'mvn sonar:sonar'
}
}
}
stage('Quality Gate') {
steps {
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
stage('Build & Deploy') {
when {
branch 'main'
}
steps {
sh 'mvn package'
sh 'docker build -t myapp:${BUILD_NUMBER} .'
sh 'docker push myregistry/myapp:${BUILD_NUMBER}'
}
}
}
}
  1. Install SonarQube Scanner:
// In Jenkins global tools configuration
SonarQube Scanner for Maven
  1. Configure SonarQube Server:
// In Jenkins system configuration
SonarQube servers:
- Name: SonarQube
- Server URL: https://sonarqube.company.com
- Server authentication token: ******

Add to your pom.xml:

<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.1.2184</version>
</plugin>

Create sonar-project.properties:

sonar.projectKey=my-project-key
sonar.projectName=My Project
sonar.projectVersion=1.0
# Source and test directories
sonar.sources=src/main/java
sonar.tests=src/test/java
sonar.java.binaries=target/classes
sonar.java.test.binaries=target/test-classes
# Coverage configuration
sonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
sonar.junit.reportsPath=target/surefire-reports
# Quality gate
sonar.qualitygate.wait=true

Define meaningful quality gates in SonarQube:

  1. Coverage: New code coverage > 80%
  2. Duplicated Lines: < 3%
  3. Maintainability Rating: A or B
  4. Reliability Rating: A
  5. Security Rating: A
  6. New Bugs: 0
  7. New Vulnerabilities: 0

Create custom quality profiles for your organization:

<!-- Custom rules configuration -->
<profile>
<name>My Company Rules</name>
<language>java</language>
<rules>
<rule>
<key>com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck</key>
<priority>MAJOR</priority>
</rule>
<!-- Add more custom rules -->
</rules>
</profile>
stage('Branch Strategy') {
when {
anyOf {
branch 'develop'
branch 'feature/*'
branch 'hotfix/*'
}
}
steps {
script {
if (env.BRANCH_NAME == 'develop') {
// Deploy to staging
deployToStaging()
} else if (env.BRANCH_NAME.startsWith('feature/')) {
// Run integration tests only
runIntegrationTests()
}
}
}
}
stage('Docker Build') {
steps {
script {
def image = docker.build("myapp:${BUILD_NUMBER}")
docker.withRegistry('https://myregistry.com', 'docker-credentials') {
image.push()
image.push('latest')
}
}
}
}
stage('Deploy to K8s') {
steps {
withKubeConfig([credentialsId: 'k8s-credentials']) {
sh '''
kubectl set image deployment/myapp myapp=myapp:${BUILD_NUMBER}
kubectl rollout status deployment/myapp
'''
}
}
}
stage('Notify') {
steps {
slackSend(
channel: '#deployments',
color: currentBuild.result == 'SUCCESS' ? 'good' : 'danger',
message: "Build ${env.JOB_NAME} - ${env.BUILD_NUMBER} - ${currentBuild.result}"
)
}
}
post {
always {
emailext(
subject: "Build ${env.JOB_NAME} - ${env.BUILD_NUMBER} - ${currentBuild.result}",
body: """<html>
<body>
<p>Build status: ${currentBuild.result}</p>
<p>Check console output at: ${env.BUILD_URL}</p>
</body>
</html>""",
to: "${env.CHANGE_AUTHOR_EMAIL}"
)
}
}
// Use Jenkins credentials
withCredentials([usernamePassword(credentialsId: 'git-credentials',
usernameVariable: 'GIT_USER',
passwordVariable: 'GIT_PASS')]) {
sh 'git push https://${GIT_USER}:${GIT_PASS}@github.com/repo.git'
}
stage('Security Scan') {
steps {
sh 'trivy fs --format json --output trivy-report.json .'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: '.',
reportFiles: 'trivy-report.json',
reportName: 'Security Scan Report'
])
}
}
stage('Parallel Tests') {
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test -Dtest=unit/**'
}
}
stage('Integration Tests') {
steps {
sh 'mvn test -Dtest=integration/**'
}
}
}
}
stage('Cache Dependencies') {
steps {
cache(maxCacheSize: '250MB', caches: {
maven {
key = 'maven-cache-${env.BRANCH_NAME}'
path = '~/.m2/repository'
}
})
}
}
stage('Quality Gate') {
steps {
timeout(time: 10, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
stage('Build with Retry') {
steps {
retry(3) {
sh 'mvn clean package'
}
}
}

A well-designed CI/CD pipeline with Jenkins and SonarQube provides:

  • Automated quality checks through SonarQube analysis
  • Consistent deployment processes across environments
  • Fast feedback on code quality issues
  • Traceability of changes through the pipeline
  • Security integration with credential management

The key is to start simple and gradually add complexity as your team becomes more comfortable with the pipeline.


This approach has been successfully implemented for multiple production applications with excellent results.