Reusing GitLab CI Templates Across Multiple Magento Projects
AI generated
CI/CD
.yml
GitLab · CI Templates · Multi-Project · Magento
Reusing GitLab CI Templates Across
Multiple Magento Projects

Running ten Magento shops with ten identical pipeline files is technical debt in its purest form. Every change to the deploy process has to be made ten times. GitLab templates with include and extends solve this problem: one central build standard, individually extensible per project.

13 min read include · extends · YAML anchors · template repository · overrides GitLab · Magento 2.4 · multi-project

1. The problem with copied pipeline files

Agencies and teams running multiple Magento projects know this problem well. The first .gitlab-ci.yml is carefully built, tested and confirmed to work. For the second project it gets copied and slightly adjusted. For the third project, the copy of the second project's file gets copied again. After a year, ten pipeline files exist, each differing in details because improvements only made it into some of the projects. A security hole in the deploy script now has to be fixed in ten repositories individually.

This is not an academic problem, it is an everyday operational reality in agencies with multiple client projects. The solution is the same one software developers have used for decades to fight code duplication: abstraction and reuse. GitLab offers the include keyword for pulling external YAML files into the current pipeline, and extends for deriving and extending job definitions from included templates.

The goal is a system in which the shared build, test and deploy standard is defined in exactly one place, the template repository, and every project only includes it and configures what is actually project-specific. An improvement to the deploy standard is made once and instantly becomes available to every project that references the template at a compatible version.

2. GitLab include: basics and variants

The include keyword in GitLab CI/CD lets you pull external YAML files into the current pipeline configuration. There are four variants: local (a file in the same repository), project (a file in another GitLab project), remote (a URL pointing to an external YAML file) and template (predefined GitLab templates). For reuse across multiple Magento projects, the project variant is the most suitable, because it references a central template repository on the same GitLab instance and benefits from GitLab's access control.

An important detail of include: project is the ref option, which determines which branch, tag or commit SHA from the template repository is used. When ref points to a fixed tag, the template version is frozen and changes to the template repository do not immediately affect every project. When ref points to main, every project always uses the newest template version, convenient for small teams, but risky because breaking changes hit all projects immediately.

# .gitlab-ci.yml in a Magento project
# Includes templates from a central template repository

include:
  # Pin to a specific version tag for stability
  - project: "mironsoft/gitlab-ci-templates"
    ref: "v2.1.0"
    file:
      - "/magento/build.yml"
      - "/magento/test.yml"
      - "/magento/deploy.yml"

# Project-specific variables override template defaults
variables:
  MAGENTO_VERSION: "2.4.8"
  PHP_VERSION: "8.4"
  DEPLOY_HOST: "shop1.example.com"
  DEPLOY_PATH: "/var/www/shop1"
  THEME_PATH: "app/design/frontend/Mironsoft/default"

# Project-specific stage additions (template stages remain)
stages:
  - build
  - test
  - package
  - deploy
  - verify
  - rollback

3. extends: deriving and overriding jobs from templates

The extends keyword lets you derive a job from a template and override or extend individual properties. GitLab performs a deep merge under the hood: dictionaries (such as variables and cache) are merged, scalars and lists are overwritten. This enables fine-grained overrides without having to copy the entire job definition.

A typical pattern is to define a template job describing the standard build process for Magento, and in each project override only the project-specific deviations. The template defines the PHP image, the composer options, the cache and the artifact configuration. The project overrides only the specific theme directory or adds project-specific build steps.

# templates/magento/build.yml (in template repository)
# Base build job for Magento projects, extended in each project

.build:magento:base:
  stage: build
  image: "php:${PHP_VERSION:-8.4}-cli"
  variables:
    COMPOSER_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/composer"
    COMPOSER_HOME: "${CI_PROJECT_DIR}/.cache/composer"
    THEME_PATH: "app/design/frontend/Mironsoft/default"  # Default theme
  cache:
    key:
      files:
        - composer.lock
    paths:
      - .cache/composer/
    policy: pull-push
  script:
    - composer install --no-dev --prefer-dist --optimize-autoloader
    - npm ci --prefix "${THEME_PATH}/web/tailwind"
    - npm run build --prefix "${THEME_PATH}/web/tailwind"
    - bin/magento setup:di:compile
  artifacts:
    paths:
      - vendor/
      - generated/
      - "${THEME_PATH}/web/tailwind/css/"
    expire_in: 3 hours

