Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Jenkins Shared Libraries: Reusable Pipeline Code

By Kokil Thapa | Last reviewed: September 2026

Jenkins Shared Libraries: Reusable Pipeline Code is how you stop copying the same Groovy deploy blocks into twenty Jenkinsfiles. On real client projects I maintain with GitLab CI for Laravel, the same problem appears everywhere: one team fixes a broken PHP-FPM reload step, and five other repos still run the old script. Shared Libraries solve that by storing pipeline logic in a versioned Git repo Jenkins loads at runtime. This guide covers structure, configuration, calling patterns, versioning, and the mistakes I see when teams adopt them for the first time.

What are Jenkins Shared Libraries and how do they work?

A Shared Library is a Git repository Jenkins clones and compiles before your Pipeline runs. Jenkins scans three directories inside that repo and exposes their contents to your Jenkinsfile. Global variables live in vars/. Groovy classes live in src/. Static files live in resources/.

Jenkins loads libraries in a fixed order. First it reads @Library annotations in your Jenkinsfile. Then it applies folder-level or global configuration from Manage Jenkins → System → Global Pipeline Libraries. Each library needs a name, a default Git URL, and a default version such as main or v2.4.0.

The library name becomes a namespace. If you register a library called company-pipeline, you can call companyPipeline.deployLaravel() from any Pipeline that imports it. That import happens at the top of the Jenkinsfile, before the pipeline {} block in Declarative syntax.

Jenkins Shared Libraries ArchitectureGit Library Repovars/ global stepssrc/ Groovy classesresources/ static filesJenkins ControllerLoads and caches libResolves @Library pinBuild AgentsRun pipeline stepsSSH, Docker, testsConsumer Jenkinsfile@Library('company-pipeline@v2') _pipeline { stages { ... } }
Jenkins Shared Libraries: Reusable Pipeline Code flows from a Git repo through the controller to agents running thin Jenkinsfiles.

Think of Shared Libraries as the pipeline equivalent of Terraform modules for reusable infrastructure. You centralise logic. You version it. You let each consumer pick a release. The difference is runtime: Jenkins fetches and compiles Groovy on every run unless caching is enabled.

For teams also running Jenkins distributed builds with agents, libraries run on the controller first. Step bodies execute on agents you label in the Jenkinsfile. Keep SSH keys and deploy credentials in Jenkins Credential Store, not inside library source.

The three directory types explained

  • vars/ — Global callable steps. Each file becomes a function. File deployLaravel.groovy becomes deployLaravel().
  • src/ — Namespaced Groovy classes for complex logic, unit tests, and typed helpers.
  • resources/ — Templates, JSON configs, or shell scripts you read with libraryResource.

How do you set up a Jenkins Shared Library repository?

Start with a dedicated Git repo. Do not mix application code and pipeline code in the same repository unless you have a strong reason. Pipeline libraries change on a different cadence than product releases.

Create the standard skeleton:

company-jenkins-library/
├── vars/
│   ├── deployLaravel.groovy
│   └── runComposer.groovy
├── src/
│   └── org/
│       └── company/
│           └── pipeline/
│               └── DeployConfig.groovy
├── resources/
│   └── org/company/pipeline/
│       └── nginx-laravel.conf.tpl
├── test/
│   └── org/company/pipeline/
│       └── DeployConfigTest.groovy
└── README.md

Register the library in Jenkins. Go to Manage Jenkins → System → Global Pipeline Libraries. Click Add. Set Name to company-pipeline. Set Default version to main. Enable Load implicitly only if every job on the controller should receive it without an annotation.

For folder-scoped libraries, open a folder in the Jenkins UI. Click Configure. Under Pipeline Libraries, add the same Git URL with a folder-specific default version. That pattern works well when legal-tech and eCommerce repos need different deploy scripts but share lint steps.

Shared Library Repo Layoutcompany-jenkins-library/vars/Global step filessrc/Groovy classesresources/Templates and configsExample: deployLaravel.groovydef call(Map config) { sh "composer install" }Invoked as deployLaravel(env: 'prod')
Standard Jenkins Shared Libraries folder layout with vars, src, and resources directories.

Minimal global variable example

File: vars/runComposer.groovy

