Jenkins Shared Libraries

25 minLesson 5 of 8

Learning Objectives

  • Understand Shared Library structure and loading
  • Write custom pipeline steps and utilities
  • Share libraries across multiple pipelines
  • Version and test shared libraries

What Are Shared Libraries?

Shared Libraries let you define reusable pipeline code in a separate Git repository, then import it into any Jenkinsfile. This avoids duplicating pipeline logic across projects.

Why Shared Libraries?

WithoutWith
Copy-paste pipeline codeSingle source of truth
Inconsistent practicesStandardized steps
Hard to updateUpdate once, applies everywhere
No code review for pipelinesLibrary goes through PR review

Library Structure

shared-library/
├── vars/                    # Global pipeline steps
│   ├── buildApp.groovy      # Called as buildApp()
│   ├── deployToK8s.groovy   # Called as deployToK8s()
│   ├── notifySlack.groovy   # Called as notifySlack()
│   └── standardPipeline.groovy
├── src/                     # Helper classes
│   └── org/
│       └── nextgen/
│           ├── Docker.groovy
│           └── Kubernetes.groovy
├── resources/               # Non-Groovy files
│   └── templates/
│       └── deployment.yaml
└── Jenkinsfile              # Test the library itself

Writing Custom Steps

vars/buildApp.groovy

// Called as: buildApp(language: 'node', version: '20')
def call(Map config = [:]) {
    def language = config.language ?: 'node'
    def version = config.version ?: '20'
 
    stage('Build') {
        switch(language) {
            case 'node':
                sh "nvm use ${version} && npm ci && npm run build"
                break
            case 'python':
                sh "pip install -r requirements.txt && python setup.py build"
                break
            case 'java':
                sh "mvn clean package -DskipTests"
                break
        }
    }
}

vars/deployToK8s.groovy

def call(Map config) {
    def namespace = config.namespace ?: 'default'
    def image = config.image
    def deployment = config.deployment
    def timeout = config.timeout ?: 300
 
    stage('Deploy to Kubernetes') {
        withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
            sh """
                kubectl set image deployment/${deployment} \
                  app=${image} \
                  --namespace=${namespace}
 
                kubectl rollout status deployment/${deployment} \
                  --namespace=${namespace} \
                  --timeout=${timeout}s
            """
        }
    }
}

vars/notifySlack.groovy

def call(Map config) {
    def status = config.status ?: 'unknown'
    def channel = config.channel ?: '#builds'
 
    def color = status == 'success' ? 'good' : 'danger'
    def emoji = status == 'success' ? '✅' : '❌'
 
    slackSend(
        channel: channel,
        color: color,
        message: "${emoji} *${env.JOB_NAME}* #${env.BUILD_NUMBER} - ${status}\n${env.BUILD_URL}"
    )
}

vars/standardPipeline.groovy

// Full pipeline template
def call(Map config) {
    pipeline {
        agent { docker { image config.buildImage ?: 'node:20' } }
 
        stages {
            stage('Checkout') {
                steps { checkout scm }
            }
            stage('Install') {
                steps { sh config.installCmd ?: 'npm ci' }
            }
            stage('Test') {
                steps { sh config.testCmd ?: 'npm test' }
            }
            stage('Build') {
                steps { sh config.buildCmd ?: 'npm run build' }
            }
            stage('Deploy') {
                when { branch 'main' }
                steps {
                    deployToK8s(
                        image: "${config.registry}/${config.appName}:${BUILD_NUMBER}",
                        deployment: config.appName,
                        namespace: config.namespace ?: 'production'
                    )
                }
            }
        }
 
        post {
            success { notifySlack(status: 'success') }
            failure { notifySlack(status: 'failure') }
        }
    }
}

Using the Library

Configure in Jenkins

Manage Jenkins → System → Global Pipeline Libraries:

  • Name: nextgen-shared
  • Default version: main
  • Source: Git → https://github.com/org/jenkins-shared-library.git

In a Jenkinsfile

@Library('nextgen-shared') _
 
// Use individual steps
pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                buildApp(language: 'node', version: '20')
            }
        }
        stage('Deploy') {
            steps {
                deployToK8s(
                    image: 'registry.example.com/my-app:latest',
                    deployment: 'my-app',
                    namespace: 'production'
                )
            }
        }
    }
    post {
        success { notifySlack(status: 'success') }
        failure { notifySlack(status: 'failure') }
    }
}

Using the Full Template

// Entire Jenkinsfile in 10 lines
@Library('nextgen-shared') _
 
standardPipeline(
    appName: 'nextgen-api',
    buildImage: 'node:20',
    registry: 'registry.example.com',
    namespace: 'production',
    installCmd: 'npm ci',
    testCmd: 'npm run test',
    buildCmd: 'npm run build'
)

Versioning

// Use a specific version/branch/tag
@Library('nextgen-shared@v2.0.0') _
@Library('nextgen-shared@feature-branch') _
 
// Load dynamically
library identifier: 'nextgen-shared@main',
        retriever: modernSCM([
            $class: 'GitSCMSource',
            remote: 'https://github.com/org/jenkins-shared-library.git'
        ])

Summary

You've learned:

  • Shared Library structure (vars, src, resources)
  • Writing custom pipeline steps as reusable functions
  • Creating full pipeline templates
  • Loading and versioning libraries in Jenkinsfiles
  • Standardizing CI/CD across teams

Next Steps

Next, we'll explore Jenkins security, RBAC, and multi-branch pipeline strategies.