Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

CI/CD Pipelines

Pipeline as Code: Shared Reusable Workflows and Templates Across Repositories

Pipeline as code stores pipeline definitions in version control, and shared templates let many repositories reuse one tested definition. In GitHub Actions these are reusable workflows and composite actions, in GitLab CI they are includes and components, and in Jenkins they are shared libraries. Versioned templates let a platform team improve every pipeline at once.

Key takeaways

  • Copy-pasted pipelines drift. A shared, versioned template turns a fix into one change, not fifty.
  • Template versioning is the safety mechanism: consumers pin a version and upgrade on purpose.
  • Good templates are opinionated with a few inputs; bad ones expose every option and become a second language.
  • Reusable workflows and composite actions solve different problems and are often used together.
  • Templates need owners, tests and a change process, or they become the fragile centre of everything.

The cost of fifty copies of the same pipeline

A company with thirty repositories usually has thirty slightly different build pipelines. One uses an old Node version, another forgot the security scan, a third deploys with a script that no longer matches the others. When a vulnerability in a build step is announced or a registry changes, someone has to open thirty pull requests and nobody is sure they found them all.

Shared pipeline definitions solve this. Treating pipelines as versioned code, and reusing them through templates, is a central part of our CI/CD pipeline service. This page explains the mechanisms in each major tool, and how to introduce them without creating a new single point of failure.

What good looks like

  • Short consumer pipelines. A service repository declares what it is (language, image name, deploy target) and calls the shared definition.
  • Central improvements. Adding a scan or fixing a cache happens once and reaches every repository on upgrade.
  • Versioned and pinned. Consumers reference a released version, so a template change never surprises them mid-sprint.
  • Tested templates. The shared pipeline has its own test suite and sample projects.
  • A small contract. A few inputs with defaults, not fifty switches.
  • Visible ownership. Someone is responsible for the template, and consumers know how to ask for changes.

How it works in each tool

GitHub Actions: reusable workflows and composite actions

A reusable workflow is triggered by workflow_call and invoked by other workflows. The shared definition lives in a central repository:

# your-org/platform-workflows/.github/workflows/service-ci.yml
name: service-ci
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        default: "20"
      image-name:
        type: string
        required: true
    secrets:
      registry-role:
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
          cache: npm
      - run: npm ci
      - run: npm test

A service then calls it with a few lines:

# service repository
name: ci
on: [pull_request, push]
jobs:
  ci:
    uses: your-org/platform-workflows/.github/workflows/service-ci.yml@v3
    with:
      image-name: checkout

A composite action packages a sequence of steps into a single action, which runs inside the caller’s job. We use composite actions for small, repeated sequences such as “set up our toolchain and log in to the registry”, and reusable workflows for whole pipelines. Reusable workflows have limits worth knowing: nesting depth is capped, and secrets and permissions must be passed deliberately. We check current limits against GitHub’s documentation when designing, since they change over time. Our GitHub Actions glossary entry covers the basics for teams new to it.

GitLab CI: include and components

GitLab uses include: to pull in YAML from another project, and newer CI/CD components for versioned, parameterised building blocks. Jobs can be extended with extends: and hidden .job templates. Pinning the include to a tag or ref is essential:

include:
  - project: "platform/ci-templates"
    ref: v3.2.0
    file: "/templates/node-service.yml"

See the GitLab CI glossary entry for background.

Jenkins: shared libraries

Jenkins shared libraries are Groovy code stored in a repository and loaded with @Library('platform@v3'). They can define custom steps such as standardBuild(language: 'node'). They are powerful, and with that comes risk: Groovy logic can become complex and hard to test. Keep libraries thin, test them with the Jenkins Pipeline Unit framework or similar, and treat them as a product. If you are weighing a move away from Jenkins, the shared library is often the most valuable thing to port, as described in our migration path.

Versioning is the safety mechanism

The main fear with shared pipelines is that one bad change breaks every repository at once. Versioning removes it.

  1. Consumers reference a major version tag (@v3), or a full version for the strictest teams.
  2. Non-breaking improvements are released under the same major tag and reach consumers automatically.
  3. Breaking changes ship as a new major version. Repositories upgrade when ready, usually through an automated pull request from Renovate or Dependabot.
  4. A canary set of internal repositories pins to the development branch and exercises changes before release.
  5. A changelog and deprecation policy tell consumers what is changing and by when.

For highest assurance, pin by commit SHA rather than a movable tag, and have the update bot bump it. This guards against a compromised or mistakenly moved tag.

Designing a good template