def call(Map config = [:]) {
    def phpBin = config.php ?: 'php'
    def noDev = config.production ? '--no-dev' : ''
    sh "${phpBin} -v"
    sh "${phpBin} $(which composer) install ${noDev} --prefer-dist --no-interaction"
}

File: src/org/company/pipeline/DeployConfig.groovy

package org.company.pipeline

class DeployConfig implements Serializable {
    String host
    String user
    String releasePath
    boolean zeroDowntime = true
}

Wire credentials through Jenkins, not hard-coded strings. Use withCredentials inside the global var. That matches how I handle deploy keys on Linux system administration engagements where one missed permission breaks every downstream job.

How do you call Shared Library functions from a Jenkinsfile?

Import the library at the top of your Jenkinsfile. The underscore after the annotation is required when you load implicitly without assigning to a variable.

@Library('company-pipeline@v2.1.0') _

pipeline {
    agent { label 'linux-php' }
    stages {
        stage('Install') {
            steps {
                runComposer production: true, php: 'php8.3'
            }
        }
        stage('Deploy') {
            steps {
                deployLaravel(
                    host: 'prod.example.com',
                    user: 'deploy',
                    releasePath: '/var/www/app'
                )
            }
        }
    }
    post {
        failure {
            emailext subject: 'Build failed', to: 'ops@example.com'
        }
    }
}

If you need a class from src/, import it explicitly inside the Jenkinsfile or inside another Groovy file in the library:

import org.company.pipeline.DeployConfig

@Library('company-pipeline@v2.1.0') _

pipeline {
    agent any
    stages {
        stage('Deploy') {
            steps {
                script {
                    DeployConfig cfg = new DeployConfig(
                        host: 'prod.example.com',
                        user: 'deploy',
                        releasePath: '/var/www/app'
                    )
                    deployLaravel(cfg)
                }
            }
        }
    }
}

For Declarative pipelines, keep orchestration in the Jenkinsfile. Push implementation detail into the library. A Jenkinsfile should read like a table of contents, not a shell script dump. That separation mirrors Jenkins Declarative Pipeline best practice: stages visible, steps hidden.

Shared Library Pipeline FlowGit PushJenkinsfile@Library pinShared LibraryrunComposer()deployLaravel()SSH + symlinkStage breakdown on agentCheckoutTestBuild assetsDeployLibrary handles Composer, PHPUnit, and Deployer stepsJenkinsfile only declares stages and parameters
Execution flow for Jenkins Shared Libraries: Reusable Pipeline Code from Git push through library steps to production deploy.

Using libraryResource for templates

Store an Nginx snippet in resources/org/company/pipeline/nginx-laravel.conf.tpl. Read it inside a global var:

def call(Map config) {
    def tpl = libraryResource 'org/company/pipeline/nginx-laravel.conf.tpl'
    writeFile file: 'nginx.conf', text: tpl.replace('{{APP_ROOT}}', config.root)
    sh 'nginx -t -c $(pwd)/nginx.conf'
}

Validate JSON configs with the site JSON formatter before committing them to resources/. A malformed config breaks every consumer pipeline at once.

How do Jenkins Shared Libraries compare to inline pipeline code?

Inline Groovy in each Jenkinsfile works for one or two repos. It fails when you maintain a dozen Laravel, WordPress, and Symfony projects with overlapping deploy steps. Shared Libraries trade local visibility for central maintenance.

CriteriaInline JenkinsfileShared Library
Duplication across reposHigh — copy-paste driftLow — single source of truth
Upgrade effortEdit every JenkinsfileTag a release, bump pin per repo
TestabilityHard without full pipeline runsUnit-test Groovy classes in src/
OnboardingRead one long file per repoRead library docs + thin Jenkinsfile
Blast radius of a bugOne repoAll consumers on that version pin
Best fitPrototypes, single-app teamsMulti-repo agencies and platform teams

My default rule: three similar pipelines means build a library. Fewer than that, keep code inline until patterns stabilise. That is the same incremental approach I use when choosing between Jenkins Freestyle vs Pipeline for legacy jobs.

Shared Libraries also pair well with Ansible roles and Galaxy reusable automation. Jenkins orchestrates when to run. Ansible performs server configuration. Do not reimplement Ansible inside Groovy unless you need tight Jenkins credential integration.

What are common mistakes when using Jenkins Shared Libraries?

