E-NO Logo
EN FR
GitLab CI/CD common errors 10 Min Read

GitLab CI/CD common errors and fixes with practical examples: practical implementation guide

calendar_today Published: 2026-07-10
update Last Updated: 2026-07-10
analytics SEO Efficiency: 97%
Technical guide illustration for GitLab CI/CD common errors and fixes with practical examples: practical implementation guide.

Intro

GitLab CI/CD is powerful, but small mistakes can stall delivery. This guide focuses on the errors you are most likely to see in real pipelines, why they happen, how to confirm root cause, and safe, copy-pasteable fixes. Keep it practical: reproduce the issue with the smallest example, apply the smallest safe change, then generalize.

What you will get:

  • Clear explanations of common GitLab CI/CD error messages
  • Minimal repros and job snippets you can adapt
  • Safe-by-default fixes you can test locally before rollout

Workflow Overview

Use a repeatable flow that turns noise into signal:

  1. Reproduce and capture context
  • Re-run the failed job with the same commit.
  • Expand all collapsed sections in the job log. Read from the top: runner info, image pull, checkout, before_script, script, after_script, artifact/upload.
  • Note the failing line and the phase it occurred in.
  1. Confirm the smallest repro
  • Comment out non-essential steps.
  • Hard-code variables that affect flow.
  • If a service is involved (Docker, database, Kubernetes), test with a single job and a tiny script.
  1. Apply the smallest safe fix
  • Prefer configuration fixes over scripting workarounds.
  • Add guardrails (rules, paths, or explicit timeouts) to prevent recurrences.
  1. Verify and document
  • Re-run the job, then a full pipeline.
  • Add a short comment in .gitlab-ci.yml near the fix.

Common errors and fixes

Below are frequent failures, their causes, how to check, and safe fixes.

1) Invalid .gitlab-ci.yml or YAML structure

Symptoms

  • Pipeline does not start, or you see: "This GitLab CI configuration is invalid".
  • Job-level messages like: "before_script config should be a string or a list of strings".

Why it happens

  • YAML indentation or list syntax errors.
  • Using a map where a list is expected, or vice versa.

How to diagnose

  • Reduce to a minimal .gitlab-ci.yml with a single echo job to isolate the fault.
  • Check that all lists use dashes and all string lists are simple strings.

Safe fix example

stages: [lint, test]

variables:
  NODE_ENV: 'test'

lint:
  stage: lint
  image: node:20-alpine
  script:
    - node --version
    - npm ci
    - npm run lint

test:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm test

Tips

  • Avoid mixing tabs and spaces.
  • Keep script as a list of strings, not a single multiline string.

2) Pipeline has no jobs

Symptoms

  • Pipeline page shows: "There are no stages/jobs for this pipeline." or nothing runs for your branch/MR.

Why it happens

  • rules or only/except exclude your ref.
  • workflow: rules prevents pipeline creation.

How to diagnose

  • Inspect the branch or MR event and environment variables.
  • Temporarily add a catch-all job to verify matching.

Safe fix example using rules with a fallback

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    - when: always  # ensure a pipeline is created when in doubt

