Prev Next

Maven / GitLab CI Basics Interview Questions

1. What is GitLab CI/CD and what problem does it solve? 2. What is the.gitlab-ci.yml file and where must it be placed? 3. What is a GitLab CI/CD pipeline? 4. What are stages in GitLab CI/CD and how do you define them? 5. What is a CI/CD job in GitLab and what are the required keywords? 6. What is a GitLab Runner and what types are available? 7. What is the 'script', 'before_script', and 'after_script' keywords and how do they differ? 8. How do you use the 'image' keyword to specify a Docker environment for a job? 9. What are GitLab CI/CD artifacts and how do you use them? 10. What is the difference between cache and artifacts in GitLab CI? 11. What are GitLab CI/CD variables and what types are available? 12. What are the most important predefined CI/CD variables in GitLab? 13. What are 'rules' in GitLab CI/CD and how do they control when jobs run? 14. What is the difference between 'only/except' and 'rules' in GitLab CI? 15. What is the 'needs' keyword in GitLab CI/CD and what problem does it solve? 16. What is the 'workflow' keyword in GitLab CI and how does it control pipeline creation? 17. What is the 'extends' keyword and how does it enable job templates? 18. What is the 'include' keyword and how do you share CI configuration across projects? 19. What are GitLab environments and how do you define them in CI/CD? 20. What are manual jobs in GitLab CI/CD and how do you configure them? 21. What is the 'allow_failure' keyword and how does it affect pipeline status? 22. How does parallel job execution work in GitLab CI/CD? 23. What are GitLab CI/CD scheduled pipelines and how do you set them up? 24. What are pipeline triggers and how can a pipeline be started without a git push? 25. What is the 'retry' keyword and how do you handle flaky tests in GitLab CI? 26. What is the 'timeout' keyword in GitLab CI and what are the defaults? 27. What is the GitLab Container Registry and how do you use it in CI/CD pipelines? 28. What are 'services' in GitLab CI/CD and how do you use them? 29. What is the 'default' keyword in GitLab CI and how does it reduce duplication? 30. What are YAML anchors and aliases in GitLab CI and how do they reduce duplication? 31. What is a merge request pipeline in GitLab CI and how does it differ from a branch pipeline? 32. What is pipeline caching and how do you configure cache keys? 33. What is the GitLab CI Lint tool and how do you validate a.gitlab-ci.yml file? 34. What are GitLab CI/CD components and how do they differ from includes? 35. What are parent-child pipelines in GitLab CI/CD? 36. What is the 'resource_group' keyword and how does it prevent concurrent deployments? 37. What are GitLab's built-in security scanning templates and how do you enable them? 38. What is the 'GIT_STRATEGY' variable in GitLab CI and what are the options? 39. What are protected branches and protected variables in GitLab CI? 40. What are common reasons a GitLab CI pipeline fails and how do you troubleshoot them?
Could not find what you were looking for? send us the question and we would be happy to answer your question.

1. What is GitLab CI/CD and what problem does it solve?

GitLab CI/CD is a built-in automation system within GitLab that automatically builds, tests, and deploys code every time a developer pushes changes to a repository. It eliminates the need for separate CI tools like Jenkins by providing pipelines as a native GitLab feature.

The problems it solves:

  • Manual errors - human steps in build/deploy processes are replaced with scripted automation
  • Slow feedback - developers learn about broken code minutes after pushing, not hours later
  • Integration debt - frequent small merges catch conflicts early instead of painful big-bang integrations
  • Inconsistency - every pipeline run uses the same defined environment and steps
CI vs CD
TermFull nameWhat it does
CIContinuous IntegrationAuto-build and test every code change
CDContinuous DeliveryAuto-prepare release-ready artifacts after CI passes
CDContinuous DeploymentAuto-deploy all the way to production after CI passes

Everything is configured in a single YAML file (.gitlab-ci.yml) committed alongside the code, making pipeline configuration version-controlled and auditable.

What file do you create to define a GitLab CI/CD pipeline?
What does Continuous Integration (CI) automate?

2. What is the.gitlab-ci.yml file and where must it be placed?

The .gitlab-ci.yml file is the pipeline configuration file for GitLab CI/CD. It uses YAML syntax to define stages, jobs, scripts, rules, and all other pipeline behaviour. GitLab automatically detects and runs the pipeline whenever this file is present and a triggering event occurs.

Key facts:

  • Must be placed at the root of the repository by default
  • The filename is case-sensitive - it must be exactly .gitlab-ci.yml
  • You can configure a different path/filename in Settings > CI/CD > General pipelines > CI/CD configuration file
  • It is a regular file tracked by Git, so all changes are version-controlled and can be reviewed in merge requests
# Minimal .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

build-job:
  stage: build
  script:
    - echo "Compiling the code..."
    - echo "Compile complete."

test-job:
  stage: test
  script:
    - echo "Running tests..."

deploy-job:
  stage: deploy
  script:
    - echo "Deploying to production..."

The CI Lint tool (available in the GitLab UI under CI/CD > Pipelines > CI Lint) validates your .gitlab-ci.yml syntax before you commit, helping you catch configuration errors early.

Where must the .gitlab-ci.yml file be placed by default?
What GitLab UI tool validates .gitlab-ci.yml syntax before committing?

3. What is a GitLab CI/CD pipeline?

A pipeline is the top-level component of GitLab CI/CD. It is a complete automated workflow that runs when triggered by an event (such as a git push). A pipeline consists of one or more stages, each containing one or more jobs.

Pipeline hierarchy
LevelComponentDescription
1 (top)PipelineThe complete automated workflow triggered by an event
2StageA group of jobs that run in parallel; stages run sequentially
3 (bottom)JobThe individual unit of work - runs scripts on a runner

Pipeline lifecycle:

  • A pipeline is created when a triggering event occurs (push, merge request, schedule, API call, etc.)
  • Jobs are assigned to available runners which execute them
  • The pipeline passes if all jobs succeed, fails if any job fails
  • Results and logs are visible under Build > Pipelines in the GitLab UI
# A pipeline with 3 stages:
# Stage 1 (build): runs first
# Stage 2 (test): runs after build passes
# Stage 3 (deploy): runs after test passes

stages:
  - build
  - test
  - deploy

build-app:
  stage: build
  script: make build

unit-tests:
  stage: test
  script: make test

deploy-staging:
  stage: deploy
  script: make deploy

In a GitLab CI/CD pipeline, what is the relationship between stages and jobs?
Where can you view running and completed pipelines in the GitLab UI?

4. What are stages in GitLab CI/CD and how do you define them?

Stages control the order of execution in a pipeline. Jobs within the same stage run in parallel; stages themselves run sequentially. If any job in a stage fails, subsequent stages are not run (unless configured otherwise).

# Define stages at the top level of .gitlab-ci.yml
stages:
  - build
  - test
  - deploy

# Jobs reference their stage with the "stage" keyword
build-job:
  stage: build
  script:
    - echo "Building..."

test-unit:
  stage: test
  script:
    - echo "Running unit tests..."

test-integration:
  stage: test       # also in "test" stage - runs PARALLEL to test-unit
  script:
    - echo "Running integration tests..."

deploy-job:
  stage: deploy
  script:
    - echo "Deploying..."

Default stages: GitLab provides default stages if you do not define a stages block. The built-in defaults are: .pre, build, test, deploy, .post. The special .pre stage always runs first (before any other stages) and .post always runs last.

Special built-in stages
StageBehaviour
.preAlways runs before all other stages - regardless of stages list order
.postAlways runs after all other stages
What happens when two jobs are assigned to the same stage in GitLab CI/CD?
What is the special built-in stage that always runs before all other stages in GitLab CI?

5. What is a CI/CD job in GitLab and what are the required keywords?