The most expensive mistake is pinning every job to @Library('company-pipeline@main') _. A breaking change on main fails production deploys for all repos at once. Pin to tags or release branches. Bump deliberately after testing on a staging folder.

Second mistake: putting secrets in library source. Groovy in Git is visible to anyone with repo access. Use Jenkins credentials bindings. Rotate keys through the credential store, not through library commits.

Third mistake: giant global vars that do everything. A 400-line deployEverything.groovy becomes untestable. Split into small vars and typed classes. Follow the same modularity you would expect in application code.

  1. Pin library versions per environment — @dev for staging folders, semver tags for production.
  2. Keep Jenkinsfiles under 80 lines; if longer, move logic to the library.
  3. Log library version at pipeline start so build logs show which code ran.
  4. Document breaking changes in the library README with migration notes.
  5. Run code coverage gates in CI on application repos, not only on the library itself.

Another trap: calling non-serializable objects inside vars used with Declarative Pipeline restart. Mark helper classes with implements Serializable when they hold state across checkpoints. The official Jenkins documentation on Shared Libraries covers serialization rules in detail.

On projects where I deploy Laravel with Deployer 7, I keep the Deployer recipe in the app repo. The Shared Library only wraps SSH, release path discovery, and PHP-FPM reload. That boundary stopped a bad library change from overwriting client-specific deploy.php files.

Library Version PinningWhich environment?ProductionPin semver tagStagingPin release branchDev sandboxmain OK with careSafe upgrade path1. Tag library v2.2.0 after unit tests pass2. Bump one staging Jenkinsfile and verify deploy3. Roll production repos in batches, not all at once
Version pinning decision tree for Jenkins Shared Libraries: Reusable Pipeline Code across production, staging, and dev environments.

How do you test and version Jenkins Shared Libraries safely?

Unit-test classes under src/ with JUnit and Gradle or Maven. Jenkins provides a jenkins-pipeline-unit test harness for mocking pipeline steps in global vars. Run those tests in the library repo's own Jenkins job or in GitHub Actions before tagging.

/* vars/deployLaravel.groovy — add at top for traceability */
def call(Map config) {
    echo "company-pipeline library commit: ${env.LIBRARY_COMPANY_PIPELINE_VERSION ?: 'unknown'}"
    // deploy steps...
}

Semantic versioning keeps consumer upgrades predictable. Tag v1.0.0 when the API is stable. Bump minor for backward-compatible features. Bump major when you rename a global var or change required parameters.

For integration testing, create a dedicated Jenkins folder called library-canary. Point test jobs at a feature branch of the library. Merge only after a green deploy to a throwaway VM. That workflow aligns with build pipeline automation best practices I apply on Notary Kathmandu sister sites that share deploy patterns.

Laravel-specific library pattern

A practical global var for PHP 8.3 and Laravel 12 or 13 projects might look like this:

def call(Map config) {
    def php = config.php ?: 'php8.3'
    sh "${php} -v"
    sh "${php} $(which composer) install --no-dev --prefer-dist --no-interaction"
    sh "${php} artisan config:cache"
    sh "${php} artisan route:cache"
    sh "${php} artisan view:cache"
    sshagent(credentials: [config.sshCred]) {
        sh "dep deploy ${config.stage}"
    }
}

Keep .env on the server, not in the library. Run migrations behind a manual approval step for production. These rules mirror what I document in build a CI/CD pipeline with Jenkins tutorials for PHP teams.

If your organisation standardises on GitLab instead, compare approaches in GitHub Actions vs Azure Pipelines before forcing Jenkins everywhere. Shared Libraries shine when Jenkins is already your hub for legacy and multi-stack repos.

For credential debugging, test regex against sample strings in the regex tester. Pipeline logs often hide trailing whitespace in secret IDs that breaks withCredentials lookups.

Key Takeaways

  • Store reusable Groovy in a dedicated Git repo with vars/, src/, and resources/ — not scattered across Jenkinsfiles.
  • Pin library versions to semver tags in production; never float on main for deploy pipelines.
  • Keep Jenkinsfiles thin: stages and parameters visible, implementation inside global vars and classes.
  • Never commit secrets to the library; bind Jenkins credentials inside step bodies.
  • Test Groovy classes with pipeline-unit before tagging; roll upgrades through a canary folder first.
  • Pair Shared Libraries with server-side tools like Ansible or Deployer instead of reimplementing them in Groovy.

