Jenkins CI/CD Pipeline

Jenkins Pipeline Complete Guide: Build, Test & Deploy

TB
Tom Bradley
CI/CD Specialist
May 20, 2025
28 min read

What You'll Learn

A complete Jenkins pipeline guide — from declarative pipeline syntax to agents, stages, credentials, shared libraries, and real-world CI/CD implementation with Docker and Kubernetes.

What is a Jenkins Pipeline?

A Jenkins Pipeline is a suite of plugins that supports implementing and integrating Continuous Delivery pipelines into Jenkins. It provides a scriptable, code-based approach to defining your build, test, and deploy process — stored as a Jenkinsfile in your repository.

Declarative Pipeline

  • ✅ Structured, opinionated syntax
  • ✅ Easier to read and validate
  • ✅ Built-in features (post, options)
  • ✅ Best for most CI/CD use cases
  • ✅ Recommended for teams

Scripted Pipeline

  • ⚙️ Full Groovy DSL flexibility
  • ⚙️ Complex conditional logic
  • ⚙️ More verbose
  • ⚙️ Harder to learn
  • ⚙️ Use for advanced use cases only

Complete Declarative Jenkinsfile

Jenkinsfile — Node.js App with Docker + K8s Deploy
pipeline {
    // Where to run (any available agent)
    agent any

    // Environment variables available to all stages
    environment {
        APP_NAME        = 'my-app'
        DOCKER_REGISTRY = 'registry.example.com'
        IMAGE_TAG       = "${BUILD_NUMBER}-${GIT_COMMIT[0..7]}"
        DOCKER_CREDS    = credentials('docker-registry-credentials')
        KUBECONFIG      = credentials('k8s-kubeconfig')
    }

    // Pipeline-wide options
    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
        timestamps()
    }

    // Run pipeline on every push
    triggers {
        pollSCM('H/5 * * * *')  // Poll SCM every 5 minutes
    }

    stages {
        // ─────────────── Stage 1: Code Quality ───────────────
        stage('Code Quality') {
            parallel {
                stage('Lint') {
                    steps {
                        sh 'npm run lint'
                    }
                }
                stage('Security Scan') {
                    steps {
                        sh 'npm audit --audit-level=high'
                    }
                }
            }
        }

        // ─────────────── Stage 2: Tests ───────────────
        stage('Test') {
            steps {
                sh '''
                    npm ci
                    npm test -- --coverage --ci
                '''
            }
            post {
                always {
                    // Publish test results
                    junit 'test-results/*.xml'
                    // Publish coverage
                    publishHTML([
                        allowMissing: false,
                        reportDir: 'coverage',
                        reportFiles: 'index.html',
                        reportName: 'Coverage Report'
                    ])
                }
            }
        }

        // ─────────────── Stage 3: Build Docker Image ───────────────
        stage('Build Docker Image') {
            when {
                anyOf {
                    branch 'main'
                    branch 'release/*'
                }
            }
            steps {
                script {
                    def image = docker.build(
                        "${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG}",
                        "--build-arg BUILD_DATE=${new Date().format('yyyy-MM-dd')} ."
                    )
                    // Push to registry
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }

        // ─────────────── Stage 4: Deploy to Staging ───────────────
        stage('Deploy to Staging') {
            when { branch 'main' }
            steps {
                withCredentials([file(credentialsId: 'k8s-kubeconfig', variable: 'KUBECONFIG')]) {
                    sh """
                        # Update Kubernetes deployment image
                        kubectl set image deployment/${APP_NAME} \\
                            ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG} \\
                            --namespace=staging
                        
                        # Wait for rollout
                        kubectl rollout status deployment/${APP_NAME} \\
                            --namespace=staging \\
                            --timeout=5m
                    """
                }
            }
        }

        // ─────────────── Stage 5: Smoke Test ───────────────
        stage('Smoke Test') {
            when { branch 'main' }
            steps {
                sh '''
                    # Wait for app to be ready
                    sleep 15
                    # Run smoke test against staging
                    curl -f https://staging.example.com/health || exit 1
                '''
            }
        }

        // ─────────────── Stage 6: Deploy to Production ───────────────
        stage('Deploy to Production') {
            when { branch 'main' }
            // Require manual approval before deploying to production
            input {
                message "Deploy to production?"
                ok "Yes, deploy!"
                parameters {
                    string(name: 'DEPLOY_REASON', description: 'Why are you deploying?')
                }
            }
            steps {
                withCredentials([file(credentialsId: 'k8s-kubeconfig-prod', variable: 'KUBECONFIG')]) {
                    sh """
                        kubectl set image deployment/${APP_NAME} \\
                            ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${IMAGE_TAG} \\
                            --namespace=production
                        kubectl rollout status deployment/${APP_NAME} --namespace=production --timeout=10m
                    """
                }
            }
        }
    }

    // Post-pipeline actions
    post {
        success {
            slackSend(color: 'good', message: "✅ ${APP_NAME} deployed successfully! Build: ${BUILD_NUMBER}")
        }
        failure {
            slackSend(color: 'danger', message: "❌ ${APP_NAME} pipeline FAILED! Build: ${BUILD_NUMBER}")
            emailext(
                to: '$DEFAULT_RECIPIENTS',
                subject: "Jenkins Build Failed: ${JOB_NAME} #${BUILD_NUMBER}",
                body: '${JELLY_SCRIPT,template="html"}'
            )
        }
        always {
            // Clean workspace
            cleanWs()
        }
    }
}

Jenkins Agents & Nodes

Jenkins agents (formerly called slaves) are machines that run build jobs. The master schedules builds and delegates actual execution to agents.

Jenkinsfile — Using specific agents
pipeline {
    // Run on any agent
    agent any

    stages {
        stage('Build') {
            // Run on a specific labeled agent
            agent { label 'linux-build-agent' }
            steps { sh 'make build' }
        }

        stage('Docker Build') {
            // Run in a Docker container on the agent
            agent {
                docker {
                    image 'node:20-alpine'
                    args '-v /tmp:/tmp'
                }
            }
            steps {
                sh 'npm ci && npm run build'
            }
        }

        stage('K8s Agent') {
            // Run in a pod on Kubernetes (cloud agent)
            agent {
                kubernetes {
                    yaml '''
                    apiVersion: v1
                    kind: Pod
                    spec:
                      containers:
                      - name: maven
                        image: maven:3.9-jdk-17
                        command: [cat]
                        tty: true
                    '''
                    defaultContainer 'maven'
                }
            }
            steps {
                sh 'mvn clean package'
            }
        }
    }
}

Credentials Management

Using credentials safely in Jenkinsfile
stage('Use Credentials') {
    steps {
        // Username/Password credential
        withCredentials([usernamePassword(
            credentialsId: 'my-credentials',
            usernameVariable: 'USERNAME',
            passwordVariable: 'PASSWORD'
        )]) {
            sh 'docker login -u $USERNAME -p $PASSWORD registry.example.com'
        }

        // Secret text
        withCredentials([string(credentialsId: 'api-key', variable: 'API_KEY')]) {
            sh 'curl -H "Authorization: Bearer $API_KEY" https://api.example.com'
        }

        // SSH key
        withCredentials([sshUserPrivateKey(
            credentialsId: 'deploy-key',
            keyFileVariable: 'SSH_KEY'
        )]) {
            sh 'ssh -i $SSH_KEY deployer@prod.example.com "sudo systemctl restart app"'
        }
    }
}

Jenkins Best Practices

  • Store Jenkinsfile in repository — Treat pipeline as code; version control it alongside your application.
  • Use Shared Libraries — Extract common pipeline steps into a shared library to avoid duplication across repos.
  • Never hardcode credentials — Always use Jenkins Credentials Store or external secrets management.
  • Clean workspace — Use cleanWs() in post to avoid stale artifacts affecting future builds.
  • Gate production deploys — Always require manual approval for production deployments (use input{} block).
  • Set timeouts — Prevent hanging builds with the timeout option.

Keep Reading

D
DevOps

Docker Networking Demystified: Bridge, Host & Overlay

8 min read Read More
C
Cloud

AWS IAM Roles vs Users vs Policies

10 min read Read More
P
Programming

Understanding Python's GIL & Multiprocessing

14 min read Read More