build:
  stage: build
  image: alpine:3.20
  rules:
    - changes:
        - src/**
      when: on_success
    - when: manual  # visible fallback if no changes matched
  script:
    - echo "Building..."

3) Job stuck or pending: no runners

Symptoms

  • Job status is stuck in pending with: "This job is stuck because the project does not have any runners online assigned to it."

Why it happens

  • No runner online, or runner tags do not match the job.
  • Runner is not allowed to run on this project or protected branch.

How to diagnose

  • Check job tags vs runner tags in Settings > CI/CD > Runners.
  • Confirm runner is online and not paused.

Safe fix example: align tags or remove tag requirement

# If your runner has tag 'docker', the job must include it
build-image:
  tags: ['docker']
  image: docker:24
  services: ['docker:24-dind']
  variables:
    DOCKER_HOST: 'tcp://docker:2375'
    DOCKER_TLS_CERTDIR: ''
  script:
    - docker version
    - docker build -t demo: ci .

Tips

  • For protected branches, ensure the runner is marked as protected or the job is not restricted to protected refs.

4) Docker-in-Docker: cannot connect to Docker daemon

Symptoms

  • "Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?"

Why it happens

  • Docker service not started, wrong host, or TLS misconfigured.

How to diagnose

  • Print env and try docker info.
  • Ensure the service alias matches the DOCKER_HOST.

Safe fix example

image: docker:24
services:
  - name: docker:24-dind
    command: ['--mtu=1460']  # optional, resolves some network issues

variables:
  DOCKER_HOST: 'tcp://docker:2375'
  DOCKER_TLS_CERTDIR: ''
  DOCKER_DRIVER: 'overlay2'

build:
  stage: build
  script:
    - docker version
    - docker build -t $CI_REGISTRY_IMAGE:ci-$CI_COMMIT_SHORT_SHA .
    - echo 'Build complete'

Tips

  • Disable TLS for the dind service inside CI and keep the job network isolated.
  • If you use a shared runner, prefer small base images to reduce pull time.

5) Artifacts upload: file not found

Symptoms

  • "Uploading artifacts... WARNING: No files to upload" or "Could not find a file matching ..."

Why it happens

  • Path is wrong, generated files go to a different directory, or the job failed before producing them.

How to diagnose

  • Add ls -R to confirm paths.
  • Ensure the working directory is $CI_PROJECT_DIR.

Safe fix example

test:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm test -- --reporter=junit --reporter-options output=reports/junit.xml
    - mkdir -p dist && echo ok > dist/health.txt
  artifacts:
    when: always
    paths:
      - reports/junit.xml
      - dist/
    expire_in: '7 days'

6) Cache does not persist between jobs

Symptoms

  • Dependencies re-install every time; cache hit rate appears 0%.

Why it happens

  • Cache key too broad, too narrow, or changes every run.

How to diagnose

  • Print cache key and compare across jobs.

Safe fix example: content-based key with fallback

cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/
  policy: pull-push

install:
  stage: .pre
  image: node:20-alpine
  script:
    - npm ci --cache .npm --prefer-offline
  artifacts:
    paths: [.npm]
    expire_in: '1 day'

7) Git submodules fail to fetch

Symptoms

  • "fatal: could not read Username for 'https://gitlab.com': No such device or address"
  • "fatal: repository not found" for submodules.

Why it happens

  • Submodules use HTTPS without credentials, or point to a private project the token cannot access.

How to diagnose

  • Print .gitmodules and verify URLs.
  • Check that submodule projects grant access to CI_JOB_TOKEN if using relative paths.

Safe fix example: use CI_JOB_TOKEN for HTTPS

variables:
  GIT_SUBMODULE_STRATEGY: 'recursive'
  GIT_SUBMODULE_UPDATE_FLAGS: '--jobs 4'

before_script:
  - git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/".insteadOf "https://gitlab.com/"

submodule_job:
  stage: test
  image: alpine:3.20
  script:
    - git submodule sync --recursive
    - git submodule update --init --recursive
    - echo 'Submodules ready'

Tips

  • Prefer relative submodule URLs within the same GitLab instance to allow CI_JOB_TOKEN auth.

8) Protected variables missing in non-protected branches

Symptoms

  • docker login or cloud CLI fails with 403 or auth errors only on feature branches.

Why it happens

  • Variables are marked protected and only exposed on protected refs.

How to diagnose

  • Compare variable protection flags with the branch protection.

Safe fix example: restrict the job to protected refs when needed

docker_push:
  stage: deploy
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
    - if: '$CI_COMMIT_TAG'
    - when: never
  script:
    - echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin "$CI_REGISTRY"
    - docker push $CI_REGISTRY_IMAGE:release

9) Kubernetes: kubectl or Helm errors

Symptoms

  • kubectl: "unable to recognize: no matches for kind ..."
  • Helm: "UPGRADE FAILED: another operation is in progress" or timeouts.

Why it happens

  • Wrong apiVersion or missing CRDs; missing permissions; stale Helm locks; kubeconfig not loaded.

How to diagnose

  • Print kubectl version --client and server versions.
  • kubectl auth can-i ... to check permissions.
  • helm list and helm history to inspect release state.

Safe fix example: pass kubeconfig via a variable and use atomic Helm upgrades

deploy:
  stage: deploy
  image: alpine/helm:3.14.4
  variables:
    KUBECONFIG: '/builds/$CI_PROJECT_PATH/.kube/config'
  before_script:
    - mkdir -p $(dirname "$KUBECONFIG")
    - echo "$KUBECONFIG_DATA" | base64 -d > "$KUBECONFIG"
    - kubectl version --client
    - kubectl config get-contexts
  script:
    - kubectl apply -f k8s/ns.yaml
    - helm upgrade --install app charts/app \
        --namespace app \
        --create-namespace \
        --atomic --wait --timeout 5m

Tips

  • Keep kubectl/Helm client versions compatible with your cluster.
  • Use --atomic and --wait to fail fast and roll back on errors.

10) Job exceeded time limit

Symptoms

  • "script exceeded time limit" or job canceled after a fixed duration.

Why it happens

  • Default timeout too low; slow image pulls; cache misses; long test suites.

How to diagnose

  • Check job duration breakdown in logs.
  • Note image pull times and network-related waits.

Safe fix example: set a per-job timeout and optimize pulls

long_tests:
  stage: test
  image: python:3.12-slim
  timeout: 45m
  script:
    - pip install -r requirements.txt
    - pytest -m 'not slow' -n auto --dist loadscope

Tips

  • Use smaller base images and enable registry-side caching where available.
  • Split long jobs into parallel shards and merge reports as artifacts.

Local Pilot Plan

Goal: validate your fixes and patterns with the smallest useful pipeline you can inspect locally before rolling out to all projects.

Scope

  • One repo, one shared or project-specific runner.
  • Stages: validate, test, build, package.
  • Docker-in-Docker build, basic caching, and one artifact.

Success criteria

  • Pipeline passes in under 10 minutes on a clean runner.
  • Cache hit on second run for dependencies.
  • No pending jobs; no artifact upload warnings.

Reference pilot pipeline

stages: [validate, test, build, package]

default:
  image: docker:24
  services: ['docker:24-dind']
  variables:
    DOCKER_HOST: 'tcp://docker:2375'
    DOCKER_TLS_CERTDIR: ''

validate: yaml:
  stage: validate
  image: alpine:3.20
  script:
    - echo 'Validating YAML layout'
    - grep -q 'stages:' .gitlab-ci.yml

unit_tests:
  stage: test
  image: node:20-alpine
  cache:
    key:
      files: [package-lock.json]
    paths: [node_modules/]
    policy: pull-push
  script:
    - npm ci
    - npm test -- --reporter=junit --reporter-options output=reports/junit.xml
  artifacts:
    when: always
    paths: [reports/junit.xml]
    expire_in: '7 days'

build_image:
  stage: build
  tags: ['docker']  # ensure this matches your runner tag
  script:
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
    - docker build -t $CI_REGISTRY_IMAGE:ci-$CI_COMMIT_SHORT_SHA .
    - docker image ls | head -n 5

package_sbom:
  stage: package
  image: alpine:3.20
  script:
    - mkdir -p out && echo '{"sbom":"demo"}' > out/sbom.json
  artifacts:
    paths: [out/sbom.json]
    expire_in: '7 days'

How to run and inspect

  • Push a feature branch and open a small MR.
  • Confirm runner tags match and that the cache hits on the second run.
  • Intentionally break a path (e.g., artifacts) to see the error, then revert.

Rollout

  • Copy the patterns into other repos, adjusting images, tags, and cache keys.
  • Keep the pilot jobs as templates you can include across projects.

Conclusion

You now have a practical set of patterns to recognize and fix the most common GitLab CI/CD errors:

  • Validate YAML early and keep scripts simple.
  • Ensure runner availability and tag alignment.
  • Configure Docker-in-Docker predictably.
  • Produce artifacts reliably and cache what is expensive to rebuild.
  • Authenticate submodules and registries correctly.
  • Deploy to Kubernetes with explicit kubeconfig, waits, and atomic rollbacks.
  • Control timeouts and parallelize long tasks.

Next steps

  • Implement the pilot pipeline and record baseline metrics.
  • Fix the most painful error in your environment first, then expand.
  • Keep changes small, observable, and easy to revert.

Article Quality Score

Reader usefulness 97%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL