Learn to build reusable, testable pipeline code that follows the DRY principle
A comprehensive, production-ready guide to Jenkins Shared Libraries with working examples, tests, and Job DSL integration.
- Getting Started ← Start here
- Advanced Guide - Testing, Job DSL, advanced patterns
- Reference - Complete examples, API, troubleshooting
- Demo Repository - Working, runnable code
###What is Jenkins Shared Library?
When we say "CI/CD as code," it should embrace modularity and reusability, following the DRY principle (Don't Repeat Yourself). Jenkins Shared Library makes this possible.
The Problem: Imagine 10 Java microservices, each with its own pipeline. The Maven build step is duplicated across all 10. When you add a service, you copy-paste the pipeline. Need to change Maven parameters? Update all 10+ pipelines manually. This is:
- ❌ Time-consuming
- ❌ Error-prone
- ❌ Not scalable
The Solution: Write the Maven build once, reference it everywhere:
// Before: Duplicated in every pipeline
stage('Build') {
sh 'mvn clean package -DskipTests'
}
// After: Shared library (one place)
stage('Build') {
mavenBuild(goals: 'clean package', skipTests: true)
}Update the library once → all pipelines get the change automatically.
- Code Reusability: Write once, use across teams and projects
- Consistency: Uniform workflows and standards
- Maintainability: Update once, apply everywhere
- Testability: Unit tests for pipeline code
- Versioning: Tag and version your CI/CD code
- Jenkins with Pipeline and Job DSL plugins
- Basic Groovy knowledge
- Git repository
my-shared-library/
├── vars/ # Global pipeline steps
│ ├── buildApp.groovy
│ └── buildApp.txt # Documentation
├── src/ # Reusable classes
│ └── com/company/jenkins/
│ └── utils/
│ └── Helper.groovy
├── resources/ # Static files
│ └── scripts/
│ └── deploy.sh
├── test/ # Unit tests
│ └── unit/groovy/
└── build.gradle # Build configuration
File: vars/buildApp.groovy
/**
* Build application using specified build tool.
*/
def call(Map config) {
String buildTool = config.buildTool ?: 'gradle'
String command = ""
switch(buildTool) {
case 'gradle':
command = './gradlew clean build'
break
case 'maven':
command = 'mvn clean package'
break
case 'npm':
command = 'npm install && npm run build'
break
default:
error("Unsupported build tool: ${buildTool}")
}
echo "Building with ${buildTool}..."
sh command
}File: vars/buildApp.txt
NAME
buildApp - Build application using various build tools
SYNOPSIS
buildApp(buildTool: 'gradle')
PARAMETERS
buildTool (String, optional, default: 'gradle')
Build tool to use: 'gradle', 'maven', or 'npm'
EXAMPLES
buildApp(buildTool: 'maven')
buildApp(buildTool: 'npm')
- Go to
Manage Jenkins→System - Find
Global Trusted Pipeline Libraries - Click
Addand configure:- Name:
my-shared-lib - Default version:
main - Retrieval method: Modern SCM → Git
- Repository URL:
https://github.com/your-org/my-shared-library.git - Credentials: Select your Git credentials
- Name:
File: Jenkinsfile (in your application repo)
@Library('my-shared-lib@main') _
pipeline {
agent any
stages {
stage('Build') {
steps {
buildApp(buildTool: 'gradle')
}
}
stage('Test') {
steps {
sh './gradlew test'
}
}
}
}That's it! You've created and used your first shared library function.
Global pipeline steps - Each .groovy file becomes a callable function.
Key points:
- Filename = function name (e.g.,
buildApp.groovy→buildApp()) - Must have a
call()method - Optional
.txtfile for documentation rendered in Jenkins UI
Example structure:
vars/
├── buildApp.groovy # Implementation
├── buildApp.txt # Help documentation in Jenkins UI
├── deployToK8s.groovy
├── dockerBuild.groovy
└── log.groovy
Reusable Groovy classes - Complex, object-oriented code.
When to use:
- Complex business logic
- Utility classes
- When vars/ DSLs aren't flexible enough
Example structure:
src/
└── com/company/jenkins/
├── builders/
│ └── DockerBuilder.groovy
├── deployers/
│ └── KubernetesDeployer.groovy
└── utils/
├── StringHelper.groovy
└── DateHelper.groovy
Usage:
import com.company.jenkins.builders.DockerBuilder
def builder = new DockerBuilder()
builder.build(imageName: 'myapp')Static files - Scripts, templates, config files.
Common use cases:
- Shell scripts for deployment
- HTML email templates
- JSON/XML configuration files
Example structure:
resources/
├── scripts/
│ ├── deploy.sh
│ └── healthcheck.sh
└── templates/
└── notification.html
Usage:
// Load and use resource
def script = libraryResource 'scripts/deploy.sh'
writeFile file: 'deploy.sh', text: script
sh 'chmod +x deploy.sh && ./deploy.sh'Unit and integration tests - Keep pipelines reliable.
test/
└── unit/groovy/
└── com/company/jenkins/
├── builders/
│ └── DockerBuilderSpec.groovy
└── utils/
└── StringHelperSpec.groovy
File: src/com/company/jenkins/utils/StringHelper.groovy
package com.company.jenkins.utils
class StringHelper {
static String toCamelCase(String text) {
return text.replaceAll(/[^a-zA-Z0-9]+([a-zA-Z0-9])/) {
full, firstLetter -> firstLetter.toUpperCase()
}
}
static String toSnakeCase(String text) {
return text.replaceAll(/([a-z])([A-Z])/, '$1_$2').toLowerCase()
}
static String slugify(String text) {
return text.toLowerCase()
.replaceAll(/[^a-z0-9]+/, '-')
.replaceAll(/^-+|-+$/, '')
}
}File: src/com/company/jenkins/shell/Bash.groovy
package com.company.jenkins.shell
class Bash implements Serializable {
private Script steps
private boolean silent
private boolean debug
Bash(Script steps, boolean silent = false, boolean debug = false) {
this.steps = steps
this.silent = silent
this.debug = debug
}
def call(String description, String script) {
steps.echo("Executing: ${description}")
String formattedScript = formatScript(script)
int exitCode = steps.sh(
script: formattedScript,
returnStatus: true
)
if (exitCode != 0) {
steps.error("Script failed with exit code: ${exitCode}")
}
}
String formatScript(String script) {
List<String> lines = []
if (debug) {
lines.add('set -x') // Enable debug mode
}
lines.add('set -e') // Exit on error
lines.add(script)
return lines.join('\n')
}
}Usage in vars:
// vars/bash.groovy
import com.company.jenkins.shell.Bash
def call(String script, Map options = [:]) {
Bash bash = new Bash(
this,
options.silent ?: false,
options.debug ?: false
)
bash.call('Bash execution', script)
}File: vars/log.groovy
def call(String message, Map options = [:]) {
Integer color = options.color ?: 37 // White default
ansiColor('xterm') {
echo "\033[${color}m${message}\033[0m"
}
}
def info(String message) {
call(message, [color: 36]) // Cyan
}
def warn(String message) {
call(message, [color: 33]) // Yellow
}
def error(String message) {
call(message, [color: 31]) // Red
}
def success(String message) {
call(message, [color: 32]) // Green
}Usage:
log.info('Starting build')
log.warn('Deprecated feature')
log.error('Build failed')
log.success('Deployment complete')File: vars/buildDockerImage.groovy
def call(Map config) {
String imageName = config.imageName
List tags = config.tags ?: ['latest']
Map buildArgs = config.buildArgs ?: [:]
// Build image
String buildArgsString = buildArgs.collect { k, v -> "--build-arg ${k}=${v}" }.join(' ')
sh "docker build ${buildArgsString} -t ${imageName}:${tags[0]} ."
// Tag additional versions
tags.drop(1).each { tag ->
sh "docker tag ${imageName}:${tags[0]} ${imageName}:${tag}"
}
echo "Built image: ${imageName} with tags: ${tags.join(', ')}"
}Usage:
buildDockerImage(
imageName: 'myapp',
tags: ['v1.0.0', 'latest'],
buildArgs: [VERSION: '1.0.0', BUILD_DATE: '2025-01-15']
)File: vars/deployToK8s.groovy
def call(Map config) {
String namespace = config.namespace ?: 'default'
String deployment = config.deployment
String image = config.image
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
sh """
kubectl config use-context ${config.cluster}
kubectl set image deployment/${deployment} ${deployment}=${image} -n ${namespace}
kubectl rollout status deployment/${deployment} -n ${namespace}
"""
}
echo "Deployed ${image} to ${namespace}/${deployment}"
}Usage:
deployToK8s(
cluster: 'production',
namespace: 'apps',
deployment: 'user-service',
image: 'registry.company.com/user-service:v1.0.0'
)File: build.gradle
plugins {
id 'groovy'
id 'codenarc'
id 'jacoco'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.codehaus.groovy:groovy-all:3.0.9'
testImplementation 'org.spockframework:spock-core:2.1-groovy-3.0'
testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.17'
testImplementation 'junit:junit:4.13.2'
}
test {
useJUnit()
}File: test/unit/groovy/com/company/jenkins/utils/StringHelperSpec.groovy
package com.company.jenkins.utils
import spock.lang.Specification
import spock.lang.Unroll
class StringHelperSpec extends Specification {
@Unroll
def 'toCamelCase converts "#input" to "#expected"'() {
expect:
StringHelper.toCamelCase(input) == expected
where:
input || expected
'hello-world' || 'helloWorld'
'foo_bar_baz' || 'fooBarBaz'
'test' || 'test'
}
@Unroll
def 'slugify converts "#input" to "#expected"'() {
expect:
StringHelper.slugify(input) == expected
where:
input || expected
'Hello World' || 'hello-world'
'Foo Bar!' || 'foo-bar'
'test' || 'test'
}
}# Run all tests
./gradlew test
# Run specific test
./gradlew test --tests "StringHelperSpec"
# Generate coverage report
./gradlew jacocoTestReport
open build/reports/jacoco/test/html/index.html// ❌ Bad - unpredictable
@Library('my-shared-lib') _
// ✅ Good - specific version
@Library('[email protected]') _
// ✅ Good for development
@Library('my-shared-lib@develop') _def call(Map config) {
// Validate required parameters
List required = ['param1', 'param2']
List missing = required.findAll { !config.containsKey(it) }
if (missing) {
error("Missing required parameters: ${missing.join(', ')}")
}
// Your logic here
}def call(Map config) {
try {
// Your logic
performAction(config)
} catch (Exception e) {
log.error("Failed: ${e.message}")
currentBuild.result = 'FAILURE'
throw e
} finally {
// Cleanup
cleanup()
}
}Create .txt files for all global variables:
NAME
myFunction - Brief description
SYNOPSIS
myFunction(config)
PARAMETERS
config (Map):
- param1 (String, required): Description
- param2 (Boolean, optional, default: false): Description
EXAMPLES
myFunction(param1: 'value')
// ❌ Bad - doing too much
def deployAll(config) {
build()
test()
dockerBuild()
deploy()
}
// ✅ Good - focused
def buildAndTest(config) {
build(config)
test(config)
}
def packageAndDeploy(config) {
dockerBuild(config)
deploy(config)
}- ✅ Complete this getting started guide
- 📖 Read Advanced Guide - Testing, Job DSL, patterns
- 📚 Review Reference - Complete examples and API
- 🧪 Explore Demo Repository - Working code
- Clone/fork this repository
- Customize package names and structure
- Add your organization-specific functions
- Configure in your Jenkins
- Start converting existing pipelines
jenkins-shared-library-tutorial/
├── README.md ← You are here
├── docs/
│ ├── advanced-guide.md ← Testing, Job DSL, team-based patterns
│ └── reference.md ← API reference, troubleshooting
└── demo-repository/
├── vars/ ← Working global variables
├── src/ ← Working classes
├── resources/ ← Example resources
├── test/ ← Complete test suite
├── jobs/ ← Team-based Job DSL structure
│ ├── jobSeed.groovy ← Seed job for team-based CI/CD
│ └── jenkins-controllers/
│ ├── common/ ← Cross-team shared jobs
│ ├── data-science/ ← ML/AI workflows (MLflow, model serving)
│ ├── engineering/ ← Software development (CI/CD, testing)
│ └── devops/ ← Infrastructure (Terraform, Kubernetes)
└── build.gradle ← Team-based build configuration
Ready to get started? Run the demo:
cd demo-repository
./gradlew clean testThis repository demonstrates a modern team-based approach to Jenkins CI/CD, moving beyond traditional environment-based structures (development/production) to domain-specific team organization.
| Team | Focus | Technologies | Pipeline Examples |
|---|---|---|---|
| Common | Shared utilities, monitoring | Jenkins, Slack, backups | Health checks, security scans |
| Data Science | ML/AI workflows | MLflow, SageMaker, Kubernetes | Model training, A/B testing, inference |
| Engineering | Software development | Docker, Kubernetes, testing | CI/CD, deployment strategies, E2E testing |
| DevOps | Infrastructure & platform | Terraform, Kubernetes, monitoring | Infrastructure as Code, compliance |
- Team Autonomy: Each team manages their own CI/CD workflows
- Domain Expertise: Pipelines tailored to specific technology stacks
- Scalability: Teams can evolve independently
- Modern Tooling: Integration with MLflow, Terraform, advanced testing
jobs/jenkins-controllers/
├── common/
│ ├── src/dsl/groovy/commonJobs.groovy
│ └── src/main/groovy/monitoring/
├── data-science/
│ ├── src/dsl/groovy/dataScienceJobs.groovy
│ └── src/main/groovy/
│ ├── ml-experiments/ # Model training, experimentation
│ ├── model-deployment/ # Multi-platform model serving
│ └── data-pipeline/ # Feature engineering, validation
├── engineering/
│ ├── src/dsl/groovy/engineeringJobs.groovy
│ └── src/main/groovy/
│ ├── applications/ # CI/CD, deployment strategies
│ ├── testing/ # E2E, performance, integration
│ └── libraries/ # Shared library development
└── devops/
├── src/dsl/groovy/devopsJobs.groovy
└── src/main/groovy/
├── infrastructure/ # Terraform, cloud resources
├── platform-engineering/ # Kubernetes, service mesh
└── compliance/ # Security, auditing
Data Science Team:
- MLflow experiment tracking and model registry
- Multi-platform model deployment (SageMaker, Kubernetes, Lambda)
- Automated model validation and drift detection
- A/B testing infrastructure
Engineering Team:
- Advanced deployment strategies (rolling, blue-green, canary)
- Comprehensive testing automation (unit, integration, E2E)
- Container security scanning and vulnerability management
- Multi-environment application deployment
DevOps Team:
- Infrastructure as Code with Terraform
- Kubernetes cluster management and compliance
- Security scanning and cost optimization
- Backup, disaster recovery, and monitoring automation
Questions? Check the troubleshooting guide or advanced patterns.
Happy coding! 🚀