Be opinionated. The point of a template is to encode decisions: caching, scanning, tagging, permissions. If every behaviour is an input, you have written a worse version of the underlying tool.

Provide escape hatches carefully. Teams will have legitimate special cases. Offer a limited, documented way to add a custom step or run a pre-build script, rather than forcing a fork of the template.

Least privilege by default. Reusable workflows should declare minimal permissions, and callers should grant only what is needed. A shared pipeline is a good place to enforce this consistently, as part of secrets and credential hygiene.

Lint and test. Run actionlint on workflow files, and keep sample repositories that call the template with different inputs. Tools such as act can run workflows locally, though behaviour differs from hosted runners, so they supplement rather than replace real runs.

Document by example. A README with a copy-paste snippet for each language is worth more than a long reference.

Decisions and trade-offs

Central template versus per-repo flexibility. Standardisation reduces maintenance and risk, and reduces autonomy. We aim for a paved road: the default is easy and sound, and departing from it is possible but visible.

One big template or several. A single “do everything” template grows branches for every case. Several focused ones (build, deploy, release) compose better.

Platform team bottleneck. If every change goes through one team, templates stall. Open contribution with required review from owners keeps things moving.

When not to do this. With three repositories and one maintainer, a shared template repository can be more overhead than copying a file. It pays off from roughly the point where changes need to be repeated and forgotten.

Common failure modes

  • Unpinned references (@main), so a template change breaks all consumers instantly.
  • Template sprawl with dozens of inputs.
  • No tests, so the first user of a change finds the bug.
  • Secrets inherited wholesale (secrets: inherit) giving callees more than they need.
  • Orphaned templates with no owner, frozen on outdated tool versions.
  • Forks of the template inside each repository, recreating the original problem.

Delivery and handover

We compare your existing pipelines, extract the common core into a template repository with tests and documentation, release a first version, and migrate services in order of value, often with automated pull requests. As part of design work, this pairs with pipeline design and implementation. You keep the template repository, release process and contribution guide, and your team can extend it without us.

How to tell it is working

Watch the number of distinct pipeline definitions across repositories (it should fall), the share of repositories on the latest template major version, the time to roll out a pipeline-wide change, and the lines of CI configuration per service. A healthy result is that a security fix to the build reaches every service within days rather than months. If you would like a view on your current setup, book a consultation.

References

How we deliver it

  1. 01

    Survey existing pipelines

    We compare pipelines across repositories to find the shared 80 percent and the legitimate differences.

  2. 02

    Design the template contract

    Inputs, outputs, secrets and defaults are defined, keeping the interface small and documented.

  3. 03

    Build and test the templates

    Templates are built in a dedicated repository with their own tests, run against sample projects before release.

  4. 04

    Version and release

    Templates are tagged with semantic versions, with major version tags for consumers and a changelog.

  5. 05

    Migrate repositories gradually

    Services adopt templates one by one, often with automated pull requests, replacing copied YAML with a short call.

  6. 06

    Govern and evolve

    We set ownership, review rules and a deprecation policy, and keep templates current as tools change.

Tools and technologies

GitHub Actions reusable workflowsGitHub composite actionsGitLab CI include and componentsJenkins shared librariesRenovateDependabotactionlintactTerraformYAML anchors

Frequently asked questions

What is a reusable workflow in GitHub Actions?
A reusable workflow is a complete workflow file that other workflows call using the uses keyword, with inputs and secrets passed in. It runs as its own set of jobs. It is suited to standardising whole pipelines such as build, test and deploy across many repositories from one central definition.
Reusable workflow or composite action?
A reusable workflow packages entire jobs, can use multiple jobs and runners, and is called at job level. A composite action packages a sequence of steps, runs inside the caller's job and shares its runner and workspace. Use composite actions for step-level reuse and reusable workflows for whole pipelines.
How do you change a shared pipeline without breaking everyone?
Version it. Consumers reference a tag, such as v3, and a breaking change ships as v4. Non-breaking improvements are released under the existing major tag. Automated update pull requests then move repositories forward deliberately, and a canary set of internal repositories tests changes first.
Does pipeline as code mean everything is in one file?
No. It means pipeline definitions are stored as text in version control, reviewed and tested like application code. They can span many files, shared templates and scripts. The benefit is history, review, rollback and reproducibility instead of configuration that lives in a web interface.
Who should own the shared templates?
A named team, usually platform or DevOps, with a defined review and release process. Without an owner, templates collect one-off options and break consumers. Application teams should be able to propose changes through pull requests while the owners protect the contract.

Talk to an engineer about reusable workflows pipeline as code

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.