A job is the fundamental unit of work in a GitLab pipeline. It defines a set of shell commands to run, where to run them, and under what conditions. Jobs run independently on runners and produce a job log accessible in the GitLab UI.

# Minimum viable job (only "script" is required):
hello-world:
  script:
    - echo "Hello, World!"

# Full job with common keywords:
build-app:
  stage: build
  image: node:20-alpine
  before_script:
    - npm ci
  script:
    - npm run build
  after_script:
    - echo "Build complete"
  artifacts:
    paths:
      - dist/
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Key job keywords
KeywordPurposeRequired?
scriptCommands to execute - the job's main workYes
stageWhich stage this job belongs toNo (defaults to 'test')
imageDocker image to use as the job environmentNo
before_scriptCommands that run before script (setup)No
after_scriptCommands that run after script (cleanup, always runs)No
artifactsFiles/dirs to save and pass to later jobsNo
rulesConditions that determine when the job runsNo
tagsSpecify which runner to use by tagNo

Jobs must be defined at the top level of the YAML file. Any YAML key not matching a reserved keyword is treated as a job name. Job names starting with a dot (e.g. .setup) are hidden jobs - they don't run but can be used as templates.

What is the only required keyword in a GitLab CI/CD job definition?
What is a 'hidden job' in GitLab CI/CD?

6. What is a GitLab Runner and what types are available?

A GitLab Runner is an agent (a program) that picks up CI/CD jobs from GitLab and executes them. Runners are separate from the GitLab server - they can run on any machine including physical servers, VMs, containers, or cloud instances.

Runner types by scope
TypeScopeConfigured at
Instance runners (shared)Available to all projects on a GitLab instanceAdmin area (self-managed) or GitLab.com
Group runnersAvailable to all projects in a group and its subgroupsGroup > Settings > CI/CD > Runners
Project runners (specific)Available to one specific projectProject > Settings > CI/CD > Runners

GitLab.com users: GitLab.com provides instance runners that you can use immediately without any setup. For self-managed GitLab, administrators must install and register runners separately.

Runner executors: how the runner actually runs jobs depends on its executor type:

Common runner executors
ExecutorRuns jobs inUse case
ShellDirect shell on the runner machineSimple setups; persistent environment
DockerA fresh Docker container per jobIsolated, reproducible environments
Docker+MachineAuto-scaled Docker VMsHigh concurrency, auto-scaling
KubernetesA Kubernetes pod per jobCloud-native, auto-scaling
Virtual MachineA VM snapshotFull VM isolation

You specify which runner to use by adding tags to a job. The runner must have the same tag registered to pick up that job.

What is the scope of a GitLab 'group runner'?
How do you direct a GitLab CI job to a specific runner?

7. What is the 'script', 'before_script', and 'after_script' keywords and how do they differ?

These three keywords define the shell commands that run during a job. They execute in sequence but have different scopes and failure behaviour.

Script keyword comparison
KeywordWhen it runsFails the job if it fails?Scope
before_scriptBefore the main scriptYesCan be set globally or per-job
scriptThe job's main workYesPer-job only (required)
after_scriptAfter script (and before_script)No (always runs)Can be set globally or per-job
# Global before_script runs before every job's script
default:
  before_script:
    - echo "Global setup"

build-job:
  stage: build
  before_script:
    - echo "Job-specific setup (overrides global)"  # overrides the default
  script:
    - npm ci
    - npm run build
  after_script:
    - echo "Cleanup - always runs even if script fails"

Key behaviours:

  • before_script defined in a job overrides (not appends to) the global before_script
  • after_script runs in a separate shell context from script - environment variable changes made in script are not visible in after_script
  • after_script runs even when the job fails or times out, making it ideal for cleanup tasks
What happens to after_script if the main script fails?
If you define before_script in both the default section and in a specific job, what happens?

8. How do you use the 'image' keyword to specify a Docker environment for a job?

The image keyword specifies the Docker image that the runner uses as the execution environment for the job. This ensures each job runs in a consistent, isolated environment with all required tools pre-installed.

# Set a default image for all jobs
default:
  image: ubuntu:24.04

# Override image for a specific job
build-node:
  stage: build
  image: node:20-alpine     # uses Node.js 20
  script:
    - node --version
    - npm ci
    - npm run build

build-python:
  stage: build
  image: python:3.12-slim   # different image for Python job
  script:
    - pip install -r requirements.txt
    - python -m pytest

# Image with a registry prefix
deploy-job:
  image: registry.gitlab.com/myorg/myimage:latest
  script:
    - ./deploy.sh

# Image with a specific SHA (for reproducibility)
test-job:
  image:
    name: node:20-alpine
    entrypoint: [""]   # override the entrypoint if needed
  script:
    - npm test

Without a Docker executor: the image keyword is only used by runners with a Docker, Kubernetes, or similar container-based executor. Runners using the Shell executor ignore the image keyword and run jobs directly on the host machine.

Default image: if you don't specify an image, GitLab.com runners use a default image (often a Ruby image). Always specify an explicit image for consistent, predictable environments.

What does the 'image' keyword in a GitLab CI job define?
Which runner executor type ignores the 'image' keyword?

9. What are GitLab CI/CD artifacts and how do you use them?

Artifacts are files and directories that a job produces and saves after it finishes. They serve two purposes: downloading results from the GitLab UI, and passing files between jobs in subsequent stages.

build-job:
  stage: build
  script:
    - npm ci
    - npm run build           # creates dist/ directory
  artifacts:
    paths:
      - dist/                 # save entire dist directory
      - coverage/report.html  # save a specific file
    exclude:
      - dist/**/*.map         # exclude sourcemap files
    expire_in: 1 week        # auto-delete after 1 week (default: 30 days)
    when: always             # save artifacts even if job fails

test-job:
  stage: test
  script:
    - ls dist/               # artifacts from build-job are available here!
    - npm test
  dependencies:
    - build-job              # explicitly specify which artifacts to download

# JUnit test reports (display inline in MR UI)
test-with-report:
  stage: test
  script:
    - pytest --junitxml=report.xml
  artifacts:
    reports:
      junit: report.xml

Artifact expiry options
when valueBehaviour
on_success (default)Save artifacts only when job succeeds
on_failureSave artifacts only when job fails (useful for debug files)
alwaysSave artifacts regardless of job result

Automatic download: by default, jobs automatically download artifacts from all jobs in preceding stages. Use dependencies: [] to opt out, or list specific jobs to download from.

What are GitLab CI/CD artifacts used for?
What does setting 'artifacts: when: always' do?

10. What is the difference between cache and artifacts in GitLab CI?

Cache and artifacts are both mechanisms for persisting files across jobs, but they serve very different purposes and have different behaviours.

Cache vs Artifacts
AspectCacheArtifacts
PurposeSpeed up jobs by reusing downloaded dependenciesPass build outputs between stages/jobs
StorageRunner's local disk or shared S3/GCSGitLab server
ScopeSame runner, same branch (by cache key)Any runner, same pipeline
Typical contentnode_modules/, .m2/, pip cachedist/, binaries, test reports
ReliabilityNot guaranteed (may not exist)Guaranteed (always transferred)
Downloaded byOnly the same runner (by default)All jobs in later stages
ExpiryConfigurable; can be cleared manuallyexpire_in (default: 30 days)
# Cache example: reuse npm packages across pipeline runs
cache:
  key: "$CI_COMMIT_REF_SLUG"   # different cache per branch
  paths:
    - node_modules/
  policy: pull-push           # default: download at start, upload at end

# Artifacts example: pass build output to next stage
build:
  script: npm run build
  artifacts:
    paths:
      - dist/

