AB
The final part of our end-to-end CI/CD series, focusing on enhancing your pipeline with Docker containers and GitHub webhooks for automated deployments.
In this project, we will create an end-to-end Continuous Integration and Continuous Deployment (CI/CD) pipeline for a Java application. This is the fourth and final part of our series, focusing on Docker for containerization and GitHub webhooks for automated pipeline triggering.
The sample code for this project is available at Spring Boot Web Application 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 other services like Docker, SonarQube, and Trivy.
Steps to install Jenkins on an EC2 instance:
sudo apt update -y
sudo apt install openjdk-17-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 the security group to access Jenkins.
You need to install the following plugins:
To install these plugins:
Configure the global tools by going to Manage Jenkins → Global Tool Configuration:
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:
Install Trivy on the Jenkins server:
sudo apt-get install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
sudo apt-get update
sudo apt-get install trivy
https://github.com/jaiswaladi246/SpringBoot-WebApplication.git
Let’s create a comprehensive pipeline that includes all the necessary stages:
pipeline {
agent any
tools {
maven 'maven3'
jdk 'jdk11'
}
stages {
stage('git checkout code') {
steps {
git branch: 'main', changelog: false, poll: false, url: 'https://github.com/jaiswaladi246/SpringBoot-WebApplication.git'
}
}
}
}
stages {
stage('compile code') {
steps {
sh 'mvn compile'
}
}
}
stages {
stage('test code') {
steps {
sh 'mvn test'
}
}
}
For this step, we’ll use the pipeline syntax helper to generate the proper syntax:
stages {
stage('SonarQube Analysis') {
environment {
scannerHome = tool 'sonarqube-scanner'
}
steps {
withSonarQubeEnv('sonarqube') {
sh '''
${scannerHome}/bin/sonar-scanner \
-Dsonar.projectKey=spring-petclinic \
-Dsonar.projectName=spring-petclinic \
-Dsonar.projectVersion=1.0 \
-Dsonar.sources=src/ \
-Dsonar.java.binaries=target/classes/ \
-Dsonar.exclusions=src/test/java/****/*.java \
-Dsonar.qualitygate.wait=true
'''
}
}
}
}
stages {
stage('OWASP Dependency Check') {
steps {
dependencyCheck additionalArguments: '--disableYarnAudit --scan ./', odcInstallation: 'DP'
dependencyCheckPublisher pattern: '**/dependency-check-report.xml', healthy: '0', unhealthy: '1', unstableTotalHigh: '0'
}
}
}
stages {
stage('build maven project') {
steps {
sh 'mvn clean install'
}
}
}
For this step, we need to add Docker Hub credentials in Jenkins:
Now we can add the Docker build stage:
stages {
stage('build docker image') {
steps {
script {
withCredentials([string(credentialsId: 'docker-hub', variable: 'dockerhubpwd')]) {
sh 'docker build -t yourdockerhubusername/spring-boot-app:latest .'
}
}
}
}
}
stages {
stage('Trivy Scan') {
steps {
sh 'trivy image yourdockerhubusername/spring-boot-app:latest'
sh 'trivy image yourdockerhubusername/spring-boot-app:latest > trivy-report.txt'
}
}
}
stages {
stage('push docker image') {
steps {
script {
withCredentials([string(credentialsId: 'docker-hub', variable: 'dockerhubpwd')]) {
sh 'docker login -u yourdockerhubusername -p ${dockerhubpwd}'
sh 'docker push yourdockerhubusername/spring-boot-app:latest'
}
}
}
}
}
stages {
stage('Run Docker Container') {
steps {
script {
sh 'docker stop spring-boot-app || true'
sh 'docker rm spring-boot-app || true'
sh 'docker run -d -p 8080:8080 --name spring-boot-app yourdockerhubusername/spring-boot-app:latest'
}
}
}
}
Here’s the complete pipeline script:
pipeline {
agent any
tools {
maven 'maven3'
jdk 'jdk11'
}
stages {
stage('Git Checkout') {
steps {
git branch: 'main',
url: 'https://github.com/jaiswaladi246/SpringBoot-WebApplication.git'
}
}
stage('Compile Code') {
steps {
sh 'mvn compile'
}
}
stage('Test Code') {
steps {
sh 'mvn test'
}
}
stage('SonarQube Analysis') {
environment {
scannerHome = tool 'sonarqube-scanner'
}
steps {
withSonarQubeEnv('sonarqube') {
sh '''
${scannerHome}/bin/sonar-scanner \
-Dsonar.projectKey=spring-petclinic \
-Dsonar.projectName=spring-petclinic \
-Dsonar.projectVersion=1.0 \
-Dsonar.sources=src/ \
-Dsonar.java.binaries=target/classes/ \
-Dsonar.exclusions=src/test/java/****/*.java \
-Dsonar.qualitygate.wait=true
'''
}
}
}
stage('OWASP Dependency Check') {
steps {
dependencyCheck additionalArguments: '--disableYarnAudit --scan ./', odcInstallation: 'DP'
dependencyCheckPublisher pattern: '**/dependency-check-report.xml', healthy: '0', unhealthy: '1', unstableTotalHigh: '0'
}
}
stage('Build Maven Project') {
steps {
sh 'mvn clean install'
}
}
stage('Build Docker Image') {
steps {
script {
withCredentials([string(credentialsId: 'docker-hub', variable: 'dockerhubpwd')]) {
sh 'docker build -t yourdockerhubusername/spring-boot-app:latest .'
}
}
}
}
stage('Trivy Scan') {
steps {
sh 'trivy image yourdockerhubusername/spring-boot-app:latest'
sh 'trivy image yourdockerhubusername/spring-boot-app:latest > trivy-report.txt'
}
}
stage('Push Docker Image') {
steps {
script {
withCredentials([string(credentialsId: 'docker-hub', variable: 'dockerhubpwd')]) {
sh 'docker login -u yourdockerhubusername -p ${dockerhubpwd}'
sh 'docker push yourdockerhubusername/spring-boot-app:latest'
}
}
}
}
stage('Run Docker Container') {
steps {
script {
sh 'docker stop spring-boot-app || true'
sh 'docker rm spring-boot-app || true'
sh 'docker run -d -p 8080:8080 --name spring-boot-app yourdockerhubusername/spring-boot-app:latest'
}
}
}
}
post {
success {
echo 'Pipeline completed successfully!'
}
failure {
echo 'Pipeline failed!'
}
}
}
To build a Docker image, you need a Dockerfile in your project root. Here’s a simple Dockerfile for a Spring Boot application:
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
To automatically trigger the Jenkins pipeline when code is pushed to GitHub, we need to set up a webhook:
http://<jenkins-ip-address>:8080/github-webhook/
application/json
Now in Jenkins:
This setup will trigger the pipeline automatically whenever code is pushed to the main branch of your GitHub repository.
Let’s examine what happens in each stage of our pipeline:
Jenkins provides a visualization of your pipeline execution. You can:
If you encounter permission errors when running Docker commands, make sure:
If SonarQube analysis fails:
If pushing to Docker Hub fails:
Our pipeline includes several security checks:
These checks help ensure your application is secure before deployment.
For larger projects, consider these enhancements:
For a quicker deployment of a Java application using Docker, follow these simplified steps:
Clone the repository:
git clone https://github.com/jaiswaladi246/Ekart
Build the application:
cd Ekart
mvn clean install --DskipTests
Build and run Docker container:
cd docker
docker build -t ekart .
docker run -d -p 8080:8080 ekart
This approach allows you to deploy a Java application within minutes, skipping the comprehensive CI/CD pipeline when quick deployment is needed.
In this tutorial, we’ve created a comprehensive CI/CD pipeline for Java applications that:
This pipeline ensures consistent, automated, and secure deployments, embodying DevOps best practices. By implementing this pipeline, you’ll save time, reduce errors, and deliver higher quality applications.
Throughout this four-part series, we’ve explored various approaches to CI/CD for Java applications, from basic Jenkins pipelines to advanced configurations with Kubernetes, Azure DevOps, and now Docker with webhooks. You can choose the approach that best fits your project’s needs and scale it as your requirements evolve.