---
# .gitlab-ci.yml in a specific project, extends the template

build:magento:
  extends: .build:magento:base
  variables:
    THEME_PATH: "app/design/frontend/Acme/custom"  # Project-specific override
  script:
    - !reference [.build:magento:base, script]  # Include base script
    - echo "Project-specific post-build step"

4. YAML anchors as an alternative within a single file

YAML anchors are a YAML-native solution for reuse within a single file. With &anchor-name a block is defined as an anchor, with *anchor-name it is referenced, and with <<: *anchor-name its values are merged into another block. GitLab CI/CD fully supports YAML anchors, which makes them useful for reuse within a project's local .gitlab-ci.yml.

However, YAML anchors are not a solution for cross-project reuse, because they cannot be imported from external files. They are well suited to avoiding duplication within a single file, not as a substitute for the include system. In practice, YAML anchors are often used for common variable blocks and SSH setup steps that are needed across multiple jobs in the same pipeline.

5. A central template repository for Magento projects

The central template repository is the heart of template reuse. It holds the shared building blocks for every Magento project: build jobs, test jobs, deploy jobs, verify jobs and rollback jobs. The structure should be organized by functional area so that projects only include the parts they actually need. A project that does not need a rollback job simply does not include rollback.yml.

The template repository needs its own pipeline that validates the templates. GitLab provides the ci-lint API endpoint for this, which checks a pipeline configuration syntactically and semantically. For the template repository this means a test job in the template pipeline runs a lint check across all template files and makes sure no syntactically invalid configuration is ever published.

# Structure of the central template repository
# mironsoft/gitlab-ci-templates/

# magento/build.yml      : composer, npm, di:compile, static content
# magento/test.yml       : PHPStan, PHPCS, PHPUnit
# magento/deploy.yml     : SSH, rsync, symlink switch, shared files
# magento/verify.yml     : health check, smoke tests, cache status
# magento/rollback.yml   : symlink switch to previous release
# magento/cleanup.yml    : remove old releases, clean old artifacts

# Template repository pipeline (validates all templates)
# .gitlab-ci.yml (in the template repo itself)
validate:templates:
  stage: test
  image: alpine:latest
  script:
    - apk add --no-cache curl
    # Validate each template file against the GitLab CI lint API
    - |
      for file in magento/*.yml; do
        echo "Validating: $file"
        curl -sf \
          --header "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}" \
          --header "Content-Type: application/json" \
          --data "{\"content\": \"$(cat ${file} | jq -Rs .)\"}" \
          "${CI_API_V4_URL}/ci/lint" | jq -e '.valid == true'
      done

6. Project-specific overrides and extensions

Not every Magento project is identical. Some projects have additional stores with different themes, others deploy to multiple server environments, and still others have specific testing requirements. The template system must allow overrides and extensions without compromising the reusability of the base templates.

The recommended strategy is to pass project-specific configuration through variables that are defined as defaults in the template. If the template sets MAGENTO_LOCALE: "de_DE" as a default and a project is multilingual, the project overrides the variable with MAGENTO_LOCALE: "de_DE en_US fr_FR". The template job consumes the variable without the job code ever changing. Only when a project's requirement fundamentally goes beyond what the template offers is the job derived with extends and the differing steps overridden.

# Project-specific .gitlab-ci.yml, minimal override pattern

include:
  - project: "mironsoft/gitlab-ci-templates"
    ref: "v2.1.0"
    file: ["/magento/build.yml", "/magento/deploy.yml", "/magento/verify.yml"]

# Override only what differs from the template defaults
variables:
  DEPLOY_HOST: "${PROD_DEPLOY_HOST}"  # From GitLab CI/CD variables
  DEPLOY_PATH: "/var/www/shop"
  MAGENTO_LOCALE: "de_DE en_US"       # Multi-locale override
  THEME_PATH: "app/design/frontend/Mironsoft/default"

# Extend the template deploy job with a project-specific step
deploy:production:
  extends: .deploy:production:base  # From included template
  after_script:
    # Notify project-specific Slack channel after deployment
    - |
      curl -X POST "${SLACK_WEBHOOK_URL}" \
        -d "{\"text\": \"Deployed ${CI_COMMIT_TAG} to production\"}"

7. Template versioning and breaking changes

Templates referenced by multiple projects must be versioned. Without versioning, every change to a template immediately affects every project that includes it. That is unacceptable for breaking changes, for example renaming a hidden job or adding a required variable, because projects could break without any warning.

Semantic versioning (SemVer) works well for CI templates too: patch versions for bug fixes, minor versions for new optional features, major versions for breaking changes. Projects pointing at a major-version tag automatically receive all compatible improvements, but have to migrate deliberately at the next major release. The template repository pipeline automatically creates tags on merges to main to simplify the versioning process.

8. Comparison: copied pipelines vs. template-based pipelines

The difference between copied and template-based pipelines shows up most clearly when a security issue or a best-practice update needs to be rolled out across every project. With copied pipelines, that is a manual process across every repository. With template-based pipelines, it is a single change in the template repository.

Aspect Copied pipelines Template-based pipelines Benefit
Rolling out a security fix Edit N repositories 1 template update Drastically less effort
Consistency across projects Diverges over time Centrally enforced Same standard everywhere
Setting up new projects Copy, adjust, test include plus setting variables Minutes instead of hours
Traceability Each repo on its own Central changelog Auditable and traceable
Project-specific adjustments Directly possible Via extends and variables Flexible, but controlled

9. Pitfalls of template reuse

A common pitfall is unintentionally overriding template variables with project variables. If a template defines a variable as required and the project sets a variable with the same name but a different value, the project value silently wins, with no error message. This can lead to silent malfunctions that are hard to debug. A naming convention for template-internal variables (for example, a _TMPL_ prefix) reduces the risk of collisions.

A second pitfall involves referencing hidden jobs from templates. Hidden jobs start with a dot (for example .build:magento:base) and are never run directly. If a project extends a job with extends: .build:magento:base and the template job gets renamed, the project's pipeline fails with an unhelpful error message. Breaking changes in templates must therefore always be communicated with a major version bump and a clear changelog.

A third pitfall is GitLab's caching of template files. With the include: remote variant, GitLab caches the external URL. With include: project using a branch ref (instead of a tag), the template file is read from the repository at the moment the pipeline runs, which gives you the expected freshness but also means an invalid commit in the template repository immediately blocks every dependent project's pipeline.

10. Summary

Reusing GitLab CI templates across multiple Magento projects is an investment that pays off starting with the second project. The include: project system lets you define a shared build, test and deploy standard centrally and deliver it to every project in a controlled way through version tags. extends allows project-specific overrides without full job copies. Variable defaults in the template and overrides in the project repository keep the shared standard cleanly separated from project-specific details.

The template repository itself needs its own pipeline with lint checks and semantic versioning. Breaking changes must be communicated, and projects migrate to new major versions deliberately. With this system, a security fix in the deploy script becomes a single commit in the template repository instead of ten commits across ten repositories.

GitLab CI Templates: The Essentials at a Glance

include: project

Pulls templates from a central repository. Set ref to a tag for stability control. Access governed by GitLab permissions.

extends

Derives jobs from template definitions. Deep merge for dictionaries. Only differing properties need to be overridden.

Versioning

SemVer for template tags. Patch for bug fixes, minor for new features, major for breaking changes with an explicit changelog.

Project overrides

Variable overrides for configurable differences. extends for structural deviations. No direct editing of template files.

11. FAQ: GitLab CI Templates for Multiple Magento Projects

1include:project vs. include:remote?
include:project uses GitLab's access control and reads from an internal repo. include:remote reads from an external URL without authentication.
2Include multiple template files?
Yes, include:file accepts a list. Keep build, test and deploy templates separate and include them as needed.
3Same job name in template and project?
The project fully overrides the template job. For partial overrides, use extends instead of the same job name.
4Roll out template updates to every project?
Branch ref: immediately on the next pipeline run. Tag ref: manual update of the tag in the project. Tags are more controllable.
5Can templates include other templates?
Yes, but with caution. Nested includes increase complexity. For most cases a single template layer is enough.
6Defining required variables in templates?
No native GitLab feature. Use a validation job at the start of the pipeline that checks variables and fails with a clear error.
7extends vs. YAML anchors?
extends: works across file boundaries, GitLab native. YAML anchors: only within a single file. Always use extends for templates.
8How many projects can be managed?
No technical upper limit. Once you have many projects, the template repository itself becomes critical and needs its own tests and reviews.
9Multiple Magento versions in one template?
Via the MAGENTO_VERSION variable. For very different versions, separate template files per version make more sense.
10Does the template repository need to be public?
No, private is fine. The runner token or CI job token needs read access. Set up group access rules correctly.