# Best practice: use both together
build-job:
  cache:
    paths:
      - node_modules/  # cache deps for speed
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/          # pass output to deploy stage

Key insight: cache is not reliable for passing required build outputs between jobs - it is a performance optimisation only. For guaranteed file transfer between jobs, always use artifacts.

What is the primary purpose of cache in GitLab CI/CD?
Why should you NOT use cache to pass required build files between stages?

11. What are GitLab CI/CD variables and what types are available?

CI/CD variables are key-value pairs that make values available to jobs. They are used to configure jobs without hardcoding values, store secrets safely, and pass information between pipeline components.

Variable types
TypeWhere definedUse case
Predefined variablesSet automatically by GitLabPipeline metadata: $CI_COMMIT_SHA, $CI_COMMIT_BRANCH
Project variablesSettings > CI/CD > VariablesProject-specific secrets: API keys, passwords
Group variablesGroup > Settings > CI/CD > VariablesShared secrets across projects in a group
Instance variablesAdmin area > CI/CD > VariablesShared across the entire GitLab instance
.gitlab-ci.yml variablesvariables: keyword in YAMLNon-secret config values
# Defining variables in .gitlab-ci.yml
variables:
  DATABASE_URL: "postgres://localhost/mydb"  # available to all jobs
  DEPLOY_ENV: "staging"

build-job:
  variables:
    NODE_ENV: "production"   # job-level variable (overrides global)
  script:
    - echo "Branch: $CI_COMMIT_BRANCH"
    - echo "Commit: $CI_COMMIT_SHA"
    - echo "DB: $DATABASE_URL"
    - echo "Node env: $NODE_ENV"

Security settings for project/group variables:

  • Masked - value is hidden in job logs (must be a single line, no spaces)
  • Protected - variable only available to jobs running on protected branches or tags
  • Expanded - controls whether $VAR references within the value are expanded
What does marking a GitLab CI variable as 'Masked' do?
What is a 'predefined variable' in GitLab CI/CD?

12. What are the most important predefined CI/CD variables in GitLab?

GitLab automatically sets dozens of predefined variables for every pipeline run. These provide jobs with information about the current commit, branch, pipeline, runner, and environment without any configuration.

Essential predefined variables
VariableValueCommon use
$CI_COMMIT_SHAFull commit SHA (40 chars)Tagging Docker images
$CI_COMMIT_SHORT_SHAFirst 8 chars of SHAShort image tags, logs
$CI_COMMIT_BRANCHBranch nameConditional logic (if main...)
$CI_COMMIT_REF_NAMEBranch or tag nameGeneric ref reference
$CI_COMMIT_REF_SLUGBranch name, URL-safe (/ replaced with -)Docker tags, URLs
$CI_PIPELINE_IDUnique pipeline IDLinking artifacts to pipelines
$CI_JOB_IDUnique job IDUnique artifact names
$CI_PROJECT_NAMERepository nameBuild labels
$CI_PROJECT_PATHnamespace/projectRegistry image paths
$CI_REGISTRYGitLab Container Registry URLDocker login
$CI_REGISTRY_IMAGEFull image path in registrydocker build/push
$GITLAB_USER_LOGINUsername of who triggered pipelineAudit logs
$CI_PIPELINE_SOURCEWhat triggered the pipelineConditional job rules
deploy:
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - echo "Deployed by $GITLAB_USER_LOGIN from $CI_COMMIT_BRANCH"

What does the predefined variable $CI_COMMIT_REF_SLUG contain?
Which predefined variable contains the full path to the project's container image in the GitLab registry?

13. What are 'rules' in GitLab CI/CD and how do they control when jobs run?

The rules keyword lets you add conditions that determine whether a job is included in a pipeline, skipped, or made manual. Rules are evaluated in order - the first matching rule applies and the rest are ignored.

deploy-prod:
  stage: deploy
  script:
    - ./deploy.sh production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"     # only run on main branch
      when: on_success                     # (default) run if prior stages passed
    - if: $CI_PIPELINE_SOURCE == "schedule"
      when: on_success
    - when: never                          # never run for any other condition

# Common rules patterns:
job-on-mr:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

job-on-tag:
  rules:
    - if: $CI_COMMIT_TAG                   # runs when a git tag is pushed

job-manual-on-prod:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual                         # must be manually triggered in UI

job-with-changes:
  rules:
    - changes:                             # only run if these files changed
        - src/**/*
        - package.json

job-allow-fail:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      allow_failure: true                  # pipeline passes even if this fails

rules: when values
ValueBehaviour
on_success (default)Run if all prior jobs in pipeline passed
on_failureRun only if a prior job failed
alwaysRun regardless of prior job status
neverNever add this job to the pipeline
manualAdd job but require manual trigger to start
delayedRun after a specified delay
In GitLab CI rules, what happens when multiple rules conditions are defined?
What does 'when: manual' in a rules block do?

14. What is the difference between 'only/except' and 'rules' in GitLab CI?

only and except are the original (legacy) keywords for controlling when jobs run. rules is the modern replacement introduced in GitLab 12.3 that provides more flexibility and clearer logic.

only/except vs rules
Aspectonly/exceptrules
IntroductionGitLab initial releaseGitLab 12.3
StatusStill supported; not deprecated but not recommended for new pipelinesRecommended for new configurations
ConflictCan be used together (but only/except wins if both present on same job)Cannot be combined with only/except on the same job
LogicSeparate allow/deny listsSequential rule evaluation - first match wins
FlexibilityLimited - only ref, variables, changes, kubernetesFull expressions, allow_failure, when, start_in
# Legacy only/except (works but not recommended for new pipelines):
deploy-old:
  script: ./deploy.sh
  only:
    - main
    - tags
  except:
    - schedules

# Modern rules equivalent (preferred):
deploy-new:
  script: ./deploy.sh
  rules:
    - if: $CI_COMMIT_TAG                         # matches any tag push
    - if: $CI_COMMIT_BRANCH == "main"
      when: on_success
    - when: never

Recommendation: use rules for all new pipeline configurations. It supports complex conditional logic with if, changes, exists, and when in a single keyword, replacing the fragmented only/except approach.

Which keyword approach is recommended for new GitLab CI/CD pipelines?
Can you use 'only' and 'rules' on the same job in GitLab CI?

15. What is the 'needs' keyword in GitLab CI/CD and what problem does it solve?

The needs keyword creates a directed acyclic graph (DAG) of job dependencies, allowing jobs to start as soon as their specific dependencies are complete - rather than waiting for the entire previous stage to finish. This reduces total pipeline time significantly.

stages:
  - build
  - test
  - deploy

build-frontend:
  stage: build
  script: npm run build
  artifacts:
    paths: [dist/]

build-backend:
  stage: build
  script: go build -o api ./cmd
  artifacts:
    paths: [api]

# Without needs: test-frontend must wait for BOTH build jobs to finish
# With needs: test-frontend starts as soon as build-frontend is done
test-frontend:
  stage: test
  needs:
    - build-frontend      # only waits for this specific job
  script: npm test

test-backend:
  stage: test
  needs:
    - build-backend       # only waits for this specific job
  script: go test ./...

deploy:
  stage: deploy
  needs:
    - test-frontend
    - test-backend
  script: ./deploy.sh

Key behaviours with needs:

  • Jobs with needs can run out of stage order - needs overrides stage sequencing
  • A job with needs: [] (empty list) starts immediately when the pipeline creates
  • By default, a job with needs downloads artifacts from the listed jobs
  • Set artifacts: false in needs to skip downloading artifacts
What problem does the 'needs' keyword solve in GitLab CI/CD?
What does 'needs: []' (an empty needs array) do to a job?

16. What is the 'workflow' keyword in GitLab CI and how does it control pipeline creation?

