Overview

Pipeline as Code is the watershed moment when Jenkins evolved from “drag-and-drop configuration” to “code-driven.” Defining pipelines in a Jenkinsfile under Git version control means every pipeline change has a diff to review, a history to trace, and a branch to roll back. This article systematically covers Pipeline as Code practices, from Jenkinsfile syntax to production-grade pipeline design.

Reference: Jenkins Pipeline Official Documentation

I. Jenkinsfile Syntax

1.1 Declarative vs. Scripted

Jenkins Pipeline has two syntax styles:

DimensionDeclarativeScripted
SyntaxStructured DSLGroovy code
ReadabilityHigh, close to config filesLow, requires Groovy knowledge
FlexibilityConstrained by DSLFully free
Input validationBuilt-in post, when, etc.Manual implementation
Recommended forStandard pipelines, team collaborationComplex logic, many conditional branches
// === Declarative Pipeline ===
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'make build'
            }
        }
    }
}

// === Scripted Pipeline ===
node {
    stage('Build') {
        sh 'make build'
    }
}

Best practice: Prefer declarative syntax. Only use script {} blocks for complex logic that declarative can’t express.

1.2 Declarative Pipeline Structure

pipeline {
    // === Agent: Execution Environment ===
    agent {
        label 'linux && docker'
        // Or use Docker
        // docker { image 'golang:1.22' }
        // Or K8s
        // kubernetes { ... }
    }

    // === Environment Variables ===
    environment {
        APP_NAME = 'myapp'
        VERSION = "${env.BUILD_ID}"
        DOCKER_REGISTRY = 'registry.example.com'
        // Read from Credentials
        DOCKER_CREDENTIALS = credentials('docker-registry')
        // Read from config file
        DEPLOY_CONFIG = 'deploy-config'
    }

    // === Options ===
    options {
        timeout(time: 30, unit: 'MINUTES')
        timestamps()
        buildDiscarder(logRotator(numToKeepStr: '20'))
        disableConcurrentBuilds()
        retry(3)
        ansiColor('xterm')
    }

    // === Triggers ===
    triggers {
        cron('H 2 * * *')          // Daily at 2 AM
        pollSCM('H/5 * * * *')     // Check SCM every 5 minutes
        upstream(upstreamProjects: 'base-library', threshold: hudson.model.Result.SUCCESS)
    }

    // === Parameters ===
    parameters {
        choice(name: 'ENVIRONMENT', choices: ['staging', 'production'], description: 'Deploy environment')
        string(name: 'VERSION', defaultValue: '', description: 'Deploy version (empty for latest)')
        booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: 'Skip tests')
        password(name: 'DEPLOY_TOKEN', defaultValue: '', description: 'Deploy token')
    }

    // === Stages ===
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                sh 'make build'
            }
        }

        stage('Test') {
            when {
                expression { !params.SKIP_TESTS }
            }
            steps {
                sh 'make test'
            }
        }
    }

    // === Post Actions ===
    post {
        always {
            junit 'reports/**/*.xml'
            archiveArtifacts artifacts: 'dist/**', fingerprint: true
            cleanWs()
        }
        success {
            echo 'Pipeline succeeded'
        }
        failure {
            echo 'Pipeline failed'
            // Send notification
            emailext(
                subject: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
                body: "Check: ${env.BUILD_URL}",
                to: 'team@example.com'
            )
        }
        unstable {
            echo 'Build is unstable'
        }
        changed {
            echo 'Build status changed'
        }
    }
}

II. Multibranch Pipelines

2.1 Configuring Multibranch Pipelines

Multibranch pipelines automatically discover branches and PRs in a Git repository, creating an independent Jenkins job for each:

// Jenkinsfile - Multibranch pipeline adaptation
pipeline {
    agent any

    environment {
        // Dynamically set environment based on branch name
        BRANCH_NAME = "${env.BRANCH_NAME ?: 'unknown'}"
        IS_MAIN = "${env.BRANCH_NAME == 'main'}"
        IS_PR = "${env.CHANGE_ID != null}"
    }

    stages {
        stage('Detect Environment') {
            steps {
                script {
                    echo "Branch: ${env.BRANCH_NAME}"
                    echo "Is PR: ${env.CHANGE_ID != null}"
                    if (env.CHANGE_ID) {
                        echo "PR number: ${env.CHANGE_ID}"
                        echo "PR target branch: ${env.CHANGE_TARGET}"
                    }
                }
            }
        }

        stage('Build') {
            steps {
                sh 'make build'
            }
        }

        stage('Test') {
            steps {
                sh 'make test'
            }
        }

        stage('Deploy') {
            when {
                anyOf {
                    branch 'main'
                    branch 'release/*'
                }
                expression { env.CHANGE_ID == null }  // Not a PR
            }
            steps {
                script {
                    if (env.BRANCH_NAME == 'main') {
                        echo 'Deploying to staging'
                        sh 'make deploy-staging'
                    } else if (env.BRANCH_NAME?.startsWith('release/')) {
                        input message: 'Confirm deployment to production?', ok: 'Deploy'
                        echo 'Deploying to production'
                        sh 'make deploy-production'
                    }
                }
            }
        }
    }
}

2.2 Branch Strategy

main          → Auto-deploy to staging
release/*     → Deploy to production after manual confirmation
develop       → Build and test, no deploy
feature/*     → Build (quick validation)
PR            → Build + test + code quality check
// Branch-specific when conditions
stage('Deploy Staging') {
    when {
        branch 'main'
    }
    steps {
        sh 'make deploy-staging'
    }
}

stage('Deploy Production') {
    when {
        branch 'release/*'
        beforeInput true  // Evaluate when before input
    }
    input {
        message "Confirm deployment to production?"
        ok "Deploy"
        submitter "release-managers"
    }
    steps {
        sh 'make deploy-production'
    }
}

stage('PR Check') {
    when {
        changeRequest target: 'main'
    }
    steps {
        sh 'make lint security-scan'
    }
}

III. Shared Libraries

3.1 Creating a Shared Library

When multiple projects share similar pipeline logic, extract it into a shared library for unified maintenance:

jenkins-shared-library/
├── vars/
   ├── standardPipeline.groovy     # Global variables (directly callable)
   ├── deploy.groovy
   ├── notify.groovy
   └── buildDocker.groovy
├── src/
   └── com/
       └── example/
           ├── Deployer.groovy     # Class library
           └── Config.groovy
└── resources/
    └── templates/
        └── deployment.yaml         # Resource files
// vars/standardPipeline.groovy
// Standard pipeline template, reused across projects

def call(Map config = [:]) {
    pipeline {
        agent {
            label config.agent ?: 'linux'
        }

        environment {
            APP_NAME = config.appName ?: error('appName is required')
            DOCKER_REGISTRY = config.registry ?: 'registry.example.com'
        }

        options {
            timeout(time: config.timeout ?: 30, unit: 'MINUTES')
            timestamps()
            buildDiscarder(logRotator(numToKeepStr: '20'))
            disableConcurrentBuilds()
        }

        stages {
            stage('Checkout') {
                steps {
                    checkout scm
                }
            }

            stage('Build') {
                steps {
                    sh config.buildCmd ?: 'make build'
                }
            }

            stage('Test') {
                steps {
                    sh config.testCmd ?: 'make test'
                }
                post {
                    always {
                        junit testResults: config.testResults ?: 'reports/**/*.xml',
                              allowEmptyResults: true
                    }
                }
            }

            stage('Code Quality') {
                when {
                    expression { config.qualityCheck != false }
                }
                steps {
                    sh config.qualityCmd ?: 'make lint'
                }
            }

            stage('Build Image') {
                when {
                    expression { config.dockerBuild != false }
                }
                steps {
                    script {
                        buildDocker(
                            image: "${env.APP_NAME}",
                            tag: "${env.BUILD_NUMBER}"
                        )
                    }
                }
            }

            stage('Deploy') {
                when {
                    anyOf {
                        branch config.deployBranch ?: 'main'
                        expression { env.BRANCH_NAME?.startsWith('release/') }
                    }
                }
                steps {
                    script {
                        deploy(
                            appName: env.APP_NAME,
                            version: env.BUILD_NUMBER,
                            env: env.BRANCH_NAME == 'main' ? 'staging' : 'production'
                        )
                    }
                }
            }
        }

        post {
            failure {
                notify.slack(
                    channel: config.slackChannel ?: '#alerts',
                    message: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
                )
            }
        }
    }
}

