AB
The fifth part of our end-to-end CI/CD series, explaining how to create a pipeline using separate Continuous Integration and Continuous Deployment jobs for better separation of concerns.
In this tutorial, we will learn how to set up a CI/CD pipeline for a Java application using two different Jenkins jobs, one for Continuous Integration (CI) and one for Continuous Deployment (CD). This separation provides better control over the pipeline stages and allows for more flexibility in managing deployments.
The sample code for this project is available at Spring Boot CI/CD Docker Jenkins GitHub Repository.
Before starting this project, make sure you have the following prerequisites:
For this we need to use an EC2 instance of type t2.large so that we have enough resources to run the Jenkins server and also to install other services like Docker, SonarQube and Trivy.
Steps to install Jenkins on an EC2 instance:
sudo apt update -y
sudo apt install openjdk-11-jdk -y
java -version
sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]" \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update
sudo apt-get install jenkins
Don’t forget to open port 8080 in security group to access Jenkins.
Open the Jenkins URL in the browser and get the initial admin password by following the instructions in the browser.
Install suggested plugins.
Set up the admin user.
Once Jenkins is set up, you can access the Jenkins dashboard.
You need to install the following plugins:
To install these plugins:
Configure the global tools by going to Manage Jenkins → Global Tool Configuration:
https://github.com/darey-devops/spring-boot-cicd-docker-jenkins.git
pipeline {
agent any
tools {
maven 'maven3'
jdk 'jdk11'
}
environment {
SONARQUBE_HOME = tool name: 'sonarqube'
}
stages {
stage('Checkout Code') {
steps {
git url: 'https://github.com/darey-devops/spring-boot-cicd-docker-jenkins.git', branch: 'main'
}
}
stage('Compile Code') {
steps {
sh 'mvn clean compile'
}
}
stage('Run Tests') {
steps {
sh 'mvn test'
}
}
stage('Sonar Analysis') {
steps {
withSonarQubeEnv('sonarqube') {
sh ''' $SONARQUBE_HOME/bin/sonar-scanner -Dsonar.projectKey=spring-boot-cicd-docker-jenkins -Dsonar.qualitygate.wait=true -Dsonar.qualitygate.timeout=600000 -Dsonar.java.binaries=.
'''
}
}
}
stage('OWASP Dependency Check') {
steps {
dependencyCheck additionalArgs: '-DskipTests --scan ./', odcInstallation: 'DP'
dependencyCheckPublisher pattern: '**/dependency-check-report.xml'
}
}
stage('Maven Install') {
steps {
sh 'mvn clean install -DskipTests=true'
}
}
stage('Build Docker Image') {
steps {
script {
withDockerRegistry([credentialsId: 'docker-hub-credentials', toolName: 'docker']) {
sh 'docker build -t darey-devops/spring-boot-cicd-docker-jenkins:latest .'
sh 'docker push darey-devops/spring-boot-cicd-docker-jenkins:latest'
}
}
}
}
stage('Trigger CD Pipeline') {
steps {
build job: 'Java-App-CD', wait: true
}
}
}
post {
success {
echo 'CI Pipeline completed successfully!'
}
failure {
echo 'CI Pipeline failed!'
}
}
}
pipeline {
agent any
stages {
stage('Deploy the image to a container') {
steps {
script {
withDockerRegistry([credentialsId: 'docker-hub-credentials', toolName: 'docker']) {
sh 'docker stop spring-boot-app || true'
sh 'docker rm spring-boot-app || true'
sh 'docker run -d --name spring-boot-app -p 8080:8080 darey-devops/spring-boot-cicd-docker-jenkins:latest'
}
}
}
}
}
post {
success {
echo 'CD Pipeline completed successfully!'
}
failure {
echo 'CD Pipeline failed!'
}
}
}
Let’s examine the components of our pipeline:
Tools and Environment Setup:
Checkout Code:
Compile Code:
Run Tests:
SonarQube Analysis:
OWASP Dependency Check:
Maven Install:
Build Docker Image:
Trigger CD Pipeline:
Before running the pipeline, add Docker Hub credentials to Jenkins:
Install Docker on the Jenkins server/EC2 instance:
sudo apt-get update
sudo apt-get install docker.io -y
sudo systemctl start docker
sudo docker run hello-world
sudo systemctl enable docker
docker --version
sudo usermod -a -G docker $(whoami)
sudo usermod -a -G docker jenkins
newgrp docker
sudo systemctl restart jenkins
Set up SonarQube on the Jenkins server using Docker:
docker run -d --name sonarqube -p 9000:9000 sonarqube:lts-community
Access SonarQube by navigating to http://<jenkins-server-ip>:9000
in your browser.
The default username and password are both: admin
To create a token for connecting Jenkins with SonarQube:
Now add this token in Jenkins credentials:
Separation of Concerns: CI and CD processes have different responsibilities and can be managed separately.
Independent Deployment: You can trigger deployments manually or on different schedules from the build process.
Security: You can restrict access to the CD job to specific team members.
Visibility: Clearer view of where a failure occurs - either in the build/test phase or in deployment.
Easier Debugging: When issues arise, it’s clearer whether they’re related to building the application or deploying it.
Using separate jobs creates two distinct pipeline visualizations in Jenkins:
This makes it easier to monitor and troubleshoot each phase of your software delivery process.
In this tutorial, we’ve created a comprehensive CI/CD pipeline using separate jobs for CI and CD. This approach:
By splitting your pipeline into CI and CD jobs, you gain more flexibility in how you manage your software delivery process, particularly for complex applications or when you need to implement approval gates between build and deployment.
In the next part of this series, we’ll explore how to extend this approach for a full-stack application with frontend, backend, and database components.