Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

GitLab CI/CD

Comprehensive guide to GitLab's integrated continuous integration and deployment platform.

GitLab CI/CD

Comprehensive guide to GitLab's integrated continuous integration and deployment platform.

Overview

GitLab CI/CD is a built-in tool for automating software development workflows through pipelines defined in .gitlab-ci.yml. It provides runners for executing jobs, advanced caching mechanisms, environment management, and integrated security scanning. GitLab CI/CD supports complex deployment strategies including blue-green, canary, and progressive delivery patterns.

PassFailCode PushPipeline TriggeredBuild StageTest StageSecurity ScanQuality GatesDeploy StagePipeline FailedProductionPassFailCode PushPipeline TriggeredBuild StageTest StageSecurity ScanQuality GatesDeploy StagePipeline FailedProduction

Pipeline Configuration

Basic .gitlab-ci.yml Structure

# Define stages
stages:
  - build
  - test
  - deploy

# Global variables
variables:
  DOCKER_DRIVER: overlay2
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"

# Job definition
build-job:
  stage: build
  image: maven:3.8-openjdk-11
  script:
    - mvn clean package
  artifacts:
    paths:
      - target/*.jar
    expire_in: 1 week

test-job:
  stage: test
  image: maven:3.8-openjdk-11
  script:
    - mvn test
  coverage: '/Total.*?([0-9]{1,3})%/'

deploy-production:
  stage: deploy
  script:
    - ./deploy.sh production
  environment:
    name: production
    url: https://example.com
  only:
    - main

Advanced Pipeline Features

# Include external configurations
include:
  - project: 'my-group/my-project'
    ref: main
    file: '/templates/.gitlab-ci.yml'
  - remote: 'https://gitlab.com/example/template.yml'
  - template: Security/SAST.gitlab-ci.yml

# Anchors and extends for DRY configuration
.deploy-template: &deploy-template
  image: alpine:latest
  before_script:
    - apk add --no-cache curl
  retry:
    max: 2
    when: runner_system_failure

deploy-staging:
  <<: *deploy-template
  stage: deploy
  environment: staging
  script:
    - curl -X POST $WEBHOOK_URL

# Using extends (modern approach)
.base-deploy:
  image: alpine:latest
  before_script:
    - apk add --no-cache curl

deploy-prod:
  extends: .base-deploy
  script:
    - ./deploy.sh production

Conditional Execution

# Rules-based execution (recommended)
build:
  script: make build
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == "main"'
    - if: '$CI_COMMIT_TAG'
      when: manual

# Only/except (legacy, still supported)
deploy:
  script: deploy.sh
  only:
    - main
    - /^release-.*$/
  except:
    - schedules

# Changes detection
test-frontend:
  script: npm test
  rules:
    - changes:
        - "**/*.js"
        - "**/*.vue"
        - package.json

Parallel and Matrix Jobs

# Parallel execution
test:
  script: pytest
  parallel: 5

# Matrix builds
test-matrix:
  image: python:${PYTHON_VERSION}
  parallel:
    matrix:
      - PYTHON_VERSION: ['3.8', '3.9', '3.10', '3.11']
        DATABASE: ['postgres', 'mysql']
  script:
    - pip install -r requirements.txt
    - pytest --database=$DATABASE

# Dynamic child pipelines
generate-pipeline:
  stage: build
  script:
    - ./generate-pipeline.sh > pipeline.yml
  artifacts:
    paths:
      - pipeline.yml

child-pipeline:
  stage: deploy
  trigger:
    include:
      - artifact: pipeline.yml
        job: generate-pipeline

Workflow Controls

workflow:
  rules:
    # Don't create pipelines for branch pushes if MR exists
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS'
      when: never
    - if: '$CI_COMMIT_BRANCH'
    # Always run for tags
    - if: '$CI_COMMIT_TAG'

# Pipeline-wide variables based on conditions
variables:
  DEPLOY_ENV:
    value: "staging"
    description: "Deployment environment"

