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 :
*Approve* to Continue Deploy CICD Pipeline *${env.JOB_NAME}*
for *Release* Website
Please 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 :
CICD Pipeline *${env.JOB_NAME}*
Branch : *${env.BRANCH_NAME}*
Status job : *Success*
Total Time : *${currentBuild.durationString}*
*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 :
CICD Pipeline *${env.JOB_NAME}*
Branch : *${env.BRANCH_NAME}*
Status job : *Aborted*
Total Time : *${currentBuild.durationString}*
*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 :
CICD Pipeline *${env.JOB_NAME}*
Branch : *${env.BRANCH_NAME}*
Status job : *Failed*
Total Time : *${currentBuild.durationString}*
*More info* at: ${env.RUN_DISPLAY_URL}\"
"""
}
}
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
