Operations 15 min read

Implementing a DevSecOps CI/CD Pipeline with Jenkins, Kubernetes, ArgoCD, and Security Scanners

This article details a comprehensive DevSecOps pipeline that uses Jenkins for CI/CD, Dependency‑Track and DefectDojo for SBOM management, SonarQube and Trivy for static and container scanning, Docker for image builds, and ArgoCD with Kubernetes for automated deployments, illustrating each stage with full code examples.

DevOps Cloud Academy
DevOps Cloud Academy
DevOps Cloud Academy
Implementing a DevSecOps CI/CD Pipeline with Jenkins, Kubernetes, ArgoCD, and Security Scanners

In the modern era of large‑scale application development, security threats are rising, prompting the author to build a DevSecOps pipeline that integrates CI/CD, software composition analysis, static application security testing, container scanning, and automated deployment.

The pipeline relies on Jenkins as the CI/CD platform, Dependency‑Track for SCA, SonarQube for SAST, Trivy for container image scanning, and DefectDojo for centralized reporting. The underlying infrastructure uses Kubernetes for orchestration, Harbor as a private container registry, and ArgoCD for continuous deployment.

Clone Repository stage

stage('Clone Repository') {
    steps {
        script {
            sourceCodeDir = sh(script: 'pwd', returnStdout: true).trim()
            git branch: 'staging', url: 'https://github.com/gilangvperdana/react-code.git'
            env.CI_COMMIT_SHORT_SHA = sh(script: "git log --pretty=format:'%h' -n 1", returnStdout: true).trim()
        }
    }
}

Generate SBOM stage

stage('Generate SBOM') {
      steps {
          dir(sourceCodeDir) {
            sh "cdxgen"
          }
      }
}

Send SBOM to Dependency‑Track & DefectDojo stage

stage('Send to Dependency Track & Defect Dojo') {
      steps {
        script {
          def projectVersion = "${env.BUILD_NUMBER}"
          dir(sourceCodeDir) {
            sh '''
            curl -X "POST" "$DEPTRACK_URL:8081/api/v1/bom" -H 'Content-Type: multipart/form-data' -H "X-Api-Key: $DEPTRACK_API_KEY" -F "autoCreate=true" -F "projectName=website" -F "projectVersion=''' + projectVersion + '''" -F "[email protected]"
            '''
            sh '''
            docker run --rm -e "DD_URL=$DD_URL" -e "DD_API_KEY=$DD_API_KEY" -e "DD_PRODUCT_TYPE_NAME=Research and Development" -e "DD_PRODUCT_NAME=website" -e "DD_ENGAGEMENT_NAME=Dependency Track" -e "DD_TEST_NAME=dependency-track" -e "DD_TEST_TYPE_NAME=Dependency Track Finding Packaging Format (FPF) Export" -e "DD_FILE_NAME=website/bom.json" -v "${WORKSPACE}:/usr/local/dd-import/website" maibornwolff/dd-import:latest dd-reimport-findings.sh
            '''
          }
        }
      }
}

SonarQube SAST stage

stage('SonarQube - SAST') {
      steps {
        withSonarQubeEnv('SonarQube') {
          dir(sourceCodeDir) {
            sh "sonar-scanner -Dsonar.projectKey=website -Dsonar.host.url=$SONAR_URL -Dsonar.login=${SONARQUBE_SECRET}"
          }
        }
        timeout(time: 2, unit: 'MINUTES') {
          script {
            waitForQualityGate abortPipeline: true
          }
        }
      }
}

Send SonarQube results to DefectDojo stage