3.2 Using the Shared Library

// Project Jenkinsfile - just a few lines
@Library('jenkins-shared-library@v1.2') _

standardPipeline(
    appName: 'payment-service',
    agent: 'golang',
    buildCmd: 'go build -o bin/app ./cmd/',
    testCmd: 'go test -v -race ./...',
    qualityCmd: 'golangci-lint run',
    dockerBuild: true,
    deployBranch: 'main',
    slackChannel: '#payments'
)

3.3 Shared Library Utility Functions

// vars/buildDocker.groovy
def call(Map params) {
    def image = params.image
    def tag = params.tag
    def dockerfile = params.dockerfile ?: 'Dockerfile'
    def context = params.context ?: '.'

    sh """
        docker build -t ${image}:${tag} \
            -f ${dockerfile} \
            --build-arg VERSION=${tag} \
            ${context}
    """

    // Push image
    if (params.push != false) {
        withCredentials([usernamePassword(
            credentialsId: 'docker-registry',
            usernameVariable: 'DOCKER_USER',
            passwordVariable: 'DOCKER_PASS'
        )]) {
            sh """
                echo \$DOCKER_PASS | docker login registry.example.com -u \$DOCKER_USER --password-stdin
                docker push ${image}:${tag}
                docker tag ${image}:${tag} ${image}:latest
                docker push ${image}:latest
                docker logout registry.example.com
            """
        }
    }
}
// vars/notify.groovy
def slack(Map params) {
    def color = params.color ?: 'danger'
    def channel = params.channel ?: '#general'
    def message = params.message ?: 'No message'

    slackSend(
        channel: channel,
        color: color,
        message: message
    )
}

def dingtalk(Map params) {
    def message = params.message ?: 'No message'
    def mobiles = params.mobiles ?: []

    dingtalk(
        robot: 'jenkins-robot',
        type: 'MARKDOWN',
        title: 'Jenkins Notification',
        text: message,
        at: mobiles
    )
}

def email(Map params) {
    emailext(
        subject: params.subject ?: "Jenkins: ${env.JOB_NAME} #${env.BUILD_NUMBER}",
        body: params.body ?: "Check: ${env.BUILD_URL}",
        to: params.to ?: 'team@example.com',
        mimeType: 'text/html',
        attachmentsPattern: params.attachments ?: ''
    )
}

IV. Parallel Stages

4.1 Parallel Test Execution

pipeline {
    agent any

    stages {
        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'go test -v -short ./... 2>&1 | tee reports/unit-tests.xml'
                    }
                    post {
                        always {
                            junit 'reports/unit-tests.xml'
                        }
                    }
                }

                stage('Integration Tests') {
                    steps {
                        sh 'make test-integration'
                    }
                }

                stage('Lint') {
                    steps {
                        sh 'golangci-lint run --out-format=junit > reports/lint.xml'
                    }
                    post {
                        always {
                            junit 'reports/lint.xml'
                        }
                    }
                }

                stage('Security Scan') {
                    steps {
                        sh 'trivy fs --severity HIGH,CRITICAL .'
                    }
                }

                stage('License Check') {
                    steps {
                        sh 'go-licenses check ./...'
                    }
                }
            }
        }
    }
}

4.2 Matrix Build

pipeline {
    agent any

    stages {
        stage('Matrix Build') {
            matrix {
                axes {
                    axis {
                        name 'GO_VERSION'
                        values '1.21', '1.22', '1.23'
                    }
                    axis {
                        name 'GOOS'
                        values 'linux', 'darwin', 'windows'
                    }
                    axis {
                        name 'GOARCH'
                        values 'amd64', 'arm64'
                    }
                }

                excludes {
                    // Exclude unsupported combinations
                    exclude {
                        axis {
                            name 'GOOS'
                            values 'windows'
                        }
                        axis {
                            name 'GOARCH'
                            values 'arm64'
                        }
                    }
                }

                agent {
                    label "golang-${GO_VERSION}"
                }

                stages {
                    stage('Build') {
                        steps {
                            sh """
                                GOOS=${GOOS} GOARCH=${GOARCH} \
                                go build -o bin/app-${GOOS}-${GOARCH} ./cmd/
                            """
                        }
                    }

                    stage('Test') {
                        when {
                            expression { GOOS == 'linux' && GOARCH == 'amd64' }
                        }
                        steps {
                            sh 'go test -v ./...'
                        }
                    }
                }
            }
        }
    }
}

V. Conditional Triggers

5.1 When Conditions In Detail

pipeline {
    agent any

    stages {
        // Based on branch
        stage('Deploy Staging') {
            when {
                branch 'main'
            }
            steps { sh 'make deploy-staging' }
        }

        // Based on PR
        stage('PR Validation') {
            when {
                changeRequest target: 'main', branch: 'feature/*'
            }
            steps { sh 'make validate' }
        }

        // Based on expression
        stage('Production Deploy') {
            when {
                expression {
                    env.BRANCH_NAME ==~ /release\/.*/ &&
                    env.BUILD_NUMBER?.toInteger() > 0
                }
            }
            steps { sh 'make deploy-production' }
        }

        // Based on changed file paths
        stage('Build Frontend') {
            when {
                changeset 'frontend/**'
            }
            steps { sh 'cd frontend && npm run build' }
        }

        stage('Build Backend') {
            when {
                changeset 'backend/**'
            }
            steps { sh 'cd backend && go build' }
        }

        // Based on commit message
        stage('Skip on [skip ci]') {
            when {
                expression {
                    !env.GIT_COMMIT_MESSAGE?.contains('[skip ci]')
                }
            }
            steps { echo 'Running because no skip ci' }
        }

        // Combined conditions
        stage('Complex Condition') {
            when {
                allOf {
                    branch 'main'
                    changeset 'deploy/**'
                    expression { params.FORCE_DEPLOY || env.GIT_MESSAGE?.contains('[deploy]') }
                }
            }
            steps { sh 'make deploy' }
        }

        // Exclusion condition
        stage('Except Docs') {
            when {
                not {
                    changeset 'docs/**'
                }
            }
            steps { sh 'make build' }
        }
    }
}

5.2 Getting Commit Info

stage('Get Commit Info') {
    steps {
        script {
            // Get latest commit
            def commitMsg = sh(
                script: 'git log -1 --pretty=%B',
                returnStdout: true
            ).trim()

            def commitAuthor = sh(
                script: 'git log -1 --pretty=%an',
                returnStdout: true
            ).trim()

            def commitHash = sh(
                script: 'git rev-parse --short HEAD',
                returnStdout: true
            ).trim()

            // Set environment variables for later use
            env.GIT_MESSAGE = commitMsg
            env.GIT_AUTHOR = commitAuthor
            env.GIT_HASH = commitHash

            echo "Commit: ${commitHash} by ${commitAuthor}"
            echo "Message: ${commitMsg}"

            // Check for specific markers
            if (commitMsg.contains('[skip deploy]')) {
                echo 'Skipping deploy stage'
                env.SKIP_DEPLOY = 'true'
            }
        }
    }
}

VI. Artifact Management

6.1 Artifact Upload and Download

pipeline {
    agent any

    environment {
        ARTIFACT_NAME = "myapp-${env.BUILD_NUMBER}.tar.gz"
    }

    stages {
        stage('Package') {
            steps {
                sh """
                    tar czf ${ARTIFACT_NAME} \
                        bin/ \
                        configs/ \
                        migrations/
                """
            }
        }

        stage('Upload Artifact') {
            steps {
                // Option 1: Using Artifactory plugin
                rtUpload(
                    serverId: 'artifactory',
                    spec: '''{
                        "files": [
                            {
                                "pattern": "*.tar.gz",
                                "target": "libs-release-local/myapp/${BUILD_NUMBER}/"
                            }
                        ]
                    }'''
                )

                // Option 2: Using Nexus
                nexusArtifactUploader(
                    nexusVersion: 'nexus3',
                    protocol: 'https',
                    nexusUrl: 'nexus.example.com',
                    groupId: 'com.example',
                    version: "${env.BUILD_NUMBER}",
                    repository: 'releases',
                    credentialsId: 'nexus-credentials',
                    artifacts: [
                        [artifactId: 'myapp',
                         classifier: '',
                         file: "${ARTIFACT_NAME}",
                         type: 'tar.gz']
                    ]
                )

                // Option 3: Using AWS S3
                withAWS(credentials: 'aws-credentials', region: 'cn-north-1') {
                    s3Upload(
                        bucket: 'my-artifacts',
                        path: "myapp/${env.BUILD_NUMBER}/",
                        includePathPattern: '**/*.tar.gz'
                    )
                }
            }
        }

        stage('Download Artifact') {
            steps {
                script {
                    // Download from artifact repository
                    rtDownload(
                        serverId: 'artifactory',
                        spec: '''{
                            "files": [{
                                "pattern": "libs-release-local/myapp/${BUILD_NUMBER}/*.tar.gz",
                                "target": "downloads/"
                            }]
                        }'''
                    )
                }
            }
        }
    }
}

6.2 Artifact Version Management

// vars/publishArtifact.groovy
def call(Map params) {
    def appName = params.appName
    def version = params.version
    def files = params.files ?: []
    def repository = params.repository ?: 'releases'

    // Determine if SNAPSHOT or Release
    def isRelease = !version.contains('SNAPSHOT') && !version.contains('dev')

    if (!isRelease) {
        repository = 'snapshots'
    }

    echo "Uploading artifact to ${repository}: ${appName}:${version}"

    files.each { file ->
        echo "  Uploading: ${file}"
    }

    // Build artifact metadata
    def metadata = [
        appName: appName,
        version: version,
        buildNumber: env.BUILD_NUMBER,
        buildUrl: env.BUILD_URL,
        gitCommit: env.GIT_COMMIT,
        timestamp: new Date().format("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone('UTC'))
    ]

    // Write metadata file
    writeJSON file: 'artifact-metadata.json', json: metadata, pretty: 2

    // Upload metadata
    files << 'artifact-metadata.json'

    return metadata
}

VII. Kubernetes Integration

7.1 K8s Pod Agent

pipeline {
    agent {
        kubernetes {
            yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: golang
    image: golang:1.22-alpine
    command: ['sleep', 'infinity']
    volumeMounts:
    - mountPath: /go/pkg
      name: gopkg
    - mountPath: /root/.cache/go-build
      name: gobuild
    resources:
      requests:
        memory: 1Gi
        cpu: 500m
      limits:
        memory: 2Gi
        cpu: 1000m
  - name: docker
    image: docker:24
    command: ['sleep', 'infinity']
    securityContext:
      privileged: true
    volumeMounts:
    - mountPath: /var/run/docker.sock
      name: docker-sock
  - name: kubectl
    image: bitnami/kubectl:latest
    command: ['sleep', 'infinity']
  volumes:
  - name: gopkg
    emptyDir: {}
  - name: gobuild
    emptyDir: {}
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
'''
            defaultContainer: 'golang'
        }
    }

    stages {
        stage('Build') {
            steps {
                container('golang') {
                    sh 'go build -o bin/app ./cmd/'
                }
            }
        }

        stage('Docker Build') {
            steps {
                container('docker') {
                    sh """
                        docker build -t myapp:${env.BUILD_NUMBER} .
                        docker tag myapp:${env.BUILD_NUMBER} registry.example.com/myapp:${env.BUILD_NUMBER}
                        docker push registry.example.com/myapp:${env.BUILD_NUMBER}
                    """
                }
            }
        }

        stage('Deploy') {
            steps {
                container('kubectl') {
                    sh """
                        kubectl set image deployment/myapp \
                            app=registry.example.com/myapp:${env.BUILD_NUMBER} \
                            -n production
                        kubectl rollout status deployment/myapp -n production --timeout=300s
                    """
                }
            }
        }
    }
}

7.2 Dynamic Agent Templates

// Select different Pod templates based on project type
def getPodTemplate(String projectType) {
    def templates = [
        golang: '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: golang
    image: golang:1.22-alpine
    command: ['sleep', 'infinity']
    resources:
      requests: { memory: 1Gi, cpu: 500m }
      limits: { memory: 2Gi, cpu: 2000m }
  - name: docker
    image: docker:24
    securityContext: { privileged: true }
    volumeMounts:
    - mountPath: /var/run/docker.sock
      name: docker-sock
  volumes:
  - name: docker-sock
    hostPath: { path: /var/run/docker.sock }
''',
        nodejs: '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: node
    image: node:20-alpine
    command: ['sleep', 'infinity']
    resources:
      requests: { memory: 2Gi, cpu: 500m }
      limits: { memory: 4Gi, cpu: 2000m }
  - name: docker
    image: docker:24
    securityContext: { privileged: true }
    volumeMounts:
    - mountPath: /var/run/docker.sock
      name: docker-sock
  volumes:
  - name: docker-sock
    hostPath: { path: /var/run/docker.sock }
''',
        python: '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: python
    image: python:3.12-slim
    command: ['sleep', 'infinity']
    resources:
      requests: { memory: 1Gi, cpu: 500m }
      limits: { memory: 2Gi, cpu: 1000m }
'''
    ]

    return templates[projectType] ?: templates.golang
}

pipeline {
    agent none

    stages {
        stage('Build & Test') {
            steps {
                script {
                    def template = getPodTemplate('golang')
                    podTemplate(yaml: template) {
                        node(POD_LABEL) {
                            container('golang') {
                                sh 'go test -v ./...'
                                sh 'go build -o bin/app ./cmd/'
                            }
                        }
                    }
                }
            }
        }
    }
}

VIII. Blue-Green Deployment Pipeline

8.1 Complete Blue-Green Deployment

pipeline {
    agent any

    environment {
        APP_NAME = 'myapp'
        VERSION = "${env.BUILD_NUMBER}"
        NAMESPACE = 'production'
        DOCKER_IMAGE = "registry.example.com/${APP_NAME}:${VERSION}"
    }

    stages {
        stage('Build & Push') {
            steps {
                sh "docker build -t ${DOCKER_IMAGE} ."
                sh "docker push ${DOCKER_IMAGE}"
            }
        }

        stage('Deploy to Blue') {
            steps {
                sh """
                    # Deploy to Blue environment
                    envsubst < deploy/blue-green.yaml | kubectl apply -f - -n ${NAMESPACE}

                    # Wait for Blue to be ready
                    kubectl rollout status deployment/${APP_NAME}-blue -n ${NAMESPACE} --timeout=300s
                """
            }
        }

        stage('Smoke Test Blue') {
            steps {
                sh """
                    # Smoke test via Blue service ClusterIP
                    BLUE_IP=\$(kubectl get svc ${APP_NAME}-blue -n ${NAMESPACE} -o jsonpath='{.spec.clusterIP}')

                    # Wait for service to be available
                    for i in \$(seq 1 30); do
                        if curl -sf http://\${BLUE_IP}:8080/health; then
                            echo "Blue service is ready"
                            break
                        fi
                        sleep 2
                    done

                    # Smoke tests
                    curl -sf http://\${BLUE_IP}:8080/health || exit 1
                    curl -sf http://\${BLUE_IP}:8080/api/version | grep "${VERSION}" || exit 1
                    curl -sf http://\${BLUE_IP}:8080/api/users?limit=1 || exit 1

                    echo "Smoke tests passed"
                """
            }
        }

        stage('Switch Traffic') {
            input {
                message "Blue environment smoke tests passed, switch traffic?"
                ok "Switch to Blue"
                submitter "release-managers"
            }
            steps {
                sh """
                    # Update service selector to route traffic to Blue
                    kubectl patch svc ${APP_NAME} -n ${NAMESPACE} -p \\
                        '{"spec":{"selector":{"version":"blue"}}}'

                    echo "Traffic switched to Blue"
                """
            }
        }

        stage('Verify Production') {
            steps {
                sh """
                    # Verify production traffic is healthy
                    sleep 10

                    # Check error rate
                    ERROR_RATE=\$(curl -sf 'http://prometheus:9090/api/v1/query' \\
                        --data-urlencode 'query=rate(http_requests_total{app="${APP_NAME}",status=~"5.."}[1m]) / rate(http_requests_total{app="${APP_NAME}"}[1m])' \\
                        | jq -r '.data.result[0].value[1] // "0"')

                    echo "Current error rate: \${ERROR_RATE}"

                    if (( \$(echo "\${ERROR_RATE} > 0.05" | bc -l) )); then
                        echo "Error rate too high, auto-rollback!"
                        kubectl patch svc ${APP_NAME} -n ${NAMESPACE} -p \\
                            '{"spec":{"selector":{"version":"green"}}}'
                        exit 1
                    fi

                    echo "Production verification passed"
                """
            }
        }

        stage('Cleanup Green') {
            steps {
                sh """
                    # Scale down old environment after confirming stability
                    kubectl scale deployment ${APP_NAME}-green -n ${NAMESPACE} --replicas=0
                    echo "Green environment scaled down"
                """
            }
        }
    }

    post {
        failure {
            sh """
                # Auto-rollback to Green on failure
                kubectl patch svc ${APP_NAME} -n ${NAMESPACE} -p \\
                    '{"spec":{"selector":{"version":"green"}}}' || true
                echo "Rolled back to Green environment"
            """
            slackSend(
                channel: '#alerts',
                color: 'danger',
                message: "Blue-green deployment failed, rolled back: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
            )
        }
        success {
            slackSend(
                channel: '#deployments',
                color: 'good',
                message: "Blue-green deployment succeeded: ${env.APP_NAME} v${env.VERSION}"
            )
        }
    }
}

8.2 Canary Deployment

// Canary deployment: gradual traffic shifting
stage('Canary Deploy') {
    steps {
        sh """
            # Deploy canary version (1 replica)
            kubectl set image deployment/${APP_NAME}-canary \
                app=${DOCKER_IMAGE} -n ${NAMESPACE} || \
            kubectl create deployment ${APP_NAME}-canary \
                --image=${DOCKER_IMAGE} -n ${NAMESPACE}

            kubectl scale deployment ${APP_NAME}-canary -n ${NAMESPACE} --replicas=1
        """
    }
}

stage('Canary 10% Traffic') {
    steps {
        sh """
            # Adjust traffic ratio via Istio VirtualService
            kubectl patch virtualservice ${APP_NAME} -n ${NAMESPACE} --type='json' \\
                -p='[{"op":"replace","path":"/spec/http/0/route/0/weight","value":90},{"op":"replace","path":"/spec/http/0/route/1/weight","value":10}]'

            echo "10% traffic to canary version"
            sleep 60  # Observe for one minute
        """
    }
}

stage('Canary 50% Traffic') {
    input {
        message "10% canary test looks good, shift to 50%?"
        ok "Shift to 50%"
    }
    steps {
        sh """
            kubectl patch virtualservice ${APP_NAME} -n ${NAMESPACE} --type='json' \\
                -p='[{"op":"replace","path":"/spec/http/0/route/0/weight","value":50},{"op":"replace","path":"/spec/http/0/route/1/weight","value":50}]'
            sleep 120  # Observe for two minutes
        """
    }
}

stage('Canary 100% Traffic') {
    input {
        message "50% canary test looks good, go full?"
        ok "Full release"
        submitter "release-managers"
    }
    steps {
        sh """
            kubectl patch virtualservice ${APP_NAME} -n ${NAMESPACE} --type='json' \\
                -p='[{"op":"replace","path":"/spec/http/0/route/0/weight","value":0},{"op":"replace","path":"/spec/http/0/route/1/weight","value":100}]'

            # Update main deployment
            kubectl set image deployment/${APP_NAME} \\
                app=${DOCKER_IMAGE} -n ${NAMESPACE}
            kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE}

            # Clean up canary
            kubectl delete deployment ${APP_NAME}-canary -n ${NAMESPACE}
        """
    }
}

IX. Pipeline Best Practices

9.1 Pipeline Performance Optimization

pipeline {
    agent any

    options {
        // Timeout control
        timeout(time: 30, unit: 'MINUTES')
        // No concurrent builds
        disableConcurrentBuilds()
        // Keep history
        buildDiscarder(logRotator(numToKeepStr: '30'))
        // Timestamps
        timestamps()
    }

    stages {
        // Skip stages when nothing changed
        stage('Build') {
            when {
                changeset '**/*.go'
                changeset 'go.mod'
            }
            steps {
                sh 'make build'
            }
        }

        // Run independent tasks in parallel
        stage('Test') {
            parallel {
                stage('Unit Test') {
                    steps { sh 'make test-unit' }
                }
                stage('Lint') {
                    steps { sh 'make lint' }
                }
                stage('Security Scan') {
                    steps { sh 'make security-scan' }
                }
            }
        }

        // Cache dependencies
        stage('Restore Cache') {
            steps {
                sh '''
                    if [ -d /go/pkg/mod ]; then
                        cp -r /go/pkg/mod ./go-mod-cache || true
                    fi
                '''
            }
        }
    }
}

9.2 Notifications and Monitoring

// vars/pipelineNotify.groovy
def call(Map config = [:]) {
    def status = config.status ?: currentBuild.currentResult
    def channel = config.channel ?: '#ci-cd'

    def color = 'good'
    def emoji = ':white_check_mark:'

    if (status == 'FAILURE') {
        color = 'danger'
        emoji = ':x:'
    } else if (status == 'UNSTABLE') {
        color = 'warning'
        emoji = ':warning:'
    }

    def duration = currentBuild.durationString ?: 'unknown'
    def message = """
${emoji} Pipeline ${status}
*Job:* ${env.JOB_NAME}
*Build:* #${env.BUILD_NUMBER}
*Branch:* ${env.BRANCH_NAME ?: 'N/A'}
*Duration:* ${duration}
*URL:* ${env.BUILD_URL}
""".stripIndent().trim()

    // Slack notification
    slackSend(channel: channel, color: color, message: message)

    // DingTalk notification
    if (config.dingtalk) {
        dingtalk(
            robot: config.dingtalkRobot ?: 'jenkins',
            type: 'MARKDOWN',
            title: "Pipeline ${status}",
            text: message
        )
    }

    // Email notification (only on failure)
    if (status == 'FAILURE' && config.emailOnFailure != false) {
        emailext(
            subject: "[${status}] ${env.JOB_NAME} #${env.BUILD_NUMBER}",
            body: message,
            to: config.emailTo ?: 'team@example.com',
            mimeType: 'text/html'
        )
    }
}

Summary

The core value of Jenkins Pipeline as Code is: transforming CI/CD processes from “configuration” to “code.” This means pipelines can be version-controlled, code-reviewed, reused, and tested. Key takeaways:

  1. Declarative first: Declarative Pipeline has clear structure and built-in validation, suitable for team collaboration. Use script {} blocks for complex logic rather than going fully scripted
  2. Multibranch pipelines are standard: Automatically create independent jobs for branches and PRs. Combined with when conditions, implement a branch strategy of “main deploys to staging, release deploys to production, PRs only run checks”
  3. Shared libraries eliminate duplication: Extract standard pipeline logic into vars/standardPipeline.groovy; each project needs only a few lines of config. Pin shared library versions to prevent upstream changes from affecting all projects
  4. Parallelization brings significant speedup: Tests, lint, and security scans run in parallel, cutting build time from 20 minutes (sequential) to 5 minutes. Matrix builds cover multiple platforms and versions
  5. Artifact management is essential: Upload each build’s artifacts to a repository (Artifactory/Nexus/S3) with version, build number, Git hash metadata. Deploy from the artifact repository, never rebuild from source
  6. K8s integration provides elasticity: Use K8s Pods as Jenkins Agents, created and destroyed on demand. Different projects use different container images for complete environment isolation
  7. Blue-green/canary must be automated: Deploy to standby → smoke test → switch traffic → verify → cleanup — the entire chain is automated. Auto-rollback on failure; human input only at key decision points

Pipeline as Code isn’t just about moving pipeline configuration into a code file — it’s about managing CI/CD processes with engineering rigor. When each project’s Jenkinsfile is just a dozen lines (calling a shared library), and all complex logic is centrally maintained and versioned in the shared library, CI/CD truly achieves “code-driven governance.”

References & Acknowledgments

This article referenced the following materials during writing. We thank the original authors for their contributions:

  1. Jenkins Pipeline Official Documentation — Jenkins Project, referenced for Jenkins Pipeline Official Documentation