
September 09, 2026
12 min read
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.
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. FiledeployLaravel.groovybecomesdeployLaravel().src/— Namespaced Groovy classes for complex logic, unit tests, and typed helpers.resources/— Templates, JSON configs, or shell scripts you read withlibraryResource.
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.
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.
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.
| Criteria | Inline Jenkinsfile | Shared Library |
|---|---|---|
| Duplication across repos | High — copy-paste drift | Low — single source of truth |
| Upgrade effort | Edit every Jenkinsfile | Tag a release, bump pin per repo |
| Testability | Hard without full pipeline runs | Unit-test Groovy classes in src/ |
| Onboarding | Read one long file per repo | Read library docs + thin Jenkinsfile |
| Blast radius of a bug | One repo | All consumers on that version pin |
| Best fit | Prototypes, single-app teams | Multi-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.
- Pin library versions per environment —
@devfor staging folders, semver tags for production. - Keep Jenkinsfiles under 80 lines; if longer, move logic to the library.
- Log library version at pipeline start so build logs show which code ran.
- Document breaking changes in the library README with migration notes.
- 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.
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/, andresources/— not scattered across Jenkinsfiles. - Pin library versions to semver tags in production; never float on
mainfor 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
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.