# Per-stage variables
deploy:
  variables:
    KUBERNETES_NAMESPACE: production
  script: kubectl apply -f manifest.yaml

Runners and Executors

Runner Architecture

Execution EnvironmentsRunner ManagerGitLab InstanceGitLab ServerJob QueueGitLab RunnerExecutorShell ExecutorDocker ExecutorKubernetes ExecutorDocker MachineExecution EnvironmentsRunner ManagerGitLab InstanceGitLab ServerJob QueueGitLab RunnerExecutorShell ExecutorDocker ExecutorKubernetes ExecutorDocker Machine

Executor Types

Executor Use Case Isolation Performance
Shell Simple scripts, local builds Low High
Docker Containerised builds Medium Medium
Docker Machine Auto-scaling with VMs High Medium
Kubernetes Cloud-native, scalable High Variable
SSH Remote server execution Medium Medium
VirtualBox/Parallels Full VM isolation High Low

Runner Configuration

# /etc/gitlab-runner/config.toml

concurrent = 10
check_interval = 0

[[runners]]
  name = "docker-runner"
  url = "https://gitlab.com/"
  token = "RUNNER_TOKEN"
  executor = "docker"

  [runners.docker]
    tls_verify = false
    image = "alpine:latest"
    privileged = false
    disable_cache = false
    volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
    shm_size = 0
    pull_policy = "if-not-present"

  [runners.cache]
    Type = "s3"
    Shared = true
    [runners.cache.s3]
      ServerAddress = "s3.amazonaws.com"
      BucketName = "gitlab-runner-cache"
      BucketLocation = "eu-west-1"

[[runners]]
  name = "kubernetes-runner"
  url = "https://gitlab.com/"
  token = "RUNNER_TOKEN"
  executor = "kubernetes"

  [runners.kubernetes]
    host = ""
    namespace = "gitlab-runner"
    privileged = false
    cpu_limit = "2"
    memory_limit = "4Gi"
    service_cpu_limit = "1"
    service_memory_limit = "1Gi"
    helper_cpu_limit = "500m"
    helper_memory_limit = "256Mi"
    poll_interval = 3
    poll_timeout = 180

    [[runners.kubernetes.volumes.empty_dir]]
      name = "docker-certs"
      mount_path = "/certs/client"
      medium = "Memory"

Runner Tags and Selection

# Job with specific runner tags
build-docker:
  tags:
    - docker
    - linux
    - high-cpu
  script:
    - docker build -t myapp .

# Runner configuration in job
test:
  tags:
    - kubernetes
  image:
    name: python:3.11
    entrypoint: [""]
  services:
    - name: postgres:14
      alias: postgres
  script:
    - pytest

Auto-scaling Configuration

[[runners]]
  name = "docker-machine-runner"
  executor = "docker+machine"
  limit = 20

  [runners.machine]
    IdleCount = 2
    IdleTime = 600
    MaxBuilds = 100
    MachineDriver = "amazonec2"
    MachineName = "gitlab-docker-machine-%s"

    [runners.machine.amazonec2]
      access-key = "ACCESS_KEY"
      secret-key = "SECRET_KEY"
      region = "eu-west-1"
      instance-type = "t3.medium"
      vpc-id = "vpc-xxxxx"
      subnet-id = "subnet-xxxxx"
      security-group = "sg-xxxxx"
      use-private-address = true

Caching and Artefacts

Caching Strategy

YesNoYesNoJob StartsCache Exists?Download CacheSkip DownloadRun JobJob CompletesCache Key Changed?Upload New CacheSkip UploadJob DoneYesNoYesNoJob StartsCache Exists?Download CacheSkip DownloadRun JobJob CompletesCache Key Changed?Upload New CacheSkip UploadJob Done

Cache Configuration

# Global cache configuration
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .npm/
  policy: pull-push

