Skip to main content

Command Palette

Search for a command to run...

Building CI/CD Pipeline

Updated
β€’4 min read
Building CI/CD Pipeline
S

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."
        }
    }
}