People Also Ask

What is the difference between a global variable and a class in a Jenkins Shared Library?

Global variables in vars/ are single-file steps callable by name from any Pipeline. Classes in src/ hold typed logic, helpers, and unit tests under a Java-style package path. Use vars for entry points. Use classes when you need structure, inheritance, or test coverage without starting a full pipeline run.

Can Jenkins Shared Libraries work with Multibranch Pipelines?

Yes. Multibranch jobs read the Jenkinsfile from each branch. The @Library annotation at the top of that file applies per branch. You can pin staging branches to a library feature branch while production branches stay on a stable tag.

How do Shared Libraries relate to Jenkins Configuration as Code?

JCasC can register Global Pipeline Libraries in YAML so new controllers boot with the same library URLs and defaults. Shared Libraries hold step logic. JCasC holds controller configuration. Use both when rebuilding controllers or running enterprise application development environments that must reproduce cleanly.

Are Jenkins Shared Libraries better than GitLab CI includes or GitHub Actions reusable workflows?

They solve the same DRY problem on different platforms. Shared Libraries are the Jenkins-native answer with Groovy and deep Pipeline integration. GitLab uses include: templates. GitHub uses reusable workflows. Pick the mechanism your controller already runs; migrating purely for syntax rarely pays off.

Ship consistent pipelines without copy-paste drift

Jenkins Shared Libraries: Reusable Pipeline Code is the fastest path to consistent deploys when you maintain more than a handful of repos. Start with one global var for Composer install and one for SSH deploy. Tag v1.0.0. Pin production jobs to that tag. Expand only when a second team asks for the same step.

If you want help designing a library for Laravel, WordPress, or mixed PHP stacks — or migrating from freestyle jobs — see the custom software development and support and maintenance services. For a live example of disciplined deploy automation, review the Adventure Third Pole Trek portfolio entry. Ready to centralise your pipeline logic? Contact us to plan your Shared Library structure.

Frequently Asked Questions

A versioned Git repository of Groovy classes, global vars, and static resources that Jenkins clones and compiles before each Pipeline run. You define reusable steps once, pin a branch or tag per job, and keep Jenkinsfiles thin.

Jenkins scans three directories and exposes their contents differently. Files in vars/ become global callable steps — deployLaravel.groovy becomes deployLaravel(). The src/ directory holds namespaced Groovy classes for complex logic, typed helpers, and unit tests under Java-style package paths like org.company.pipeline. The resources/ directory stores static templates, JSON configs, or shell scripts you read at runtime with libraryResource. In practice I use vars/ for pipeline entry points, src/ when logic needs structure and test coverage, and resources/ for Nginx snippets or config templates shared across Laravel and WordPress deploy jobs.

Start with a dedicated Git repo — do not mix pipeline code with application code unless you have a strong reason. Create the standard skeleton with vars/, src/org/company/pipeline/, resources/org/company/pipeline/, and a test/ directory for Groovy unit tests. Register it in Manage Jenkins → System → Global Pipeline Libraries: set Name to your namespace like company-pipeline, add the Git URL, and set a default version such as main or v2.4.0. For folder-scoped libraries, open the folder in Jenkins UI, click Configure, and add the same Git URL with a folder-specific default version. That pattern works well when legal-tech and eCommerce repos need different deploy scripts but share lint steps.

Import the library at the top of your Jenkinsfile before the pipeline block. Use @Library('company-pipeline@v2.1.0') _ — the underscore is required when loading without assigning to a variable. Then call global vars directly inside stages: runComposer production: true, php: 'php8.3'. For classes in src/, import them explicitly with import org.company.pipeline.DeployConfig and instantiate inside a script block. Keep the Jenkinsfile as a table of contents — stages and parameters visible, implementation detail pushed into the library. A well-structured Jenkinsfile should read like orchestration, not a shell script dump.

Never float production deploy pipelines on main. Pinning every job to @Library('company-pipeline@main') _ means one breaking commit fails production deploys for all repos at once. Pin to semver tags or release branches and bump deliberately after testing on a staging folder. Use @dev or feature branches for staging folders, semver tags for production. Log the library version at pipeline start so build logs show exactly which code ran. Document breaking changes in the library README with migration notes before tagging a major release.