# Job-specific cache
build:
  cache:
    key:
      files:
        - package-lock.json
      prefix: npm
    paths:
      - node_modules/
    policy: pull-push
  script:
    - npm install
    - npm run build

# Cache only (don't update)
test:
  cache:
    key:
      files:
        - package-lock.json
      prefix: npm
    paths:
      - node_modules/
    policy: pull
  script:
    - npm test

# Multiple caches
build-java:
  cache:
    - key: maven-${CI_COMMIT_REF_SLUG}
      paths:
        - .m2/repository/
    - key: gradle-${CI_COMMIT_REF_SLUG}
      paths:
        - .gradle/
  script:
    - mvn package

Artefacts Management

# Basic artefact configuration
build:
  script:
    - make build
  artifacts:
    name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
    paths:
      - build/
      - dist/*.jar
    exclude:
      - build/**/*.log
    expire_in: 1 week
    when: on_success

# Advanced artefact handling
test:
  script:
    - npm test
  artifacts:
    paths:
      - coverage/
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
      junit: test-results/junit.xml
    expose_as: 'Test Coverage'
    expire_in: 30 days

# Conditional artefacts
deploy:
  script:
    - ./deploy.sh
  artifacts:
    paths:
      - logs/
    when: on_failure
    expire_in: 1 week

# Download artefacts from previous jobs
integration-test:
  script:
    - ./integration-test.sh
  dependencies:
    - build
    - test
  # Or use needs for DAG
  needs:
    - job: build
      artifacts: true

# Dotenv artefacts for variable passing
build-vars:
  script:
    - echo "BUILD_VERSION=1.2.3" >> build.env
    - echo "DOCKER_TAG=myapp:1.2.3" >> build.env
  artifacts:
    reports:
      dotenv: build.env

deploy:
  script:
    - echo "Deploying $BUILD_VERSION"
    - docker push $DOCKER_TAG
  needs:
    - job: build-vars
      artifacts: true

Cache vs Artefacts

Feature Cache Artefacts
Purpose Speed up builds Pass data between jobs
Reliability Best effort, may be deleted Guaranteed availability
Scope Across pipelines Within pipeline
Storage Runner or external (S3) GitLab instance
Download Automatic Explicit via dependencies/needs
Typical Use Dependencies, build tools Build outputs, reports

Environments and Deployments

Environment Configuration

# Basic environment
deploy-staging:
  stage: deploy
  script:
    - kubectl apply -f k8s/
  environment:
    name: staging
    url: https://staging.example.com
    on_stop: stop-staging
    auto_stop_in: 1 day

stop-staging:
  stage: deploy
  script:
    - kubectl delete -f k8s/
  environment:
    name: staging
    action: stop
  when: manual

# Dynamic environments
deploy-review:
  stage: deploy
  script:
    - ./deploy-review.sh
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_COMMIT_REF_SLUG.review.example.com
    on_stop: stop-review
    auto_stop_in: 3 days
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

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

Deployment Strategies

Rolling Updatev1 PodsReplace 1 PodReplace 2 PodsReplace AllCanary DeploymentYesNoStable 90%Traffic SplitCanary 10%Metrics OK?Increase CanaryRollbackBlue-Green DeploymentBlue v1.0Switch TrafficGreen v1.1All to GreenRolling Updatev1 PodsReplace 1 PodReplace 2 PodsReplace AllCanary DeploymentYesNoStable 90%Traffic SplitCanary 10%Metrics OK?Increase CanaryRollbackBlue-Green DeploymentBlue v1.0Switch TrafficGreen v1.1All to Green

Progressive Delivery

# Canary deployment with incremental rollout
deploy-canary:
  stage: deploy
  script:
    - kubectl apply -f k8s/canary/
  environment:
    name: production/canary
    url: https://example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

deploy-production-25:
  stage: deploy
  script:
    - kubectl patch deployment app -p '{"spec":{"replicas":2}}'
  environment:
    name: production
  when: manual
  needs: [deploy-canary]

deploy-production-50:
  stage: deploy
  script:
    - kubectl patch deployment app -p '{"spec":{"replicas":4}}'
  environment:
    name: production
  when: manual
  needs: [deploy-production-25]

deploy-production-100:
  stage: deploy
  script:
    - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - kubectl delete -f k8s/canary/
  environment:
    name: production
  when: manual
  needs: [deploy-production-50]

# Feature flag deployment
deploy-with-flags:
  stage: deploy
  script:
    - |
      curl -X POST https://api.featureflag.io/v1/features \
        -H "Authorization: Bearer $FF_API_KEY" \
        -d '{
          "name": "new_feature",
          "enabled": true,
          "rollout_percentage": 10
        }'
    - ./deploy.sh
  environment:
    name: production

Environment Protection

# Protected environment (configured in GitLab UI)
# Settings > CI/CD > Environments

deploy-production:
  stage: deploy
  script:
    - ./deploy.sh production
  environment:
    name: production
    url: https://example.com
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
  # Requires:
  # - Allowed to deploy: Maintainer role
  # - Deployment approvals: 2 approvers required

# Deployment with approval
deploy:
  stage: deploy
  script:
    - echo "Deploying to production"
  environment:
    name: production
    deployment_tier: production
  needs:
    - job: build
      artifacts: true
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
      allow_failure: false

Security Scanning and Compliance

Integrated Security Templates

include:
  # Static Application Security Testing
  - template: Security/SAST.gitlab-ci.yml
  # Dependency Scanning
  - template: Security/Dependency-Scanning.gitlab-ci.yml
  # Container Scanning
  - template: Security/Container-Scanning.gitlab-ci.yml
  # Dynamic Application Security Testing
  - template: Security/DAST.gitlab-ci.yml
  # Secret Detection
  - template: Security/Secret-Detection.gitlab-ci.yml
  # Infrastructure as Code Scanning
  - template: Security/SAST-IaC.gitlab-ci.yml
  # Note: the standalone Security/License-Scanning.gitlab-ci.yml template was
  # removed in GitLab 17.0. Licence information is now produced by Dependency
  # Scanning (CycloneDX SBOM); no separate licence-scanning job is needed.

variables:
  # Configure scanners
  SAST_EXCLUDED_PATHS: "spec,test,tests,tmp"
  DS_EXCLUDED_PATHS: "spec,test,tests,tmp"
  SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"

Custom Security Scanning

# Custom SAST configuration
sast:
  variables:
    SAST_CONFIDENCE_LEVEL: 2
    SEARCH_MAX_DEPTH: 20
  before_script:
    - echo "Running custom SAST configuration"

# Trivy container scanning
container-scan:
  stage: test
  image: aquasec/trivy:latest
  variables:
    IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  script:
    - trivy image --exit-code 0 --severity LOW,MEDIUM $IMAGE
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.json

# OWASP Dependency Check
dependency-check:
  stage: test
  image: owasp/dependency-check:latest
  script:
    - >
      /usr/share/dependency-check/bin/dependency-check.sh
      --scan .
      --format JSON
      --format HTML
      --out reports/
      --project "$CI_PROJECT_NAME"
  artifacts:
    paths:
      - reports/
    reports:
      dependency_scanning: reports/dependency-check-report.json

# Secret scanning with gitleaks
secrets-scan:
  stage: test
  image: zricethezav/gitleaks:latest
  script:
    - gitleaks detect --source . --report-format json --report-path gl-secret-detection-report.json
  artifacts:
    reports:
      secret_detection: gl-secret-detection-report.json
  allow_failure: true

Compliance and Policy Enforcement

# Compliance pipeline configuration
include:
  - project: 'compliance/pipelines'
    file: 'compliance-jobs.yml'

