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.
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}\"
"""
}
}
}DevOps Cloud Academy
Exploring industry DevOps practices and technical expertise.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.