Skip to content

Jenkins Pipeline — Declarative & Scripted Pipeline Guide

DodaTech Updated 2026-06-24 5 min read

In this tutorial, you'll learn about Jenkins Pipeline. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Jenkins Pipeline is a suite of plugins that supports implementing and integrating continuous delivery pipelines into Jenkins, using a Groovy-based DSL to define build, test, and deploy stages as code.

What You'll Learn

Why It Matters

Manual build and deployment processes are slow, error-prone, and untraceable. Jenkins Pipeline gives you a single source of truth for your entire delivery process — version-controlled in your repository, automatically triggered on code changes, and visible through an intuitive Blue Ocean interface. DodaTech uses Jenkins Pipeline to orchestrate builds across 40+ Microservices, running 300+ daily pipeline executions with zero manual intervention.

Real-World Use

Durga Antivirus Pro's CI pipeline compiles kernel modules across 6 operating system versions, runs 15,000+ unit and integration tests, performs Static Analysis with SonarQube, packages installers for Windows, macOS, and Linux, and signs binaries — all defined in a single Jenkinsfile.

flowchart LR
    A[Code Push] --> B[SCM Poll / Webhook]
    B --> C[Jenkinsfile Declared]
    C --> D[Stage: Build]
    D --> E[Stage: Test]
    E --> F[Stage: Static Analysis]
    F --> G[Stage: Package]
    G --> H[Stage: Deploy to Staging]
    H --> I[Stage: Approval Gate]
    I --> J[Stage: Production Deploy]
    style C fill:#D24939,color:#fff
â„šī¸ Info

Prerequisites: Basic Groovy or Java syntax familiarity. A running Jenkins instance with Pipeline plugin installed.

Pipeline Types

Declarative Pipeline

Declarative syntax provides a simpler, structured way to define pipelines with a predefined pipeline block:

// Jenkinsfile — Declarative Pipeline
pipeline {
    agent any

    tools {
        maven 'Maven-3.9'
        jdk 'JDK-21'
    }

    environment {
        DOCKER_REGISTRY = 'registry.dodatech.com'
        APP_NAME        = 'user-service'
    }

    parameters {
        string name: 'DEPLOY_ENV', defaultValue: 'staging', description: 'Target environment'
    }

    triggers {
        pollSCM('H/5 * * * *')
    }

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

        stage('Build') {
            steps {
                sh 'mvn clean compile -q'
            }
            post {
                failure {
                    slackSend color: '#CC0000', message: "Build failed: ${env.BUILD_URL}"
                }
            }
        }

        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'mvn test -q'
                    }
                }
                stage('Integration Tests') {
                    steps {
                        sh 'mvn verify -q -Pintegration'
                    }
                }
            }
            post {
                always {
                    junit '**/target/surefire-reports/*.xml'
                }
            }
        }

        stage('Package') {
            when {
                branch 'main'
            }
            steps {
                sh 'mvn package -DskipTests'
                sh "docker build -t ${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} ."
            }
        }

        stage('Deploy') {
            input {
                message "Deploy ${APP_NAME} to ${params.DEPLOY_ENV}?"
                ok "Deploy"
            }
            steps {
                sh "kubectl set image deployment/${APP_NAME} ${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} -n ${params.DEPLOY_ENV}"
            }
        }
    }

    post {
        always {
            cleanWs()
        }
        success {
            emailext(
                subject: "Pipeline succeeded: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                to: 'team@dodatech.com',
                body: "The pipeline completed successfully. See ${env.BUILD_URL}"
            )
        }
        failure {
            emailext(
                subject: "Pipeline failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}",
                to: 'team@dodatech.com',
                body: "The pipeline failed. See ${env.BUILD_URL}"
            )
        }
    }
}

Expected behavior: On every push to main, Jenkins checks out the code, runs unit and integration tests in parallel, packages the application, waits for manual approval, then deploys to the target environment.

Scripted Pipeline

