Building CI/CD Pipeline

Skilled in managing carrier-grade ISP infrastructure, enterprise environments, and server operations. Enthusiastic about optimizing high-performance networks and exploring emerging technologies. Committed to continuous learning and driven to leverage cloud solutions and automation tools to enhance innovation and efficiency.
Recently wrapped up implementing a CI/CD pipeline for a Spring Boot application, and I wanted to share the experience and some valuable insights I gained along the way!
The CI Pipeline handles the heavy lifting:
Automated builds with Maven
Code quality checks via SonarQube
Docker image creation and versioning
Secure artifact storage in JFrog Artifactory
The CD Pipeline takes it home:
Pulls the latest verified images
Deploys with Docker Compose
Runs health checks to ensure everything's running smoothly
Key Learning: JFrog Artifactory
One of the most eye-opening aspects of this project was diving deep into JFrog Artifactory. It's not just a storage solutionβit's a comprehensive artifact management platform that enterprise teams rely on for good reason:
Xray Security Scanning β Before anything touches production, Xray scans artifacts for vulnerabilities in components and libraries. This early detection is crucial for maintaining secure deployments.
Repository Types that serve different purposes:
Local repos for storing your built artifacts
Remote repos acting as proxies for downloading libraries and dependencies
Distribution repos for deploying artifacts across environments
This layered approach gives you control, security, and efficiency all in one place.
What's Next?
I'm excited to expand this pipeline further by exploring:
Rollback strategies for safer deployments
Blue-green deployment patterns for zero-downtime releases
Notifications and alerting to keep the team informed of build status
Jenkins CI Pipeline
pipeline {
agent { label 'docker1' }
environment {
JAVA_HOME = '/usr/lib/jvm/temurin-21-jdk-amd64'
PATH = "/opt/maven/bin:${JAVA_HOME}/bin:${env.PATH}"
IMAGE_NAME = 'todo-springboot-app'
IMAGE_TAG = "v${BUILD_NUMBER}"
IMAGE_LATEST = 'latest'
ARTIFACTORY_URL = 'bitwranglers.jfrog.io'
ARTIFACTORY_REPO = 'docker-repo'
SONAR_HOST_URL = 'http://SonarQube-Docker:9000'
SONAR_PROJECT_KEY = 'todo-with-junit'
SONAR_LOGIN = credentials('sonarqube-token') // store token in Jenkins credentials
}
stages {
stage('Checkout Code') {
steps {
git branch: 'main',
url: 'https://github.com/Kashimo0054/Dockerized-todo-app.git'
}
}
stage('Verify Tools') {
steps {
sh '''
java -version
mvn -version
docker --version
'''
}
}
stage('Build with Maven') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('SonarQube-Docker') {
withCredentials([string(credentialsId: 'sonarqube-token', variable: 'SONAR_TOKEN')]) {
sh '''
mvn sonar:sonar \
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.login=${SONAR_LOGIN}
'''
}
}
}
}
stage('Build Docker Image') {
steps {
sh """
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:${IMAGE_LATEST}
"""
}
}
stage('Push to Artifactory') {
steps {
withCredentials([usernamePassword(
credentialsId: 'artifactory-cred',
usernameVariable: 'ART_USER',
passwordVariable: 'ART_PASS'
)]) {
sh """
echo $ART_PASS | docker login ${ARTIFACTORY_URL} -u $ART_USER --password-stdin
docker tag ${IMAGE_NAME}:${IMAGE_TAG} \
${ARTIFACTORY_URL}/${ARTIFACTORY_REPO}/${IMAGE_NAME}:${IMAGE_TAG}
docker tag ${IMAGE_NAME}:${IMAGE_LATEST} \
${ARTIFACTORY_URL}/${ARTIFACTORY_REPO}/${IMAGE_NAME}:${IMAGE_LATEST}
docker push ${ARTIFACTORY_URL}/${ARTIFACTORY_REPO}/${IMAGE_NAME}:${IMAGE_TAG}
docker push ${ARTIFACTORY_URL}/${ARTIFACTORY_REPO}/${IMAGE_NAME}:${IMAGE_LATEST}
docker logout ${ARTIFACTORY_URL}
"""
}
}
}
}
post {
success {
echo "CI Completed Successfully!"
echo "Image available at:"
echo "${ARTIFACTORY_URL}/${ARTIFACTORY_REPO}/${IMAGE_NAME}:${IMAGE_TAG}"
echo "${ARTIFACTORY_URL}/${ARTIFACTORY_REPO}/${IMAGE_NAME}:latest"
}
failure {
echo "CI Failed β No image was pushed"
}
always {
cleanWs() // optional: keep agents clean
}
}
}
Jenkins CD pipeline
pipeline {
agent { label 'docker1' }
environment {
ARTIFACTORY_URL = "bitwranglers.jfrog.io"
ARTIFACTORY_REPO = "docker-repo"
IMAGE_NAME = "todo-springboot-app"
DEPLOY_PATH = "/home/jenkins-agent/deploy/todoapp" // where compose runs
}
stages {
stage('Checkout Deployment Repo') {
steps {
sh "mkdir -p ${DEPLOY_PATH}"
dir("${DEPLOY_PATH}") {
git branch: 'main',
url: 'https://github.com/Kashimo0054/Dockerized-todo-app.git'
}
}
}
stage('Pull Latest Image from Artifactory') {
steps {
withCredentials([usernamePassword(
credentialsId: 'artifactory-cred',
usernameVariable: 'ART_USER',
passwordVariable: 'ART_PASS'
)]) {
sh """
echo "Logging in to Artifactory..."
echo $ART_PASS | docker login ${ARTIFACTORY_URL} -u $ART_USER --password-stdin
echo "Pulling latest image..."
docker pull ${ARTIFACTORY_URL}/${ARTIFACTORY_REPO}/${IMAGE_NAME}:latest
echo "Logging out..."
docker logout ${ARTIFACTORY_URL}
"""
}
}
}
stage('Deploy New Version') {
steps {
dir("${DEPLOY_PATH}") {
sh """
echo "π Stopping running containers..."
docker compose down || true
echo "π Starting with latest image..."
docker compose up -d
"""
}
}
}
stage('Health Check') {
steps {
script {
echo "π Checking if application is healthy..."
def tries = 0
def healthy = false
while (tries < 10) {
def status = sh(
script: "curl -s -o /dev/null -w '%{http_code}' http://springboot-app:8084/actuator/health"
",
returnStdout: true
).trim()
if (status == "200") {
healthy = true
break
}
sleep 5
tries++
}
if (!healthy) {
error("β Deployment FAILED β Health check did not pass.")
} else {
echo "β
Health check passed β Deployment successful!"
}
}
}
}
}
post {
success {
echo "CD Deployment Successful!"
}
failure {
echo "Deployment FAILED β rollback recommended."
}
}
}