No. Groovy in Git is visible to anyone with repo access, so putting secrets in library source is one of the most expensive mistakes teams make. Wire credentials through the Jenkins Credential Store and bind them inside global vars with withCredentials or sshagent. Rotate keys through the credential store, not through library commits. On Linux deploy engagements I have seen one missed credential binding break every downstream job. For credential debugging, watch for trailing whitespace in secret IDs — pipeline logs often hide characters that break withCredentials lookups.

Inline Groovy works for one or two repos but fails when you maintain a dozen Laravel, WordPress, and Symfony projects with overlapping deploy steps. Shared Libraries trade local visibility for central maintenance. Inline code causes high duplication and copy-paste drift — one team fixes a broken PHP-FPM reload step while five other repos still run the old script. Libraries give you a single source of truth, unit-testable Groovy classes, and predictable upgrades by tagging a release and bumping the pin per repo. The blast radius of a bug is one repo with inline code versus all consumers on a given version pin with a library.

The top four I see repeatedly: pinning production jobs to main, committing secrets to library source, writing giant 400-line global vars that do everything, and using non-serializable objects inside vars with Declarative Pipeline restart. Mark helper classes with implements Serializable when they hold state across checkpoints. Split large vars into small entry points and typed classes. Keep Jenkinsfiles under 80 lines — if longer, move logic to the library. Another trap is reimplementing Ansible or Deployer inside Groovy when server-side tools already do the job better. Pair the library with those tools instead of replacing them.

Unit-test classes under src/ with JUnit through Gradle or Maven. Use the jenkins-pipeline-unit test harness to mock pipeline steps in global vars without starting a full Jenkins run. Run those tests in the library repo's own Jenkins job or in GitHub Actions before tagging. For integration testing, create a dedicated Jenkins folder called library-canary, point test jobs at a feature branch of the library, and merge only after a green deploy to a throwaway VM. Validate JSON configs stored in resources/ before committing — a malformed config breaks every consumer pipeline at once.

Global variables in vars/ are single-file steps callable by name from any Pipeline. Classes in src/ hold typed logic, helpers, and unit tests under a Java-style package path. Use vars for entry points; use classes when you need structure, inheritance, or test coverage without starting a full pipeline run.

Yes. Multibranch jobs read the Jenkinsfile from each branch, and the @Library annotation at the top of that file applies per branch. You can pin staging branches to a library feature branch while production branches stay on a stable semver tag. That gives you environment-specific library versions without maintaining separate Jenkins jobs. Combined with folder-scoped library configuration, you can run @dev library pins in a staging folder and v2.x tags in production folders on the same controller.

JCasC can register Global Pipeline Libraries in YAML so new controllers boot with the same library URLs and default versions. Shared Libraries hold step logic — the Groovy deploy blocks, Composer install wrappers, and credential bindings. JCasC holds controller configuration — library registration, agent labels, and credential store setup. Use both when rebuilding controllers or running enterprise environments that must reproduce cleanly. They complement each other: JCasC defines which libraries exist, Shared Libraries define what those libraries do at runtime.

They solve the same DRY problem on different platforms. Shared Libraries are the Jenkins-native answer with Groovy and deep Pipeline integration. GitLab uses include: templates. GitHub uses reusable workflows. Pick the mechanism your controller already runs — migrating purely for syntax rarely pays off. On real client projects I maintain with GitLab CI for Laravel, the same copy-paste drift problem appears everywhere. Shared Libraries shine when Jenkins is already your hub for legacy and multi-stack repos. If your organisation standardises on GitLab, compare approaches before forcing Jenkins everywhere.

Build a library after three similar pipelines exist. Fewer than that, keep code inline until deploy and lint patterns stabilize across your repos.

Keep the Deployer 7 recipe in the application repo where client-specific release paths and host config belong. The Shared Library should only wrap SSH connection, release path discovery, Composer install, artisan cache commands, and PHP-FPM reload. A practical global var for PHP 8.3 and Laravel 12 or 13 projects runs composer install --no-dev, artisan config:cache, route:cache, and view:cache, then calls dep deploy inside sshagent. Keep .env on the server, not in the library. Run migrations behind a manual approval step for production. That boundary stopped a bad library change from overwriting client-specific deploy.php files on projects I maintain.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: