This closing episode presents a case study of an integrated enterprise pipeline architecture, from SCM webhooks, dynamic build pod provisioning in Kubernetes, to production deployment and Slack notifications. We also write a complete eight-stage Jenkinsfile and a security hardening checklist for the Jenkins controller.

This is the final episode of the Learn Jenkins series. In the previous 19 episodes we built skills from installation, pipeline as code, agents, secrets, Docker, shared libraries, JCasC, to troubleshooting. Now we assemble everything into one complete enterprise pipeline architecture — the flow that DevOps teams at large companies actually use.
We will build a pipeline that satisfies the following flow:
The key is separation of responsibilities. The Jenkins controller only orchestrates; all heavy work runs on Kubernetes agent pods created and removed per build (episode 3). The source of truth for deployment is the Helm chart (episode 16), quality is controlled by SonarQube (episode 14), and image security is maintained by Trivy. Every stage can fail and stop the flow early — never proceed to production if quality or security has not passed.
Here is the full Jenkinsfile for the flow above. The application repository contains Node.js source code, the chart/ folder contains the Helm chart, and the pipeline runs as a Multibranch Pipeline.
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: build
image: node:20-alpine
command: ['sleep', 'infinity']
- name: sonar
image: sonarsource/sonar-scanner-cli:11
command: ['sleep', 'infinity']
- name: docker
image: docker:27
command: ['sleep', 'infinity']
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
'''
}
}
options {
timestamps()
timeout(time: 60, unit: 'MINUTES')
disableConcurrentBuilds()
}
environment {
IMAGE = 'registry.example.com/my-app'
TAG = "1.0.${BUILD_NUMBER}"
}
stages {
stage('Checkout & SonarQube Scan') {
steps {
checkout scm
container('sonar') {
sh 'sonar-scanner -Dsonar.projectKey=my-app'
}
}
}
stage('Quality Gate') {
steps {
withSonarQubeEnv('SonarQube') {
sh 'echo "Menunggu quality gate..."'
}
timeout(time: 5, unit: 'MINUTES') {
waitForQualityGate abortPipeline: true
}
}
}
stage('Test & Build') {
steps {
container('build') {
sh 'npm ci'
sh 'npm run test:coverage'
sh 'npm run build'
}
}
post {
success {
junit 'coverage/report.xml'
jacoco execPattern: '**/jacoco.exec'
}
}
}
stage('Push Image & Trivy Scan') {
steps {
container('docker') {
docker.withRegistry('https://registry.example.com', 'registry-creds') {
sh "docker build -t ${IMAGE}:${TAG} ."
sh "docker push ${IMAGE}:${TAG}"
}
}
container('build') {
sh "trivy image --severity HIGH,CRITICAL --exit-code 1 ${IMAGE}:${TAG}"
}
}
}
stage('Deploy Staging & E2E') {
steps {
withKubeConfig(credentialsId: 'kubeconfig-staging', serverUrl: 'https://k8s-staging.example.com') {
sh "helm upgrade --install my-app ./chart --namespace staging --set image.repository=${IMAGE} --set image.tag=${TAG}"
}
sh 'npm run test:e2e -- --base-url=https://staging.example.com'
}
}
stage('Manual Approval') {
when { branch 'main' }
steps {
input message: 'Approve production deployment?', submitter: 'release-manager'
}
}
stage('Deploy Production') {
when { branch 'main' }
steps {
withKubeConfig(credentialsId: 'kubeconfig-prod', serverUrl: 'https://k8s-prod.example.com') {
sh "helm upgrade --install my-app ./chart --namespace production --set image.repository=${IMAGE} --set image.tag=${TAG} --wait"
}
}
}
}
post {
success {
slackSend(color: 'good', message: "Deploy ${env.JOB_NAME} #${env.BUILD_NUMBER} berhasil ke production")
}
failure {
emailext(to: 'release@example.com', subject: "Pipeline gagal: ${env.JOB_NAME}", body: "Cek log: ${env.BUILD_URL}")
}
cleanup {
cleanWs()
}
}
}build (default), sonar, and docker. After the build finishes, the pod is automatically destroyed, leaving no resources behind.waitForQualityGate blocks the pipeline if SonarQube gives a failed result. Configure the SonarQube quality gate to auto-pass on non-prod projects so the pipeline does not hang forever.--exit-code 1 makes the pipeline fail if HIGH or CRITICAL vulnerabilities are found.input step stops the pipeline on the main branch until the release manager approves.helm upgrade --install with the --wait flag performs a rolling update and waits for pods to be ready before the pipeline is declared successful, then Slack reports.Warning
Mounting the host docker.sock in the agent pod makes image builds easy, but it grants root access to the node. In restricted environments, use Docker-in-Docker (DinD) or a dedicated privileged runner, and make sure the agent pod never accepts jobs from untrusted users.
Finally, the checklist that must be satisfied before a Jenkins controller is declared production-ready:
withCredentials and never store secrets in the Jenkinsfile.In this episode we assembled everything into an enterprise pipeline case study:
From episode 0 to 20, you now have a complete toolkit for building, securing, monitoring, and troubleshooting Jenkins at enterprise scale. Do not stop here — try applying it to a real project, automate the small things first, then raise the complexity. Happy building your own production-grade pipeline!