# License compliance
# The `license_scanning` artifact report type was removed in GitLab 18.0.
# Licence data is now ingested from a CycloneDX SBOM produced by Dependency
# Scanning; emit a CycloneDX report and register it with the `cyclonedx` type.
license-compliance:
  stage: test
  image: licensefinder/license_finder:latest
  script:
    - license_finder report --format cyclonedx > gl-sbom.cdx.json
  artifacts:
    reports:
      cyclonedx: gl-sbom.cdx.json

# Code quality scanning
code-quality:
  stage: test
  image: docker:stable
  services:
    - docker:dind
  variables:
    DOCKER_DRIVER: overlay2
    CODE_QUALITY_IMAGE: "registry.gitlab.com/gitlab-org/ci-cd/codequality:latest"
  script:
    - |
      docker run --env SOURCE_CODE="$PWD" \
        --volume "$PWD":/code \
        --volume /var/run/docker.sock:/var/run/docker.sock \
        "$CODE_QUALITY_IMAGE" /code
  artifacts:
    reports:
      codequality: gl-code-quality-report.json
    expire_in: 1 week

# Policy enforcement with OPA
policy-check:
  stage: test
  image: openpolicyagent/opa:latest
  script:
    - opa test policies/ -v
    - opa check policies/
  rules:
    - changes:
        - policies/**/*
        - data/**/*

Security Report Integration

# Vulnerability management workflow
security-gate:
  stage: test
  script:
    - |
      # Parse security reports and fail on high/critical issues
      python3 scripts/security-gate.py \
        --sast gl-sast-report.json \
        --dependency gl-dependency-scanning-report.json \
        --container gl-container-scanning-report.json \
        --max-high 0 \
        --max-critical 0
  needs:
    - sast
    - dependency_scanning
    - container_scanning
  artifacts:
    reports:
      metrics: security-metrics.txt

# Merge request security widget
# Automatically displays in GitLab MR interface
merge-request-security:
  stage: test
  inherit:
    default: false
  dependencies:
    - sast
    - dependency_scanning
    - secret_detection
  script:
    - echo "Security reports available in MR widget"
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Advanced Pipeline Patterns

Parent-Child Pipelines

# Parent pipeline
trigger-child:
  stage: deploy
  trigger:
    include: ci/child-pipeline.yml
    strategy: depend
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'

# Multi-project pipeline
trigger-downstream:
  stage: trigger
  trigger:
    project: group/downstream-project
    branch: main
    strategy: depend
  variables:
    UPSTREAM_COMMIT_SHA: $CI_COMMIT_SHA
    UPSTREAM_BRANCH: $CI_COMMIT_BRANCH

DAG Pipelines

# Directed Acyclic Graph for optimised execution
stages:
  - build
  - test
  - deploy

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