The workflow keyword controls whether an entire pipeline is created at all, based on conditions. While rules on individual jobs control whether a specific job runs, workflow: rules controls whether the entire pipeline is created for a given event.

# Only create pipelines for main branch, MRs, and tags
workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_TAG
    - when: never   # suppress all other pipeline triggers

# Name the pipeline dynamically
workflow:
  name: "Pipeline for $CI_COMMIT_REF_NAME"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      name: "MR Pipeline: $CI_MERGE_REQUEST_TITLE"
    - if: $CI_COMMIT_BRANCH

# Prevent duplicate pipelines (MR + branch pipelines)
# This is a very common pattern:
workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
      when: never   # skip branch pipeline when MR pipeline exists
    - if: $CI_COMMIT_BRANCH

Preventing duplicate pipelines: by default, pushing to a branch that has an open MR creates two pipelines - a branch pipeline and an MR pipeline. The pattern above suppresses branch pipelines when an MR is open, avoiding redundant CI runs that waste runner minutes.

What does 'workflow: rules' control in GitLab CI/CD?
Why is the 'prevent duplicate pipelines' workflow pattern commonly used?

17. What is the 'extends' keyword and how does it enable job templates?

The extends keyword lets one job inherit configuration from another job (or a hidden job used as a template). This reduces duplication and keeps the pipeline DRY (Don't Repeat Yourself).

# Define a hidden job template (starts with .)
.base-deploy:
  image: alpine:latest
  before_script:
    - apk add --no-cache curl
    - echo "Logging into registry..."
  after_script:
    - echo "Deployment complete"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

# Jobs extend the template
deploy-staging:
  extends: .base-deploy
  script:
    - ./deploy.sh staging
  environment:
    name: staging

deploy-production:
  extends: .base-deploy
  script:
    - ./deploy.sh production
  environment:
    name: production
  rules:                          # overrides the template rules
    - if: $CI_COMMIT_TAG
      when: manual

# extends with multiple parents (array form)
.logging:
  after_script:
    - send-logs.sh

my-job:
  extends:
    - .base-deploy
    - .logging          # merges config from both (later parent wins on conflict)

Merge behaviour: when a job extends a template, YAML hashes are merged deeply. Arrays (like script, rules) in the child job replace (not append to) the parent's arrays. A job can extend multiple templates by providing an array - conflicts are resolved with the last-listed parent winning, then the child overriding all.

How does GitLab CI resolve conflicts when a job overrides a field from its extended template?
What naming convention is used for template jobs that should not run on their own?

18. What is the 'include' keyword and how do you share CI configuration across projects?

The include keyword lets you split pipeline configuration across multiple files or pull shared configuration from external sources. This is the foundation for sharing CI standards across many projects in an organisation.

include types
TypeSyntaxUse case
localinclude: local: '/templates/jobs.yml'Another file in the same repository
projectinclude: project: 'group/repo', file: '/ci/template.yml'File in another GitLab project
remoteinclude: remote: 'https://example.com/ci.yml'Any publicly accessible URL
templateinclude: template: 'Auto-DevOps.gitlab-ci.yml'GitLab built-in templates
# Include multiple sources
include:
  - local: '/ci/build.yml'         # file in same repo
  - project: 'myorg/ci-templates' # file from another project
    ref: main
    file: '/templates/deploy.yml'
  - template: 'SAST.gitlab-ci.yml'  # GitLab built-in security template
  - remote: 'https://cdn.example.com/ci/lint.yml'  # external URL

# Using included configuration
# The included files can define jobs, stages, variables, etc.
# They are merged into the main .gitlab-ci.yml

stages:
  - build
  - test
  - security
  - deploy

# Your local jobs here, plus everything from the included files

Security note: when including files from another project with include: project, the user triggering the pipeline must have at least Reporter access to that project. Using a specific SHA hash for ref (rather than a branch name) is recommended for stability and security - branch-based refs can change.

Which include type pulls a shared CI template from another GitLab project in the same instance?
Why is it recommended to use a specific SHA hash for 'ref' when including files from another project?

19. What are GitLab environments and how do you define them in CI/CD?

Environments in GitLab CI/CD represent deployment targets - staging, production, review apps, etc. Defining an environment in a job links that deployment to the environment, enabling GitLab to track which version is deployed where and provide rollback capabilities.

# Basic environment definition
deploy-staging:
  stage: deploy
  script:
    - ./deploy.sh staging
  environment:
    name: staging
    url: https://staging.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

deploy-production:
  stage: deploy
  script:
    - ./deploy.sh production
  environment:
    name: production
    url: https://example.com
  rules:
    - if: $CI_COMMIT_TAG
      when: manual              # requires manual approval

# Dynamic review environments per merge request
review-app:
  stage: deploy
  script:
    - deploy-to-review.sh $CI_COMMIT_REF_SLUG
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_COMMIT_REF_SLUG.review.example.com
    on_stop: stop-review
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

stop-review:
  stage: deploy
  script:
    - teardown-review.sh $CI_COMMIT_REF_SLUG
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: manual

Environments are visible under Deploy > Environments in the GitLab UI, showing the current deployed version, who deployed it, and when. The on_stop option links a cleanup job that tears down ephemeral environments (like review apps) when no longer needed.

What does defining 'environment: name: production' in a deploy job do?
What is a 'review app' in GitLab CI/CD?

20. What are manual jobs in GitLab CI/CD and how do you configure them?

A manual job is a job that is added to the pipeline but does not run automatically - it requires someone to click a button in the GitLab UI (or API call) to trigger it. Manual jobs are commonly used for production deployments that need human approval.

# Method 1: using rules
deploy-production:
  stage: deploy
  script:
    - ./deploy.sh production
  environment: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual              # must be clicked to run
      allow_failure: false      # blocks pipeline completion until clicked

# Method 2: legacy "when" keyword
deploy-prod-legacy:
  stage: deploy
  script: ./deploy.sh production
  when: manual
  only:
    - main

# Blocking vs non-blocking manual jobs:
# Non-blocking (default): pipeline is marked as passed without clicking
# Blocking: pipeline stays in "blocked" status until job is triggered

# To make it blocking (requires allow_failure: false):
critical-approval:
  stage: approve
  script: echo "Approval recorded"
  when: manual
  allow_failure: false          # blocks the pipeline

# Protected manual jobs (only certain roles can trigger):
# Set in project Settings > CI/CD > Protected environments

allow_failure with manual jobs: when when: manual is set without allow_failure: false, the job is non-blocking - the pipeline proceeds (and can pass) without the manual job being triggered. With allow_failure: false, the pipeline stays in a blocked state until someone triggers the manual job.

What is the difference between a blocking and non-blocking manual job in GitLab CI?
Which keyword combination makes a manual job block the pipeline from completing?

21. What is the 'allow_failure' keyword and how does it affect pipeline status?

The allow_failure keyword controls whether a failing job causes the pipeline to fail. When set to true, the job can fail without marking the overall pipeline as failed - GitLab shows a warning icon on the job instead of a failure.

# allow_failure: true - job failure does not fail the pipeline
linting:
  stage: test
  script:
    - eslint src/ --max-warnings 0
  allow_failure: true          # linting warnings won't block deployment

# allow_failure with rules (preferred modern approach)
code-quality:
  stage: test
  script:
    - run-quality-check.sh
  rules:
    - if: $CI_COMMIT_BRANCH
      allow_failure: true
    - if: $CI_COMMIT_TAG
      allow_failure: false     # strict on releases

# allow_failure with exit codes
# Only allow specific non-zero exit codes:
flaky-test:
  script:
    - run-flaky-tests.sh
  allow_failure:
    exit_codes: 137            # allow OOM kills (exit code 137)

# allow_failure: true with manual jobs
# makes the manual job non-blocking
optional-deploy:
  when: manual
  allow_failure: true          # pipeline passes without triggering this job

allow_failure effect on pipeline
allow_failureJob failsPipeline result
false (default)YesPipeline fails
trueYesPipeline passes with warning icon
exit_codes: [N]Exit code NPipeline passes; other codes still fail pipeline
What does setting 'allow_failure: true' on a job do?
How does allow_failure: exit_codes differ from allow_failure: true?

22. How does parallel job execution work in GitLab CI/CD?

GitLab CI supports running multiple instances of the same job in parallel using the parallel keyword. This is useful for splitting test suites across multiple runners to reduce total execution time.

# Run the same job N times in parallel
test:
  stage: test
  image: ruby:3.2
  script:
    - bundle exec rspec
  parallel: 5
  # GitLab creates 5 jobs: test 1/5, test 2/5, ..., test 5/5
  # Each job gets: CI_NODE_INDEX (1-5) and CI_NODE_TOTAL (5)
  # Your test framework uses these to split the test suite

# Parallel matrix - run job with different variable combinations
build:
  stage: build
  script:
    - echo "Node: $NODE_VERSION on $OS"
  parallel:
    matrix:
      - NODE_VERSION: ["18", "20", "22"]
        OS: ["ubuntu", "alpine"]
  # Creates 6 jobs: all combinations of NODE_VERSION x OS

# Access which shard you are in:
test-shard:
  script:
    - echo "Running shard $CI_NODE_INDEX of $CI_NODE_TOTAL"
    - run-tests --split $CI_NODE_TOTAL --shard $CI_NODE_INDEX

Parallel job variables
VariableValue
$CI_NODE_INDEXThe index of this parallel job (1-based)
$CI_NODE_TOTALTotal number of parallel jobs
$MATRIX_VARMatrix variables become env vars in each job

Parallel jobs all run in the same stage - they start at the same time (assuming runners are available) and the stage completes when all parallel jobs finish. All jobs must pass for the stage to pass.

What predefined variable tells a parallel job which shard it is?
What does 'parallel: matrix' allow you to do in GitLab CI?

23. What are GitLab CI/CD scheduled pipelines and how do you set them up?

Scheduled pipelines run on a defined timetable (like a cron job) without requiring a code push. They are useful for nightly builds, weekly security scans, database backups, and performance benchmarks.

# In .gitlab-ci.yml - detect schedule trigger:
nightly-test:
  stage: test
  script:
    - run-full-test-suite.sh
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"   # only runs when scheduled

regular-test:
  stage: test
  script:
    - run-quick-tests.sh
  rules:
    - if: $CI_PIPELINE_SOURCE != "schedule"   # skip during scheduled runs

# Variables defined in the schedule are available as CI/CD variables:
nightly-deploy:
  script:
    - echo "Running type: $PIPELINE_TYPE"     # set in schedule config
    - echo "Env: $DEPLOY_ENV"

Setting up a schedule in the UI:

  • Go to Build > Pipeline schedules > New schedule
  • Enter a cron expression (e.g. 0 2 * * * for 2am daily)
  • Select the target branch or tag
  • Optionally add custom CI/CD variables for this schedule
  • Enable/disable the schedule without deleting it

The $CI_PIPELINE_SOURCE predefined variable equals 'schedule' for scheduled pipelines, allowing you to run different jobs for scheduled vs push-triggered pipelines.

What value does $CI_PIPELINE_SOURCE have when a pipeline runs on a schedule?
Where do you configure scheduled pipelines in GitLab?

24. What are pipeline triggers and how can a pipeline be started without a git push?

GitLab pipelines can be triggered by multiple mechanisms beyond a git push. Understanding all trigger sources allows you to design flexible CI/CD workflows.

Pipeline trigger sources ($CI_PIPELINE_SOURCE values)
SourceValueHow triggered
Git pushpushDeveloper pushes commits to any branch
Merge requestmerge_request_eventMR is created or updated
Web UIweb'Run pipeline' button in GitLab UI
APIapiPOST to /api/v4/projects/:id/pipeline
Trigger tokentriggerPOST with a pipeline trigger token
SchedulescheduleScheduled pipeline ran
Parent pipelineparent_pipelineUpstream pipeline triggered this child
ChatchatGitLab ChatOps slash command
# Trigger a pipeline via API:
curl --request POST \
  --form "token=<trigger-token>" \
  --form "ref=main" \
  --form "variables[DEPLOY_ENV]=staging" \
  "https://gitlab.example.com/api/v4/projects/123/trigger/pipeline"

# Trigger a downstream pipeline from a job (bridge job):
trigger-deploy:
  stage: deploy
  trigger:
    project: myorg/deploy-repo
    branch: main
    strategy: depend   # wait for downstream pipeline to complete

# Check trigger source in rules:
job-for-mr-only:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Pipeline triggers can pass custom variables, enabling flexible parameterised pipelines. For example, you can trigger a deployment pipeline from another system, passing the environment name and image tag as variables.

What $CI_PIPELINE_SOURCE value is set when a pipeline is triggered via the GitLab UI 'Run pipeline' button?
What does the 'trigger' keyword in a GitLab CI job do?

25. What is the 'retry' keyword and how do you handle flaky tests in GitLab CI?

The retry keyword automatically reruns a job when it fails, up to the specified number of times. This is useful for handling transient failures - network timeouts, flaky tests, or race conditions - without manual intervention.

# Simple retry: retry up to 2 times on any failure
flaky-test:
  script:
    - run-tests.sh
  retry: 2                     # total: 3 attempts (1 original + 2 retries)

# Retry only on specific failure types:
network-test:
  script:
    - curl https://api.example.com/data
  retry:
    max: 2
    when:
      - runner_system_failure   # runner itself crashed
      - stuck_or_timeout_failure  # job timed out
      - api_failure             # GitLab API error during job
      # NOT for script failures (that would mask real test failures)

# All retry "when" values:
# always, unknown_failure, script_failure, api_failure,
# runner_system_failure, missing_dependency_failure,
# stuck_or_timeout_failure, scheduler_failure, data_integrity_failure

Common retry when values
ValueTriggers retry when
alwaysAny failure - use with caution
runner_system_failureThe runner system failed (not script failure)
stuck_or_timeout_failureJob timed out or got stuck
api_failureGitLab API error during job execution
script_failureThe script returned non-zero exit code

Best practice: avoid retry: always or retrying on script_failure - these can hide real failures. Only retry on infrastructure failures (runner_system_failure, stuck_or_timeout_failure) where the failure is clearly not the code's fault.

What does 'retry: 2' in a GitLab CI job mean?
Why is it generally bad practice to set 'retry: when: script_failure'?

26. What is the 'timeout' keyword in GitLab CI and what are the defaults?

The timeout keyword sets the maximum duration a job is allowed to run before GitLab forcefully stops it. Timeouts prevent hanging jobs from blocking runners and consuming resources indefinitely.

# Job-level timeout
long-running-test:
  script:
    - run-full-integration-tests.sh
  timeout: 2 hours             # stop after 2 hours

build-job:
  script:
    - make build
  timeout: 30 minutes          # stop after 30 minutes

# Timeout format examples:
# timeout: 3600         (seconds)
# timeout: 60 minutes
# timeout: 2 hours
# timeout: 1h 30m
# timeout: 1 day

# Default timeout hierarchy:
# 1. Job-level timeout (if set)
# 2. Project-level timeout (Settings > CI/CD > General pipelines)
# 3. Runner timeout (set when runner is registered)
# The smallest of project timeout and runner timeout applies,
# then job-level timeout can only be lower than that

Timeout hierarchy
LevelWhere setDefault
Jobtimeout: keyword in jobNo default (uses project or runner)
ProjectSettings > CI/CD > General pipelines60 minutes
RunnerRegistered runner configNo limit (unlimited)

Key rule: a job timeout cannot exceed the project-level timeout or the runner's maximum timeout. If you set a job timeout longer than the project timeout, the project timeout wins.

What is the default project-level job timeout in GitLab CI?
If a job sets timeout: 2 hours but the project-level timeout is 60 minutes, how long does the job run?

27. What is the GitLab Container Registry and how do you use it in CI/CD pipelines?

The GitLab Container Registry is a built-in Docker image registry integrated with every GitLab project. It provides a private place to store and manage Docker images, directly accessible from CI/CD pipelines using predefined variables.

# Build and push to the GitLab Container Registry:
build-image:
  stage: build
  image: docker:27
  services:
    - docker:27-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest

# Pull from registry in a later job:
deploy-job:
  stage: deploy
  script:
    - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - docker run -d $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

Registry predefined variables
VariableValue
$CI_REGISTRYregistry.gitlab.com (or your instance's registry URL)
$CI_REGISTRY_IMAGEFull path: registry.gitlab.com/group/project
$CI_REGISTRY_USERGitLab CI token username (gitlab-ci-token)
$CI_REGISTRY_PASSWORDGitLab CI token (auto-generated job token)

The docker:dind (Docker-in-Docker) service is required when building Docker images inside a Docker-based runner. The DOCKER_TLS_CERTDIR variable enables TLS for Docker-in-Docker communication, which is required in modern versions of Docker.

Which predefined variable contains the full image path in the GitLab Container Registry?
What is 'docker:dind' used for in GitLab CI/CD?

28. What are 'services' in GitLab CI/CD and how do you use them?

Services are Docker containers that run alongside the main job container, providing supporting infrastructure like databases, message brokers, or a Docker daemon. They share a network namespace with the job container.

# Run tests with a PostgreSQL database service:
test-with-db:
  stage: test
  image: python:3.12
  services:
    - postgres:16-alpine          # starts a Postgres container
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: postgres
    POSTGRES_PASSWORD: secret
    DATABASE_URL: "postgresql://postgres:secret@postgres/testdb"
  script:
    - pip install -r requirements.txt
    - python -m pytest

# Multiple services:
integration-test:
  image: node:20
  services:
    - redis:7-alpine
    - postgres:16-alpine
    - name: rabbitmq:3-management
      alias: rabbit             # access as "rabbit" hostname
  script:
    - npm test

# Service with alias and entrypoint override:
custom-service:
  services:
    - name: my-registry/my-service:latest
      alias: api-mock
      entrypoint: ["/usr/bin/api-mock"]
      command: ["--port", "8080"]

Services are accessible from the main job container using the service's image name (or alias) as the hostname. For example, a postgres:16 service is reached at hostname postgres. Set an alias when you need a custom hostname.

How do you access a PostgreSQL service container from the main job container in GitLab CI?
What is the purpose of the 'alias' option when defining a service in GitLab CI?

29. What is the 'default' keyword in GitLab CI and how does it reduce duplication?

The default keyword defines configuration that applies to all jobs in the pipeline unless overridden at the job level. It acts as a global default for common settings, reducing repetition across many jobs.

# Set defaults for all jobs
default:
  image: ubuntu:24.04
  before_script:
    - apt-get update -qq
    - echo "Setting up environment..."
  after_script:
    - echo "Job complete"
  retry:
    max: 1
    when: runner_system_failure
  timeout: 1 hour
  interruptible: true           # allow newer pipelines to cancel this job

# Jobs inherit defaults but can override individual settings:
build-job:
  image: node:20-alpine         # overrides the default ubuntu image
  script:
    - npm ci && npm run build   # before_script from default still runs

python-test:
  # uses all defaults: ubuntu image, before_script, retry, timeout
  script:
    - pip install -r requirements.txt
    - pytest
  before_script:
    - pip install --upgrade pip # overrides global before_script entirely

Keywords supported in 'default'
KeywordEffect when set globally
imageDefault Docker image for all jobs
before_scriptSetup commands prepended to every job
after_scriptCleanup commands appended to every job
retryDefault retry behaviour for all jobs
timeoutDefault job timeout
interruptibleWhether newer pipelines can cancel pending jobs
artifactsDefault artifact paths
cacheDefault cache configuration
What does the 'default' keyword in .gitlab-ci.yml define?
What is the 'interruptible: true' setting in the default section used for?

30. What are YAML anchors and aliases in GitLab CI and how do they reduce duplication?

YAML anchors (&name) and aliases (*name) are standard YAML features that let you define a block once and reuse it multiple times. In GitLab CI, they reduce duplication for shared configuration like rules, before_script, or variables - without requiring the extends keyword.

# Define a reusable block with an anchor
.common_rules: &common_rules
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

.common_setup: &common_setup
  image: node:20-alpine
  before_script:
    - npm ci

# Reuse with alias (*) or merge key (<<: *)
build-job:
  <<: *common_setup        # merge the anchor content
  <<: *common_rules        # YAML 1.1 syntax
  script:
    - npm run build

test-job:
  <<: *common_setup
  <<: *common_rules
  script:
    - npm test

# Anchors for variables
.env_vars: &prod_vars
  variables:
    ENVIRONMENT: production
    LOG_LEVEL: error

deploy-prod:
  <<: *prod_vars
  script: ./deploy.sh production

Anchors vs extends:

  • YAML anchors are processed before GitLab reads the YAML - they are pure YAML features and work at the text level
  • extends is a GitLab-specific feature processed after YAML parsing, offering deep merging, multiple parents, and better tooling visibility
  • GitLab recommends using extends over YAML anchors for job configuration since it is more explicit and readable
What is the difference between a YAML anchor (&) and an alias (*) in .gitlab-ci.yml?
Which approach does GitLab recommend for reusing job configuration - YAML anchors or the 'extends' keyword?

31. What is a merge request pipeline in GitLab CI and how does it differ from a branch pipeline?

GitLab supports two types of pipelines related to merge requests: branch pipelines (triggered by a push to a branch) and merge request pipelines (triggered specifically by merge request events). Understanding the difference is essential for designing efficient review workflows.

Branch pipeline vs MR pipeline
AspectBranch PipelineMerge Request Pipeline
TriggerPush to a branchMR created, updated, or rebased
Source variablepushmerge_request_event
Runs onThe feature branch's HEADMerge result (branch + target merged)
MR status checksNot shown in MR (unless configured)Shown in MR as status checks
MR variables$CI_MERGE_REQUEST_* not set$CI_MERGE_REQUEST_* fully populated
Merged resultTests the branch as-isTests what the code will look like after merge
# Jobs that run only in MR pipelines:
code-review:
  script:
    - run-code-quality.sh
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

# Useful MR-specific variables:
check-mr:
  script:
    - echo "MR ID: $CI_MERGE_REQUEST_ID"
    - echo "Target branch: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME"
    - echo "Source branch: $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
    - echo "MR title: $CI_MERGE_REQUEST_TITLE"
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

# Merged results pipeline (tests the post-merge state):
# Enable in Settings > Merge requests > Merged results pipelines
# Runs on the merge commit that would result from accepting the MR

Merged results pipelines (enabled in project settings) run the CI on the simulated merge result - catching conflicts between the MR branch and the target branch that would only appear after merging. This is safer than testing just the source branch.

What predefined variable is populated in a merge request pipeline but NOT in a branch pipeline?
What advantage does a 'merged results pipeline' have over a regular branch pipeline?

32. What is pipeline caching and how do you configure cache keys?

Cache in GitLab CI speeds up jobs by storing files (typically dependencies like node_modules/) between pipeline runs. The cache key determines which jobs share a cache and when the cache is invalidated.

# Basic cache with branch-scoped key:
cache:
  key: "$CI_COMMIT_REF_SLUG"     # different cache per branch
  paths:
    - .npm/
    - node_modules/

# Cache keyed to dependency file (invalidates when file changes):
cache:
  key:
    files:
      - package-lock.json         # cache key changes when lockfile changes
    prefix: "$CI_COMMIT_REF_SLUG"
  paths:
    - node_modules/

# Cache policies (pull-push is default):
job-with-readonly-cache:
  cache:
    key: $CI_COMMIT_REF_SLUG
    paths:
      - node_modules/
    policy: pull                  # only download cache, never update it

job-that-updates-cache:
  cache:
    policy: push                  # only upload, skip downloading

# Per-job cache:
build:
  cache:
    key: "build-$CI_COMMIT_REF_SLUG"
    paths: [dist/]

test:
  cache:
    key: "deps-$CI_COMMIT_REF_SLUG"
    paths: [node_modules/]

Cache policy values
PolicyBehaviour
pull-push (default)Download cache at start, upload updated cache at end
pullDownload cache only - never update (read-only)
pushUpload cache only - never download (write-only)

Use policy: pull for most jobs that just use the cache, and one dedicated job with policy: push or pull-push to update it. This prevents multiple parallel jobs from overwriting each other's cache updates.

What does using 'cache: key: files: [package-lock.json]' as a cache key accomplish?
What does 'cache: policy: pull' do?

33. What is the GitLab CI Lint tool and how do you validate a.gitlab-ci.yml file?

Before committing a .gitlab-ci.yml change and waiting for a pipeline to run, you can validate the configuration using GitLab's built-in CI Lint tool. This catches syntax errors, invalid keywords, and configuration issues immediately.

# Method 1: GitLab UI
# Navigate to: Build > Pipelines > then click "CI Lint"
# or: Project > CI/CD > Lint (path varies by GitLab version)
# Paste your YAML and click "Validate"

# Method 2: Pipeline Editor
# Navigate to: Build > Pipeline editor
# The editor validates in real-time as you type

# Method 3: GitLab API
curl --header "PRIVATE-TOKEN: <your-token>" \
     --header "Content-Type: application/json" \
     --data '{"content": "include:\n  - local: /.gitlab-ci.yml"}' \
     "https://gitlab.example.com/api/v4/projects/:id/ci/lint"

# Method 4: gitlab-ci-local (third-party tool)
# Run pipelines locally for development:
pip install gitlab-ci-local  # or: brew install gitlab-ci-local
gitlab-ci-local              # simulates pipeline execution locally

# The CI Lint tool checks:
# - Valid YAML syntax
# - Valid GitLab CI keywords
# - Correct job structure
# - Variable expansion
# - Stage references are defined

Pipeline editor: GitLab's built-in pipeline editor (Build > Pipeline editor) provides real-time validation, syntax highlighting, a visual pipeline diagram, and the CI Lint tool in one interface. It is the recommended way to edit .gitlab-ci.yml files in the browser.

What is the primary purpose of the GitLab CI Lint tool?
Where can you find GitLab's built-in pipeline editor that validates .gitlab-ci.yml in real time?

34. What are GitLab CI/CD components and how do they differ from includes?

CI/CD components (introduced in GitLab 16.0) are a more structured, versioned, and discoverable way to share pipeline configuration across projects compared to simple include: project. They are published to the CI/CD catalog.

# Using a CI/CD component in .gitlab-ci.yml:
include:
  - component: $CI_SERVER_FQDN/myorg/my-components/deploy@1.0.0
    inputs:
      environment: production
      image_tag: $CI_COMMIT_SHORT_SHA

  # Use a specific version (tag), branch, or SHA:
  - component: gitlab.com/gitlab-org/ci-components/secret-detection@main

# Creating a component (in its own repository):
# File: templates/deploy.yml
---
spec:
  inputs:
    environment:
      description: "Deployment environment (staging or production)"
    image_tag:
      description: "Docker image tag to deploy"
      default: "latest"
---
deploy-$[[ inputs.environment ]]:
  stage: deploy
  script:
    - ./deploy.sh $[[ inputs.environment ]] $[[ inputs.image_tag ]]

include: project vs CI/CD components
Aspectinclude: projectCI/CD components
VersioningVia ref (branch/tag/SHA)Semantic versioning + catalog
InputsNo input mechanismTyped, documented inputs (spec: inputs:)
DiscoveryManual (know the path)Searchable CI/CD catalog
ValidationNoneInput type validation
GitLab versionAll versions16.0+
Recommended forInternal file reuseShareable, published templates

CI/CD components use the $[[ inputs.variable_name ]] syntax (double square brackets) to reference inputs, which is different from regular CI/CD variable syntax $VARIABLE.

What GitLab feature makes CI/CD components more discoverable than a regular 'include: project'?
What syntax is used to reference input values inside a CI/CD component template?

35. What are parent-child pipelines in GitLab CI/CD?

Parent-child pipelines (also called dynamic pipelines) allow a job in one pipeline (the parent) to dynamically generate and trigger another pipeline (the child). This enables modular, scalable pipelines for monorepos and complex projects.

# Parent pipeline (.gitlab-ci.yml):
stages:
  - generate
  - trigger

generate-child-config:
  stage: generate
  script:
    - generate-pipeline-config.sh > child-pipeline.yml  # dynamic YAML
  artifacts:
    paths:
      - child-pipeline.yml

trigger-child:
  stage: trigger
  trigger:
    include:
      - artifact: child-pipeline.yml
        job: generate-child-config
    strategy: depend              # wait for child to complete

# Or trigger from a static file:
trigger-static-child:
  trigger:
    include:
      - local: ci/child-pipeline.yml
    strategy: depend

# Child pipeline (ci/child-pipeline.yml):
stages:
  - build
  - test
build-child:
  stage: build
  script: echo "Child pipeline building..."

Parent-child pipeline features
FeatureDetail
strategy: dependParent pipeline status reflects child pipeline result
Dynamic configChild YAML can be generated at runtime by the parent
IsolationChild pipeline has its own stages, jobs, and variables
ArtifactsChild can receive artifacts from the parent job that triggered it
Depth limitGitLab limits nesting to a certain depth (check current docs)

Common monorepo use case: a parent pipeline detects which subdirectories changed, then triggers separate child pipelines for only those services - avoiding running every service's tests on every push.

What is the purpose of 'strategy: depend' in a trigger job?
What is a key advantage of parent-child pipelines for monorepos?

36. What is the 'resource_group' keyword and how does it prevent concurrent deployments?

The resource_group keyword ensures that only one job with the same resource group name runs at a time across all pipelines. This prevents concurrent deployments to the same environment, which can cause race conditions or configuration conflicts.

# Prevent concurrent deployments to production:
deploy-production:
  stage: deploy
  script:
    - ./deploy.sh production
  resource_group: production      # only one "production" deploy at a time
  environment: production

# Multiple environments, each serialized separately:
deploy-staging:
  script: ./deploy.sh staging
  resource_group: staging         # serializes staging deploys

deploy-production:
  script: ./deploy.sh production
  resource_group: production      # serializes production deploys separately

# Process mode (controls job ordering within the group):
deploy-job:
  resource_group: my-group
  process_mode: oldest_first      # FIFO - deploy in the order triggered
  # Options:
  # oldest_first (default): FIFO queue
  # newest_first: LIFO - skip old deploys, do most recent
  # unordered: no ordering guarantee

resource_group process modes
ModeBehaviourBest for
oldest_first (default)Queue in order - FIFO, every deploy runsCompliance-critical environments
newest_firstLIFO - newer jobs skip older ones waiting in queueFast-moving dev environments
unorderedNo guaranteed orderNon-deployment resource exclusion

Why this matters: without resource groups, if two pipelines both have a deploy-to-production job and run at the same time, both deployment scripts could run concurrently, overwriting each other's changes or causing partial deployments.

What does the 'resource_group' keyword guarantee in GitLab CI?
Which resource_group process_mode would you use to skip outdated deploy jobs and only run the most recently triggered one?

37. What are GitLab's built-in security scanning templates and how do you enable them?

GitLab provides built-in CI/CD templates for security scanning that can be included in any pipeline with a single line. These templates add jobs to your pipeline that scan for vulnerabilities without requiring manual configuration.

# Include GitLab security templates:
include:
  - template: Security/SAST.gitlab-ci.yml           # Static Application Security Testing
  - template: Security/Secret-Detection.gitlab-ci.yml  # Detect secrets/tokens in code
  - template: Security/Dependency-Scanning.gitlab-ci.yml
  - template: Security/DAST.gitlab-ci.yml            # Dynamic (requires running app)
  - template: Security/Container-Scanning.gitlab-ci.yml

stages:
  - build
  - test
  - security                  # security templates add jobs to appropriate stages
  - deploy

# These templates are maintained by GitLab and auto-update
# Results appear in:
# Secure > Vulnerability Report (GitLab Ultimate)
# As MR security widgets (GitLab Ultimate)

# SAST customisation:
variables:
  SAST_EXCLUDED_PATHS: "spec,test,tests,tmp"  # skip test directories
  SAST_EXCLUDED_ANALYZERS: "bandit"           # skip specific analyzer

# Secret Detection customisation:
  SECRET_DETECTION_EXCLUDED_PATHS: "tests/"

Security templates overview
TemplateScans for
SASTCode vulnerabilities using static analysis
Secret DetectionHardcoded secrets, API keys, passwords in code
Dependency ScanningKnown CVEs in project dependencies
DASTRuntime vulnerabilities by attacking a live app
Container ScanningCVEs in Docker image layers

Security scanning results are visible in merge requests and the Vulnerability Report for GitLab Ultimate. For lower tiers, results appear in the job artifacts as JSON reports that can be parsed by other tools.

How do you enable GitLab's built-in SAST (Static Application Security Testing) in a pipeline?
Which GitLab security template detects hardcoded passwords and API tokens committed to source code?

38. What is the 'GIT_STRATEGY' variable in GitLab CI and what are the options?

The GIT_STRATEGY variable controls how GitLab CI fetches your repository's code at the start of each job. Choosing the right strategy balances freshness of code against job startup speed.

GIT_STRATEGY options
ValueBehaviourSpeedUse case
fetch (default)Fetches only new commits; reuses existing cloneFastMost jobs
cloneFull fresh clone every timeSlowWhen clean state is required
noneSkips Git entirely; no repo codeFastestJobs that don't need repo code (deploy from artifact)
# Set globally for all jobs:
variables:
  GIT_STRATEGY: fetch          # default - fast incremental fetch

# Override for a specific job:
clean-build:
  variables:
    GIT_STRATEGY: clone        # ensure completely fresh checkout
  script:
    - make clean && make build

# Skip checkout for deploy-only jobs:
deploy-from-artifact:
  variables:
    GIT_STRATEGY: none         # no git checkout needed
  script:
    - echo "Using artifact from build job..."
    - ls dist/                 # artifact already downloaded
    - ./deploy.sh

# Related: control submodule fetching
variables:
  GIT_SUBMODULE_STRATEGY: normal  # clone/fetch submodules too

GIT_DEPTH: the related variable GIT_DEPTH controls shallow clone depth. Setting GIT_DEPTH: "1" fetches only the latest commit (no history), which is significantly faster for large repositories. Setting it to 0 disables shallow cloning and fetches full history.

What does GIT_STRATEGY: none do in a GitLab CI job?
Which GIT_STRATEGY setting ensures a completely fresh repository clone for every job run?

39. What are protected branches and protected variables in GitLab CI?

Protected branches and protected variables are security features that control who can push to critical branches and which jobs can access sensitive CI/CD variables.

# Protected variables are set in:
# Settings > CI/CD > Variables > Add variable
# Tick "Protected variable" checkbox

# A protected variable is ONLY available in jobs running on:
# 1. Protected branches (e.g. main, production)
# 2. Protected tags

# Example: PROD_API_KEY is protected:
deploy-production:
  stage: deploy
  script:
    - echo "Using: $PROD_API_KEY"    # only available because main is protected
  rules:
    - if: $CI_COMMIT_BRANCH == "main"  # main is a protected branch

# feature branches (unprotected) CANNOT access $PROD_API_KEY:
test-on-feature:
  script:
    - echo "$PROD_API_KEY"   # will be empty - variable not available here
  rules:
    - if: $CI_COMMIT_BRANCH =~ /^feature/

# Protected branches in GitLab:
# Settings > Repository > Protected branches
# Can restrict push access to Maintainers, Developers, No one
# Can require merge request approval before merging
# Can require status checks (pipeline must pass)

Protected branch settings
SettingControls
Allowed to mergeMinimum role required to merge into this branch
Allowed to push and mergeMinimum role required to push directly (bypass MR)
Allowed to force pushWhether force push is allowed (usually disabled)
Code owner approvalWhether CODEOWNERS must approve changes

Security design: this combination means developers on feature branches cannot access production secrets even if they have access to the project. Only pipelines running on protected branches (typically main or release tags) can use protected variables like production API keys.

A CI variable is marked as 'Protected'. In which job will it be available?
What is the security benefit of combining protected branches with protected variables?

40. What are common reasons a GitLab CI pipeline fails and how do you troubleshoot them?

When a pipeline fails, GitLab provides job logs, exit codes, and visual indicators to help diagnose the issue. A systematic approach to troubleshooting resolves most failures quickly.

Common failure causes and fixes
FailureLikely causeFix
Job stuck in 'pending'No runner available with matching tagsCheck runner availability; verify job tags match a runner
'script: command not found'Tool not installed in the Docker imageChange image to one that includes the tool, or install it in before_script
TimeoutJob exceeds project or job timeoutIncrease timeout or optimise the slow step
Artifact not foundDependency job failed or wrong pathCheck dependency job passed; verify artifact path
Permission denied (push)CI_JOB_TOKEN lacks write accessEnable token access in Settings > CI/CD > Token access
Cache not workingCache key mismatch or runner has no cache storageCheck cache key; verify runner has cache storage configured
YAML syntax errorInvalid .gitlab-ci.ymlUse CI Lint tool to validate before committing
# Debugging tips:

# 1. Check the full job log - error is usually at the bottom
# Build > Pipelines > Click the failed job > View log

# 2. Add debug output to the script:
job-debug:
  script:
    - env                        # print all environment variables
    - ls -la                     # show workspace contents
    - which node && node -v      # verify tools are installed
    - cat /etc/os-release        # check OS in the Docker image

# 3. Enable CI/CD debug logging:
variables:
  CI_DEBUG_TRACE: "true"         # very verbose - shows all shell commands

# 4. Re-run a single failed job:
# Click the failed job > Retry
# Or retry from the pipeline view

# 5. Use CI Lint to validate YAML before pushing:
# Build > Pipelines > CI Lint

Most common beginner mistake: setting GIT_STRATEGY: none in a job that actually needs repository code, or forgetting that artifacts from a failed job are not available by default (add artifacts: when: on_failure to capture debug files from failing builds).

A GitLab CI job is stuck in 'pending' for a long time. What is the most likely cause?
What does setting CI_DEBUG_TRACE: 'true' do in a GitLab CI job?
«
»

Comments & Discussions