Jenkins — Level by Level›05 · Shared libraries

Lesson 05 of 8 · Level 2 — Pipelines for teams

Shared libraries

Stop copying the same 200-line Jenkinsfile into every repository: package pipeline steps and whole standard pipelines in a versioned shared library, so each repository's Jenkinsfile shrinks to a few lines and fixes roll out everywhere through one release.

Practitioner → Advanced
Key wordsshared library@Libraryvars/src/resources/global variablecall()versioned libraryGlobal Pipeline LibrariesJenkinsPipelineUnitscript approval

The problem

Forty repositories, forty copies of nearly the same Jenkinsfile. A security fix to the build stage means forty pull requests, and some copies are always behind. A shared library is a Git repository of pipeline code that every Jenkinsfile can load.

Instead of every class writing its own fire-drill instructions, the school prints one official version. Each classroom just pins up "follow the school fire drill, version 3", and when the drill changes, the school reprints it once.

Library layout

platform-lib/
├── vars/
│   ├── buildImage.groovy            # step: buildImage(name: ...)
│   ├── standardServicePipeline.groovy
│   └── standardServicePipeline.txt  # help text shown in the Jenkins UI
├── src/com/acme/ci/Versioning.groovy
├── resources/templates/buildkit-pod.yaml
└── test/...

A step in vars/:

// vars/buildImage.groovy
def call(Map args) {
  String image = "registry.example.com/shop/${args.name}"
  String tag   = env.GIT_COMMIT.take(12)
  container('buildkit') {
    sh """
      buildctl-daemonless.sh build --frontend dockerfile.v0 \
        --local context=${args.context ?: '.'} --local dockerfile=${args.context ?: '.'} \
        --output type=image,name=${image}:${tag},push=true
    """
  }
  return "${image}:${tag}"
}

A whole standard pipeline:

// vars/standardServicePipeline.groovy
def call(Map cfg) {
  pipeline {
    agent { kubernetes { yaml libraryResource('templates/buildkit-pod.yaml') } }
    options { timeout(time: 30, unit: 'MINUTES'); buildDiscarder(logRotator(numToKeepStr: '30')) }
    stages {
      stage('Test')    { steps { sh cfg.testCommand ?: 'make test' } }
      stage('Image')   { when { branch 'main' } steps { script { env.IMAGE = buildImage(name: cfg.app) } } }
      stage('Promote') { when { branch 'main' } steps { promoteToDev(app: cfg.app, image: env.IMAGE) } }
    }
    post { always { junit allowEmptyResults: true, testResults: 'reports/**/*.xml' } }
  }
}

Each service's Jenkinsfile is now:

@Library('platform-lib@v2.3.0') _
standardServicePipeline(app: 'orders-api', testCommand: 'pytest --junitxml=reports/unit.xml')

Registering and versioning the library

Register it once under Manage Jenkins → System → Global Pipeline Libraries (or in JCasC): name, default version, and the Git source. Then:

  • Tag releases (v2.3.0) and have Jenkinsfiles reference a tag, not main.
  • Keep a changelog; announce breaking changes; support the previous major version for a while.
  • Roll out new versions to a few pilot repositories first.
  • Global libraries run with elevated trust (no Groovy sandbox), so restrict who can change them, exactly like production code.

Testing a library

Pipeline code is code. Unit test it with JenkinsPipelineUnit (mocks Jenkins steps in plain JUnit), and run an integration test pipeline against a sample repository for every library pull request.

Try it: shrink a Jenkinsfile to two lines

  1. Create a platform-lib repository with vars/standardServicePipeline.groovy (use echo steps if you don't have a registry).
  2. Register it as a Global Pipeline Library with default version main, then tag v1.0.0.
  3. Change a service repository's Jenkinsfile to the two-line version pinned to v1.0.0; run it.
  4. Change the library, tag v1.1.0, and upgrade only one service to it; the other keeps working on v1.0.0.

Recap

  • A shared library (vars/, src/, resources/) removes copy-pasted pipelines.
  • Services call standard steps or whole pipelines in a few lines.
  • Version the library with tags, test it, roll out gradually, and restrict who can change it.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.