Scripted syntax gives full control with a more imperative Groovy style:

// Jenkinsfile — Scripted Pipeline
def buildApp() {
    sh 'mvn clean compile'
}

def testApp() {
    sh 'mvn test'
}

def packageApp() {
    sh 'mvn package -DskipTests'
}

node('docker-agent') {
    try {
        stage('Checkout') {
            checkout scm
        }

        stage('Build') {
            buildApp()
        }

        stage('Test') {
            testApp()
        }

        stage('Package') {
            packageApp()
        }

        stage('Deploy') {
            if (env.BRANCH_NAME == 'main') {
                input "Deploy to production?"
                sh 'kubectl apply -f k8s/deployment.yaml'
            }
        }
    } catch (Exception e) {
        currentBuild.result = 'FAILURE'
        echo "Pipeline failed: ${e.message}"
        error e.message
    } finally {
        cleanWs()
    }
}

Multi-Branch Pipeline

Automatically creates pipelines for each branch in your repository:

pipeline {
    agent any

    triggers {
        // Build on push events
        eventTrigger {
            events([
                push(),
                pullRequest {
                    actions(['opened', 'synchronize'])
                }
            ])
        }
    }

    stages {
        stage('Build') {
            steps {
                script {
                    // Branch-specific build logic
                    if (env.BRANCH_NAME == 'main') {
                        sh 'mvn package -Pproduction'
                    } else if (env.BRANCH_NAME.startsWith('feature/')) {
                        sh 'mvn package -Ptest'
                    }
                }
            }
        }
    }
}

Pipeline Global Variables

Share data across stages using the env object or custom variables:

pipeline {
    agent any

    environment {
        VERSION = readFile('version.txt').trim()
    }

    stages {
        stage('Set Version') {
            steps {
                script {
                    env.GIT_COMMIT_HASH = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
                    echo "Building version: ${env.VERSION}-${env.GIT_COMMIT_HASH}"
                }
            }
        }

        stage('Tag Release') {
            when {
                branch 'main'
            }
            steps {
                sh "git tag v${env.VERSION}-${env.BUILD_NUMBER}"
                sh 'git push origin --tags'
            }
        }
    }
}

Common Configuration Mistakes

  1. Not using when conditions correctly: Without when directives, all stages run on every branch. Use when { branch 'main' } to restrict deployment stages.

  2. Storing secrets in Jenkinsfile: Credentials should use Jenkins credential store with credentialsId references, never plain text.

  3. Missing parallel boundaries: Test stages that don't use parallel run sequentially, doubling pipeline duration. Wrap independent test suites in parallel {}.

  4. Using input without timeout: An input step without a timeout can block the pipeline indefinitely. Always add timeout parameter: time(time: 1, unit: 'DAYS').

  5. Ignoring checkout scm in shared library pipelines: When using shared libraries, the pipeline must explicitly check out the source code with checkout scm.

Practice Questions

  1. What is the difference between Declarative and Scripted Pipeline syntax? Answer: Declarative is a structured syntax with a predefined pipeline block, simpler for most use cases. Scripted is a more flexible, imperative Groovy-based syntax suitable for complex logic.

  2. How does the when directive control stage execution? Answer: The when directive evaluates conditions before a stage runs. It supports branch, environment, expression, not, allOf, and anyOf conditions.

  3. What is the purpose of post in Declarative Pipeline? Answer: The post section defines actions that run after the pipeline or stage completes, with conditions like always, success, failure, unstable, and changed.

  4. How do you pass parameters to a pipeline run? Answer: Define a parameters block in Declarative or use properties([parameters(...)]) in Scripted. Parameters are accessible via params.PARAM_NAME.

Challenge

Create a Declarative Pipeline that builds a Node.js application, runs linting and unit tests in parallel, performs container scanning with Trivy, publishes the Docker image to a private registry, deploys to Kubernetes staging with canary rollout, runs smoke tests, and promotes to production on approval.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro