Skip to main content
DevopsIntermediate18 min readUpdated March 2025

Jenkins Pipelines

Jenkins is the most widely deployed open-source CI/CD server. Declarative Pipelines define the entire build, test, and deploy process as code in a Jenkinsfile, enabling version control, code review, and reproducibility.

Jenkins Architecture

Jenkins follows a master-agent (controller-agent) architecture:

- Jenkins Controller — The central server that manages configuration, schedules builds, and serves the web UI. It should not run builds directly in production. - Agents (Nodes) — Worker machines that execute pipeline steps. Can be permanent (always-on VMs) or ephemeral (Docker containers, Kubernetes pods spun up per build). - Executors — Slots on an agent for running concurrent builds. Each agent can have multiple executors.

This architecture allows Jenkins to scale horizontally — add more agents to handle more concurrent builds.

Declarative vs Scripted Pipeline

Jenkins supports two pipeline syntaxes:

  • Declarative Pipeline — Structured, opinionated syntax with a predefined set of sections. Easier to read and write. Recommended for most use cases.
  • Scripted Pipeline — Full Groovy DSL. More flexible and powerful but harder to read. Use when declarative syntax is insufficient.
  • Both are stored in a Jenkinsfile at the root of the repository.
  • Declarative pipelines have built-in validation and better IDE support.

Declarative Pipeline Syntax

A complete Declarative Pipeline for a Java application:

groovy
// Jenkinsfile (Declarative Pipeline)
pipeline {
    agent {
        docker {
            image 'maven:3.9-eclipse-temurin-17' args '-v $HOME/.m2:/root/.m2'
        }
    }

    environment {
        APP_NAME = 'my-java-app'
        DOCKER_REGISTRY = 'registry.company.com'
        SONAR_TOKEN = credentials('sonar-token')
        DOCKER_CREDS = credentials('docker-registry-creds')
    }

    options {
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '10'))
        disableConcurrentBuilds()
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
                sh 'git log --oneline -5'
            }
        }

        stage('Build') {
            steps {
                sh 'mvn clean compile -q'
            }
        }

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

        stage('Code Quality') {
            steps {
                sh """
                    mvn sonar:sonar \
                      -Dsonar.projectKey=${APP_NAME} \
                      -Dsonar.token=${SONAR_TOKEN}
                """
            }
        }

        stage('Package') {
            steps {
                sh 'mvn package -DskipTests'
                archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
            }
        }

        stage('Build Docker Image') {
            steps {
                script {
                    def image = docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}")
                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-creds') {
                        image.push()
                        image.push('latest')
                    }
                }
            }
        }

        stage('Deploy to Staging') {
            when {
                branch 'develop'
            }
            steps {
                sh "kubectl set image deployment/${APP_NAME} " +
                   "${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} "+ "-n staging"
            }
        }

        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            input {
                message "Deploy to production?" ok="Deploy" submitter="devops-team"
            }
            steps {
                sh "kubectl set image deployment/${APP_NAME} " +
                   "${APP_NAME}=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} "+ "-n production"
            }
        }
    }

    post {
        success {
            slackSend channel: '#deployments', color: 'good',
                message: "SUCCESS: ${APP_NAME} build ${BUILD_NUMBER} deployed"
        }
        failure {
            slackSend channel: '#deployments', color: 'danger',
                message: "FAILED: ${APP_NAME} build ${BUILD_NUMBER} - ${BUILD_URL}"
            emailext to: 'team@company.com',
                subject: "Build Failed: ${APP_NAME}",
                body: "Check ${BUILD_URL} for details"
        }
        always {
            cleanWs()   // Clean workspace after build
        }
    }
}

Shared Libraries

Shared Libraries allow you to define reusable pipeline code in a separate repository and import it into multiple Jenkinsfiles. This prevents code duplication across projects.

Structure of a shared library: - vars/ — Global variables and functions callable from pipelines - src/ — Groovy classes for complex logic - resources/ — Non-Groovy files (scripts, templates)

Configure shared libraries in Jenkins: Manage Jenkins > Configure System > Global Pipeline Libraries.

groovy
// vars/deployApp.groovy (in shared library repo)
def call(Map config) {
    pipeline {
        agent any
        stages {
            stage('Deploy') {
                steps {
                    sh "kubectl set image deployment/${config.appName} " +
                       "${config.appName}=${config.image}:${config.tag} "+ "-n ${config.namespace}"
                }
            }
        }
    }
}

// Jenkinsfile in application repo (uses shared library)
@Library('my-shared-library@main') _

deployApp(
    appName: 'web-app',
    image: 'registry.company.com/web-app',
    tag: env.BUILD_NUMBER,
    namespace: 'production'
)

Key Takeaways

  • Jenkins uses a controller-agent architecture — the controller schedules builds, agents execute them.
  • Declarative Pipelines in a Jenkinsfile define the entire CI/CD process as version-controlled code.
  • Use parallel stages to run independent tasks (unit tests, integration tests) simultaneously.
  • The input step pauses a pipeline for manual approval before deploying to production.
  • Shared Libraries eliminate code duplication by centralizing reusable pipeline logic.

Contact Us

Have a question or feedback? Fill out the form below or reach us directly at support@nvaitraining.com