build-backend:
  stage: build
  script: mvn package
  artifacts:
    paths:
      - target/*.jar

test-unit:
  stage: test
  needs: [build-backend]
  script: mvn test

test-integration:
  stage: test
  needs: [build-backend, build-frontend]
  script: ./integration-test.sh

test-e2e:
  stage: test
  needs: [build-frontend]
  script: npm run test:e2e

deploy:
  stage: deploy
  needs: [test-unit, test-integration, test-e2e]
  script: ./deploy.sh

Service Containers

# Database service for testing
test-with-db:
  image: node:16
  services:
    - name: postgres:14
      alias: postgres
    - name: redis:7-alpine
      alias: redis
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: testuser
    POSTGRES_PASSWORD: testpass
    POSTGRES_HOST_AUTH_METHOD: trust
    DATABASE_URL: "postgresql://testuser:testpass@postgres:5432/testdb"
    REDIS_URL: "redis://redis:6379"
  script:
    - npm install
    - npm run db:migrate
    - npm test

# Docker-in-Docker for container builds
build-docker:
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_CERTDIR: "/certs"
    DOCKER_TLS_VERIFY: 1
    DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

Quick Reference

Essential Pipeline Keywords

Keyword Purpose Example
stages Define pipeline stages stages: [build, test, deploy]
script Commands to execute script: npm test
image Docker image for job image: node:16
services Service containers services: [postgres:14]
before_script Commands before script before_script: npm install
after_script Commands after script after_script: cleanup.sh
variables Environment variables variables: NODE_ENV: production
cache Cache directories cache: paths: [node_modules/]
artifacts Job outputs artifacts: paths: [dist/]
dependencies Artefact dependencies dependencies: [build]
needs DAG dependencies needs: [build, test]
rules Conditional execution rules: if: $CI_COMMIT_BRANCH
environment Deployment target environment: production
when Job execution timing when: manual
allow_failure Continue on failure allow_failure: true
retry Retry configuration retry: max: 2
timeout Job timeout timeout: 2h
parallel Parallel execution parallel: 5
trigger Trigger child pipeline trigger: include: child.yml
extends Inherit configuration extends: .template
include Include external config include: template: SAST.yml

Predefined Variables

Variable Description
$CI_COMMIT_SHA Commit hash
$CI_COMMIT_REF_NAME Branch or tag name
$CI_COMMIT_REF_SLUG URL-safe ref name
$CI_COMMIT_BRANCH Branch name (not for tags)
$CI_COMMIT_TAG Tag name (only for tags)
$CI_PIPELINE_SOURCE Pipeline trigger source
$CI_PROJECT_DIR Project directory path
$CI_PROJECT_NAME Project name
$CI_PROJECT_PATH Project path with namespace
$CI_REGISTRY Container registry URL
$CI_REGISTRY_IMAGE Container registry image path
$CI_JOB_NAME Current job name
$CI_JOB_STAGE Current stage name
$CI_ENVIRONMENT_NAME Environment name
$CI_ENVIRONMENT_URL Environment URL
$GITLAB_USER_LOGIN User triggering pipeline

Runner Management Commands

# Install GitLab Runner
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt-get install gitlab-runner

# Register runner
sudo gitlab-runner register \
  --url https://gitlab.com/ \
  --registration-token $REGISTRATION_TOKEN \
  --executor docker \
  --docker-image alpine:latest \
  --description "My Runner" \
  --tag-list "docker,linux"

# Runner operations
sudo gitlab-runner start
sudo gitlab-runner stop
sudo gitlab-runner restart
sudo gitlab-runner status
sudo gitlab-runner verify

# Unregister runner
sudo gitlab-runner unregister --url https://gitlab.com/ --token $RUNNER_TOKEN

# Run single job for debugging
sudo gitlab-runner exec docker test-job

Pipeline Debugging

# Debug mode with verbose output
test:
  variables:
    CI_DEBUG_TRACE: "true"
  script:
    - echo "Debug mode enabled"
    - env | sort

# Pipeline troubleshooting job
debug-job:
  script:
    - echo "CI_COMMIT_SHA=$CI_COMMIT_SHA"
    - echo "CI_PIPELINE_SOURCE=$CI_PIPELINE_SOURCE"
    - echo "Runner executor=$CI_RUNNER_EXECUTOR"
    - df -h
    - free -m
    - cat /etc/os-release
  when: manual

Common Issues and Solutions

Pipeline Failures

Issue Cause Solution
Job stuck in pending No available runners with matching tags Check runner tags, verify runner is active
yaml invalid Syntax errors in .gitlab-ci.yml Use GitLab CI Lint tool, validate YAML syntax
Job failed (system failure) Runner infrastructure issue Check runner logs, restart runner service
timeout Job exceeded time limit Increase timeout or optimise job execution
No stages / jobs Empty or invalid pipeline Ensure stages and jobs are defined correctly

Cache and Artefact Problems

# Cache not being used
build:
  cache:
    key:
      files:
        - package-lock.json  # Ensure key file exists
    paths:
      - node_modules/
    policy: pull-push
  script:
    - npm ci  # Use ci instead of install for reproducible builds

# Artefacts not available
test:
  needs:
    - job: build
      artifacts: true  # Explicitly enable artefact download
  script:
    - ls -la dist/  # Verify artefacts exist

# Cache pollution fix
clean-cache:
  script:
    - rm -rf node_modules/
  cache:
    key: npm-${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
    policy: push  # Only upload, don't download
  when: manual

Runner Configuration Issues

# Runner not picking up jobs
# 1. Check runner status
sudo gitlab-runner verify

# 2. Check runner logs
sudo journalctl -u gitlab-runner -f

# 3. Ensure runner is not paused (GitLab UI)
# Settings > CI/CD > Runners

# 4. Check runner tags match job tags
gitlab-runner list

# Docker executor permission issues
# Add gitlab-runner to docker group
sudo usermod -aG docker gitlab-runner
sudo systemctl restart gitlab-runner

# Disk space issues
# Clean up old containers and images
docker system prune -a --volumes

Security Scan Failures

# SAST analyzer failing
sast:
  variables:
    SAST_EXCLUDED_ANALYZERS: "eslint"  # Exclude problematic analyzer
    SAST_ANALYZER_IMAGE_TAG: "3"  # Pin to specific version
  allow_failure: true  # Don't block pipeline during rollout

# Container scanning with custom policies
container_scanning:
  variables:
    CS_SEVERITY_THRESHOLD: "high"  # Only fail on high/critical
    CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    CS_DOCKERFILE_PATH: Dockerfile
  before_script:
    - echo "Scanning $CS_IMAGE"

# Dependency scanning exclusions
dependency_scanning:
  variables:
    DS_EXCLUDED_PATHS: "spec,test,tests,tmp,node_modules"
    DS_JAVA_VERSION: "11"
    DS_REMEDIATE: "false"  # Don't auto-fix vulnerabilities

Environment and Deployment Issues

# Environment not showing in deployments
deploy:
  environment:
    name: production
    url: https://example.com  # URL required for environment to show
    deployment_tier: production  # Helps with environment organisation

# Stuck deployments
stop-environment:
  script:
    - echo "Cleaning up environment"
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  when: manual
  rules:
    - if: '$CI_COMMIT_REF_NAME != "main"'
      when: manual

# Protected environment access issues
# Solution: Grant deployment permissions in GitLab UI
# Settings > CI/CD > Protected Environments
# Add allowed roles/users for deployment

Network and Connectivity

# Docker registry authentication
.docker-login: &docker-login
  before_script:
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
    - docker info

# Proxy configuration
variables:
  HTTP_PROXY: "http://proxy.example.com:8080"
  HTTPS_PROXY: "http://proxy.example.com:8080"
  NO_PROXY: "localhost,127.0.0.1,.example.com"
  http_proxy: "http://proxy.example.com:8080"
  https_proxy: "http://proxy.example.com:8080"
  no_proxy: "localhost,127.0.0.1,.example.com"

# DNS resolution issues
test:
  script:
    - cat /etc/resolv.conf
    - nslookup gitlab.com
    - curl -v https://gitlab.com
  # Configure runner with custom DNS
  # In config.toml: dns = ["8.8.8.8", "8.8.4.4"]

Performance Optimisation

# Reduce pipeline duration with needs
build-frontend:
  stage: build
  script: npm run build
  artifacts:
    paths: [dist/]

build-backend:
  stage: build
  script: mvn package
  artifacts:
    paths: [target/]

# Both tests run in parallel, not sequentially
test-frontend:
  stage: test
  needs: [build-frontend]  # Start as soon as frontend builds
  script: npm test

test-backend:
  stage: test
  needs: [build-backend]  # Start as soon as backend builds
  script: mvn test

# Optimise Docker builds with BuildKit
docker-build:
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_BUILDKIT: 1
    DOCKER_DRIVER: overlay2
  script:
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest --build-arg BUILDKIT_INLINE_CACHE=1 -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .

# Shallow clone for faster checkouts
variables:
  GIT_DEPTH: 10  # Only fetch last 10 commits
  GIT_STRATEGY: fetch  # Or 'clone' for clean state