GitLab Shared Libraries let you stop copying and pasting your CI/CD logic across dozens of repositories.

Imagine you have a fleet of microservices, each with its own .gitlab-ci.yml. You want to enforce a consistent build, test, and deploy process across all of them. Instead of manually updating each gitlab-ci.yml whenever you need to tweak a build step, you can define that logic once in a Shared Library and include it everywhere.

Here’s a typical scenario: a common build_and_test job.

Let’s say you have a Python project. Your base .gitlab-ci.yml might look like this:

include:
  - project: 'my-group/ci-templates'
    ref: 'main'
    file: '/python/build_and_test.yml'

stages:
  - build
  - test
  - deploy

variables:
  PYTHON_VERSION: "3.11"

build_job:
  stage: build
  script:
    - echo "Building Python project..."
    - pip install --upgrade pip
    - pip install -r requirements.txt
    - python setup.py build

test_job:
  stage: test
  script:
    - echo "Running tests..."
    - pip install pytest
    - pytest

Now, let’s look at what my-group/ci-templates/python/build_and_test.yml might contain. This is the Shared Library.

# .gitlab-ci.yml for the Shared Library project
# Path: python/build_and_test.yml

# Define base stages and common variables
stages:
  - build
  - test

variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

# Default build job template
.build_template:
  stage: build
  image: python:$PYTHON_VERSION
  cache:
    key: "$CI_COMMIT_REF_SLUG-build"
    paths:
      - "$PIP_CACHE_DIR"
  script:
    - echo "Setting up Python environment..."
    - python -m venv venv
    - source venv/bin/activate
    - pip install --upgrade pip
    - pip install -r requirements.txt
    - echo "Compiling project..."
    # This script block is intentionally left open for project-specific build steps
    # Project-specific build commands will be appended here.

# Default test job template
.test_template:
  stage: test
  image: python:$PYTHON_VERSION
  cache:
    key: "$CI_COMMIT_REF_SLUG-build" # Reuse build cache for tests
    paths:
      - "$PIP_CACHE_DIR"
  script:
    - echo "Setting up Python environment for testing..."
    - python -m venv venv
    - source venv/bin/activate
    - pip install -r requirements.txt
    - pip install pytest coverage
    - echo "Running tests with coverage..."
    # Project-specific test commands will be appended here.

# Job definitions that actually use the templates
build_job:
  extends: .build_template
  variables:
    # Project-specific overrides or additions can go here
    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" # Explicitly setting cache dir within the job
  script:
    - !reference [.build_template, script] # Include the base script
    - echo "Running project-specific build steps..."
    - python setup.py build # Example project-specific step

test_job:
  extends: .test_template
  variables:
    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
  script:
    - !reference [.test_template, script]
    - echo "Running project-specific tests..."
    - pytest --cov=.

In the example above:

  • my-group/ci-templates: This is a separate GitLab project that acts as your central repository for CI/CD templates.
  • python/build_and_test.yml: This is the actual file within the Shared Library project containing the reusable CI logic.
  • .build_template and .test_template: These are hidden jobs (prefixed with .) that define the common logic. They use extends to allow other jobs to inherit their settings.
  • !reference: This GitLab CI keyword is crucial. It allows you to include specific parts of a parent job’s configuration, like the script section, and then append or prepend your own commands. This is how you customize the template for each project.
  • include directive: In each microservice’s .gitlab-ci.yml, the include directive pulls in the Shared Library. project, ref (branch or tag), and file specify exactly which template to load.

When a pipeline runs for a microservice that includes this Shared Library:

  1. GitLab fetches the specified file from the project and ref.
  2. It merges the included template’s jobs and rules with the project’s own .gitlab-ci.yml.
  3. Jobs like build_job and test_job in the microservice’s .gitlab-ci.yml will extend the templates, inheriting their image, stage, cache, and script configurations.
  4. The !reference directive ensures that the base script from the template is executed, followed by any project-specific commands defined in the microservice’s .gitlab-ci.yml.

This pattern allows you to maintain a single source of truth for your CI/CD pipelines, dramatically reducing duplication and making it easy to enforce standards and roll out updates across your entire organization.

The most surprising aspect of Shared Libraries is how seamlessly they integrate with standard GitLab CI features like extends and rules, allowing for deep customization without sacrificing reusability. You aren’t just including a static file; you’re inheriting and modifying dynamic job configurations.

Consider how you might want to pass project-specific configurations into a template. You can achieve this by defining variables in the top-level .gitlab-ci.yml of the including project, which are then picked up by the included template jobs.

The next logical step is to explore how to manage different versions of your Shared Libraries using Git tags and how to implement complex conditional logic using rules within your templates.

Want structured learning?

Take the full Gitlab course →