GitLab CI/CD: Optimize Pipelines Using extends for Common Job Definitions
This chapter explores how to optimize GitLab CI/CD pipelines by utilizing the extends keyword for reusing common job templates. Building upon previous lessons involving the reference keyword, this guide contrasts the two mechanisms, demonstrates how extends merges entire configuration blocks, and explains how local job definitions override template defaults.
1. Why Do We Need Job Template Reuse?
In complex CI/CD pipelines, multiple jobs often share identical configurations, such as specific deployment steps, default environment variables, execution stages, and execution rules.
- The Status Quo Problem: Duplicating identical configuration blocks across multiple jobs leads to bloated
.gitlab-ci.ymlfiles, making maintenance difficult and updates error-prone. - The Solution: Define a common job template once and reuse its components across multiple jobs. While the
referencekeyword allows pulling specific individual components, theextendskeyword offers a cleaner, higher-level mechanism to inherit entire job structures.
2. Core Mental Model: The Blueprint and Construction Analogy
Real-Life Analogy: Think of an
extendstemplate like an architectural blueprint for a standard house model. Instead of redrawing the foundation, plumbing, and electrical layouts for every single plot of land, you simply state, "Build House B extending Blueprint A."If Blueprint A specifies standard grey walls, but for House B you explicitly request blue paint, the builder uses your custom blue paint for the walls while keeping the rest of the standard blueprint intact.
3. Reference Keyword vs. Extends Keyword: The Evolution
Previously, we used the reference keyword to pull specific snippets from a template job into another job.
# Conceptual comparison showing the architectural shift
Reference Approach:
Job B -> Explicitly points to specific keys in Job A (e.g., script only).
Extends Approach:
Job B -> Inherits the entire blueprint of Job A automatically, merging all sections.
Comparison Table
4. Step-by-Step Implementation of extends
To see how extends functions in practice, we set up a dedicated Git branch (demo-extends) to compare it directly with our previous reference-based pipeline.
Step 1: Defining the Base Job Template
We create a hidden job (indicated by the leading dot . in .default_job) which acts as our reusable template containing stages, variables, scripts, and rules.
# Base template job (hidden from direct execution)
.default_job:
stage: test
variables:
ENVIRONMENT: "dev"
TIMEOUT: "100"
script:
- echo "Running default script - Installing dependencies..."
- echo "Executing shared test suite..."
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
Step 2: Creating a Job that Extends the Template
Next, we create a new job named test2 and use the extends keyword to inherit everything from .default_job.
test2:
extends: .default_job
What Happens Behind the Scenes?
When GitLab parses this configuration, it merges all components of .default_job into test2:
stagebecomestestvariablesinheritsENVIRONMENT: "dev"andTIMEOUT: "100"scriptinherits the default echo commandsrulesinherits the web-trigger condition
5. Pipeline Execution and Manual Triggering
After committing our changes to GitLab, we notice that pipelines do not trigger automatically on git push.
# Rule enforcing manual web triggers
rules:
- if: '$CI_PIPELINE_SOURCE == "web"'
Manual Execution Workflow
- Navigate to the GitLab project web interface.
- Go to Build -> Pipelines -> Run pipeline.
- Select the branch containing the
extendsimplementation (demo-extends). - Click Run pipeline.
6. Handling Conflicts: Local Overrides vs. Template Defaults
A critical aspect of extends is how GitLab handles conflicts when a property is defined both in the template and inside the extending job.
Experimenting with Script Overrides
What happens if we define a local script inside test2 while also inheriting the template's script?
.default_job:
stage: test
script:
- echo "Running default script from template"
test2:
extends: .default_job
script:
- echo "Running script directly inside test2 job"
Merger and Precedence Rules
- Merging: When
extendsruns, GitLab merges the configuration keys. - Precedence: If a scalar property (like
scriptor a single variable) is defined in both the template and the child job, the child job's definition takes highest priority and completely overwrites the template's value for that key.
In our pipeline run, the logs successfully outputted the custom script from test2, proving that local overrides supersede inherited template values while non-conflicting keys (like un-overridden variables) remain intact from the template.
7. Summary and Key Takeaways
- Template Reusability: The
extendskeyword allows clean, declarative reuse of entire job definitions without duplicating YAML code across pipelines. - Hidden Job Templates: Base templates are typically prefixed with a dot (e.g.,
.default_job) so GitLab knows they are templates and not standalone executable jobs. - Automatic Merging: All components—stages, variables, scripts, and rules—are automatically merged into the extending job.
- Precedence Rules: If a key (such as
scriptorvariables) is defined in both the template and the child job, the child job's definition takes absolute precedence, overriding the inherited value. - Flexible Customization: This behavior allows developers to establish a standardized enterprise CI/CD baseline while giving individual microservices or jobs the flexibility to override specific parameters.