stage('Send Sonarqube to DefectDojo') {
      steps {
        sh '''
        sonar-report --sonarurl="$SONAR_URL" --sonartoken ${SONAR_TOKEN} --sonarcomponent="website" --sonarorganization="website" --project="website" --application="website" --release="1.0.0" --branch="main" --output="sonarreport.html"
        '''
        sh '''
        docker run --rm -e "DD_URL=$DD_URL" -e "DD_API_KEY=${DD_API_KEY}" -e "DD_PRODUCT_TYPE_NAME=Research and Development" -e "DD_PRODUCT_NAME=website" -e "DD_ENGAGEMENT_NAME=Sonar Qube" -e "DD_TEST_NAME=sonar-qube" -e "DD_TEST_TYPE_NAME=SonarQube Scan detailed" -e "DD_FILE_NAME=website/sonarreport.html" -v "${WORKSPACE}:/usr/local/dd-import/website" maibornwolff/dd-import:latest dd-reimport-findings.sh
        '''
      }
}

Docker Build stage

stage('Docker Build') {
    steps {
        sh '''
          docker build -t $HARBOR_URL/temp/research:${CI_COMMIT_SHORT_SHA} .
        '''
    }
}

Trivy Scan Image stage

stage('Trivy Scan Image') {
    steps {
        sh 'trivy image --exit-code 0 --no-progress --severity HIGH,CRITICAL -f json -o trivy_report.json $HARBOR_URL/temp/research'
    }
}

Send Trivy report to DefectDojo stage

stage('Send Trivy Report to Defectdojo') {
      steps {
          sh '''
          docker run --rm -e "DD_URL=$DD_URL" -e "DD_API_KEY=$DD_API_KEY" -e "DD_PRODUCT_TYPE_NAME=Research and Development" -e "DD_PRODUCT_NAME=website" -e "DD_ENGAGEMENT_NAME=Trivy" -e "DD_TEST_NAME=trivy" -e "DD_TEST_TYPE_NAME=Trivy Scan" -e "DD_FILE_NAME=website/trivy_report.json" -v "${WORKSPACE}:/usr/local/dd-import/website" maibornwolff/dd-import:latest dd-reimport-findings.sh
          '''
      }
}

Push Image stage

stage('Push Image'){
        steps {
            sh 'docker push $HARBOR_URL/temp/research:${CI_COMMIT_SHORT_SHA}'
            sh 'docker rmi $HARBOR_URL/temp/research:${CI_COMMIT_SHORT_SHA}'
        }
}

Change Image Tag on GitHub (Staging) stage

stage('Change Image Tag on Github'){
    steps {
        script{
            env.GIT_URL = sh (
                script: 'echo @github.com/gilangvperdana/EXAMPLE.git'>https://oauth2:${access_token_PSW}@github.com/gilangvperdana/EXAMPLE.git',
                returnStdout: true
            ).trim()
        }
        dir('react-manifest-staging'){
            git branch: 'main', credentialsId: 'github_access_token', url: "$GIT_URL"
            sh 'git config user.email [email protected] && git config user.name ci-bot'
            sh 'sed -i "s+$HARBOR_URL/temp/research:.*+$HARBOR_URL/temp/research:${CI_COMMIT_SHORT_SHA}+g" infra/staging/deployment.yaml'
            sh 'git add . && git commit -m "update staging research image tag to ${CI_COMMIT_SHORT_SHA}"'
            sh 'git push origin main'
        }
    }
}

ArgoCD Sync Staging stage

stage('ArgoCD Sync Staging'){
        steps {
            withCredentials([string(credentialsId: "ARGOCD_TOKEN", variable: 'ARGOCD_TOKEN')]) {
                dir('react-manifest-staging'){
                    git branch: 'main', credentialsId: 'github_access_token', url: "https://github.com/gilangvperdana/EXAMPLE.git"
                    sh 'ARGOCD_SERVER=$ARGOCD_URL argocd --grpc-web app create research-service-staging --project default --repo https://github.com/gilangvperdana/EXAMPLE.git --path ./infra/staging/ --revision main --dest-namespace staging --dest-server https://kubernetes.default.svc --upsert'
                    sh 'argocd --grpc-web app sync research-service-staging --force'
                }
            }
        }
}

Send Approval Deploy to Prod stage

stage('Send Approval Deploy to Prod'){
    steps{
        script{
            sh """
            curl -s -X POST https://api.telegram.org/bot$BOT_TOKEN/sendMessage -d chat_id=$TELEGRAM_CHATID -d parse_mode=markdown -d text=\"Dear team : \n*Approve* to Continue Deploy CICD Pipeline *${env.JOB_NAME}* \nfor *Release* Website \n\nPlease Approve at: ${env.RUN_DISPLAY_URL}\""""
            input message: 'Approve to continue', submitter: "[email protected]", submitterParameter: "[email protected]"
        }
    }
}

Change Image Tag on GitHub (Production) stage

stage('Change Image Tag on Github'){
    steps {
        script{
            env.GIT_URL = sh (
                script: 'echo @github.com/gilangvperdana/EXAMPLEPROD.git'>https://oauth2:${access_token_PSW}@github.com/gilangvperdana/EXAMPLEPROD.git',
                returnStdout: true
            ).trim()
        }
        dir('react-manifest-production'){
            git branch: 'main', credentialsId: 'github_access_token', url: "$GIT_URL"
            sh 'git config user.email [email protected] && git config user.name ci-bot'
            sh 'sed -i "s+$HARBOR_URL/temp/research:.*+$HARBOR_URL/temp/research:${CI_COMMIT_SHORT_SHA}+g" infra/production/deployment.yaml'
            sh 'git add . && git commit -m "update production research image tag to ${CI_COMMIT_SHORT_SHA}"'
            sh 'git push origin main'
        }
    }
}

ArgoCD Sync Production stage

stage('ArgoCD Sync Production'){
    steps {
        withCredentials([string(credentialsId: "ARGOCD_TOKEN", variable: 'ARGOCD_TOKEN')]) {
            dir('react-manifest-production'){
                git branch: 'main', credentialsId: 'github_access_token', url: "https://github.com/gilangvperdana/EXAMPLEPROD.git"
                sh 'ARGOCD_SERVER=$ARGOCD_URL argocd --grpc-web app create research-service-production --project default --repo https://github.com/gilangvperdana/EXAMPLEPROD.git --path ./infra/production/ --revision main --dest-namespace production --dest-server https://kubernetes.default.svc --upsert'
                sh 'argocd --grpc-web app sync research-service-production --force'
            }
        }
    }
}

Post actions (cleanup and notifications)

post {
        always {
            deleteDir()
            dir("${workspace}@tmp") { deleteDir() }
            dir("${workspace}@script") { deleteDir() }
        }
        success {
            sh """
            curl -s -X POST https://api.telegram.org/bot$BOT_TOKEN/sendMessage -d chat_id=$TELEGRAM_CHATID -d parse_mode=markdown -d text=\" Dear team : \nCICD Pipeline *${env.JOB_NAME}* \nBranch : *${env.BRANCH_NAME}* \nStatus job : *Success* \nTotal Time : *${currentBuild.durationString}* \n\n*More info* at : ${env.RUN_DISPLAY_URL}\"
            """
        }
        aborted {
            sh """
            curl -s -X POST https://api.telegram.org/bot$BOT_TOKEN/sendMessage -d chat_id=$TELEGRAM_CHATID -d parse_mode=markdown -d text=\" Dear team : \nCICD Pipeline *${env.JOB_NAME}* \nBranch : *${env.BRANCH_NAME}* \nStatus job : *Aborted* \nTotal Time : *${currentBuild.durationString}* \n\n*More info* at : ${env.RUN_DISPLAY_URL}\"
            """
        }
        failure {
            sh """
            curl -s -X POST https://api.telegram.org/bot$BOT_TOKEN/sendMessage -d chat_id=$TELEGRAM_CHATID -d parse_mode=markdown -d text=\" Dear team : \nCICD Pipeline *${env.JOB_NAME}* \nBranch : *${env.BRANCH_NAME}* \nStatus job : *Failed* \nTotal Time : *${currentBuild.durationString}* \n\n*More info* at:  ${env.RUN_DISPLAY_URL}\"
            """
        }
    }
}
DockerCI/CDKubernetesDevOpsJenkinssecurity scanningArgoCD
DevOps Cloud Academy
Written by

DevOps Cloud Academy

Exploring industry DevOps practices and technical expertise.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.