Skip to main content

validation

GitLab CI/CD Pipeline Validation

Test and validate pipelines locally before pushing to prevent wasted CI minutes and broken pipelines.

Table of Contents

Why Validate Locally

Cost of Pipeline Errors

Without validation:

Push  Pipeline fails (syntax error)  5 CI minutes wasted
Fix  Push  Pipeline fails (wrong variable)  5 CI minutes wasted
Fix  Push  Pipeline fails (missing dependency)  5 CI minutes wasted
Fix  Push  Pipeline succeeds  Total: 20 CI minutes wasted

With local validation:

Validate locally  Fix errors  Push  Pipeline succeeds  5 CI minutes used
Savings: 15 CI minutes (75% reduction)

Additional Benefits

  • Faster feedback: Seconds vs minutes
  • Fewer failed pipelines: Better success rate metrics
  • Less noise: Don't spam team with broken pipeline notifications
  • Confidence: Know pipeline will work before pushing

gitlab-ci-local

What is gitlab-ci-local?

A tool to run GitLab CI/CD pipelines locally using Docker or shell executors.

Repository: https://github.com/firecow/gitlab-ci-local

Key features:

  • Run entire pipelines or individual jobs
  • Test with real Docker containers
  • Support for variables, artifacts, cache
  • Interactive debugging mode
  • Fast iteration without pushing

Source: gitlab-ci-local GitHub

Installation

macOS:

brew install gitlab-ci-local

Debian/Ubuntu:

# Add repository curl -s https://gitlab-ci-local-ppa.firecow.dk/public.key | sudo apt-key add - echo "deb https://gitlab-ci-local-ppa.firecow.dk/debian stable main" | sudo tee /etc/apt/sources.list.d/gitlab-ci-local.list # Install sudo apt update sudo apt install gitlab-ci-local

npm (all platforms):

npm install -g gitlab-ci-local

Source: BrowserStack Guide

Basic Usage

Run entire pipeline:

gitlab-ci-local

List available jobs:

gitlab-ci-local --list

Output:

name          description  stage  when        allow_failure  needs
lint          Lint code    test   on_success  false          []
build         Build app    build  on_success  false          [lint]
test          Run tests    test   on_success  false          [build]
deploy        Deploy       deploy manual      false          [test]

Run specific job:

gitlab-ci-local build

Run job with dependencies:

gitlab-ci-local --needs test # Runs lint build test

Advanced Features

Interactive Mode (for debugging):

gitlab-ci-local --shell-isolation test

Pauses execution and drops into a shell where you can inspect state, run commands manually, and debug issues.

Dry Run (show what would run):

gitlab-ci-local --preview

Set Variables:

gitlab-ci-local --variable ENVIRONMENT=staging deploy

Mount Volumes:

gitlab-ci-local --volume /host/path:/container/path

CSV Output (for scripting):

gitlab-ci-local --list-csv

Source: gitlab-ci-local Documentation

Example Workflow

# 1. Make changes to .gitlab-ci.yml vim .gitlab-ci.yml # 2. Validate syntax gitlab-ci-local --preview # 3. Test specific job gitlab-ci-local build # 4. If passing, run full pipeline gitlab-ci-local # 5. Only push when local tests pass git add .gitlab-ci.yml git commit -m "ci: update build configuration" git push

Time saved: 5-10 iterations 5 minutes each = 25-50 CI minutes saved per pipeline change.

Limitations

  • No GitLab-hosted features: Can't test GitLab-specific services (Auto DevOps, SAST, etc.)
  • Environment differences: Local Docker vs GitLab runners may differ
  • Secrets: Must provide secrets locally (don't use production secrets!)
  • Resource limits: Local machine resources != runner resources

Recommendation: Use for quick validation, still verify on real runners for critical changes.

Source: Testing GitLab Pipeline Locally

GitLab CI Lint API

API-based Validation

GitLab provides a /api/v4/ci/lint endpoint to validate .gitlab-ci.yml syntax.

Endpoint: POST https://gitlab.com/api/v4/ci/lint

Request:

curl --header "Content-Type: application/json" \ --data '{"content":"stages:\n - build\n\nbuild-job:\n stage: build\n script:\n - echo 'Building'"}' \ https://gitlab.com/api/v4/ci/lint

Response (valid):

{ "valid": true, "merged_yaml": "...", "errors": [], "warnings": [] }

Response (invalid):

{ "valid": false, "errors": ["Invalid syntax at line 5: unexpected '}'"], "warnings": [] }

Using the Web UI

Location: Project CI/CD Editor Validate

  1. Paste your .gitlab-ci.yml content
  2. Click "Validate"
  3. See errors/warnings immediately

Use case: Quick syntax checks without CLI tools.

CI Lint CLI Tool

Script (save as gitlab-ci-lint):

#!/bin/bash # Validate .gitlab-ci.yml using GitLab API if [ ! -f .gitlab-ci.yml ]; then echo "Error: .gitlab-ci.yml not found" exit 1 fi CONTENT=$(cat .gitlab-ci.yml | jq -sR '.') RESPONSE=$(curl -s --header "Content-Type: application/json" \ --data "{\"content\":$CONTENT}" \ https://gitlab.com/api/v4/ci/lint) VALID=$(echo "$RESPONSE" | jq -r '.valid') if [ "$VALID" = "true" ]; then echo " .gitlab-ci.yml is valid" exit 0 else echo " .gitlab-ci.yml is invalid:" echo "$RESPONSE" | jq -r '.errors[]' exit 1 fi

Usage:

chmod +x gitlab-ci-lint ./gitlab-ci-lint

Source: GitLab CI Lint Documentation

Pre-commit Hooks

What are Pre-commit Hooks?

Git hooks that run automatically before git commit executes, allowing validation to catch errors before code is committed.

Framework: https://pre-commit.com/

Installation

# Install pre-commit pip install pre-commit # Or via Homebrew brew install pre-commit

Configuration

Create .pre-commit-config.yaml:

repos: # Validate GitLab CI syntax - repo: https://github.com/emmeowzing/gitlabci-lint-pre-commit-hook rev: v1.3.0 hooks: - id: gitlabci-lint args: ['--server', 'https://gitlab.com'] # Alternative: validate-gitlab-ci - repo: https://github.com/dcs3spp/validate-gitlab-ci rev: v1.1.0 hooks: - id: validate-gitlab-ci args: ['--api-endpoint', 'https://gitlab.com/api/v4'] # Additional checks - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - id: check-yaml # Validate YAML syntax - id: end-of-file-fixer - id: trailing-whitespace

Install hooks:

pre-commit install

Sources:

Usage

Automatic (on every commit):

git add .gitlab-ci.yml git commit -m "ci: update pipeline" # Pre-commit hooks run automatically # If validation fails, commit is blocked

Manual (test before commit):

pre-commit run --all-files

Output (on success):

validate-gitlab-ci..................................................Passed
check-yaml..........................................................Passed
end-of-file-fixer...................................................Passed
trailing-whitespace.................................................Passed

Output (on failure):

validate-gitlab-ci..................................................Failed
- hook id: validate-gitlab-ci
- exit code: 1

Error: Invalid GitLab CI syntax at line 10: unexpected token

Cost savings: Block invalid configs before they reach GitLab runners = 100% CI minute savings for that error.

Project-Specific Configuration

For projects with custom GitLab instances:

repos: - repo: https://github.com/dcs3spp/validate-gitlab-ci rev: v1.1.0 hooks: - id: validate-gitlab-ci args: - '--api-endpoint' - 'https://gitlab.company.com/api/v4' - '--project-id' - '12345' # Your project ID

CI Integration

Run pre-commit in CI to enforce validation:

pre-commit: stage: validate image: python:3.11 before_script: - pip install pre-commit script: - pre-commit run --all-files

Source: Using pre-commit in GitLab Pipelines

Schema Validation

JSON Schema for .gitlab-ci.yml

GitLab provides a JSON schema for .gitlab-ci.yml that editors can use for validation and autocompletion.

Schema URL: https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json

VS Code Integration

Install Extension: GitLab Workflow (official)

Or configure manually in .vscode/settings.json:

{ "yaml.schemas": { "https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json": ".gitlab-ci.yml" } }

Features:

  • Real-time syntax validation
  • Autocompletion for keywords
  • Hover documentation
  • Error highlighting

Source: GitLab VS Code Extension

IntelliJ / PyCharm Integration

Install Plugin: GitLab Plugin

Configure:

  1. Settings Languages & Frameworks YAML
  2. Add schema mapping:
    • URL: https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json
    • File pattern: .gitlab-ci.yml

CLI Validation with yq

Install yq:

brew install yq # macOS

Validate YAML syntax:

yq eval '.gitlab-ci.yml' > /dev/null # Exit code 0 = valid YAML # Exit code 1 = syntax error

Check for required fields:

# Check if stages are defined yq eval '.stages' .gitlab-ci.yml # Check if job has script yq eval '.build.script' .gitlab-ci.yml

Testing Strategies

Staged Testing Approach

Level 1: Syntax Validation (instant)

yq eval .gitlab-ci.yml > /dev/null

Level 2: Schema Validation (seconds)

pre-commit run gitlabci-lint

Level 3: Local Execution (minutes)

gitlab-ci-local build

Level 4: CI Execution (minutes, on real runners)

git push

Recommendation: Run Levels 1-3 locally, only push when all pass.

Testing Pipeline Changes

1. Create a Test Branch:

git checkout -b test/pipeline-changes

2. Make Changes:

vim .gitlab-ci.yml

3. Validate Locally:

gitlab-ci-local --preview gitlab-ci-local

4. Push to Test Branch:

git push -u origin test/pipeline-changes

5. Verify on Real Runners:

  • Check GitLab UI for pipeline status
  • Review job logs for unexpected behavior

6. Merge to Main:

git checkout main git merge test/pipeline-changes git push

Testing Variables and Secrets

Local Testing (use dummy values):

# Create .gitlab-ci-local-variables.yml cat > .gitlab-ci-local-variables.yml <<EOF DEPLOY_TOKEN: dummy-token-for-testing DATABASE_URL: postgresql://localhost/test EOF # Run with local variables gitlab-ci-local

Security: Never use production secrets for local testing!

Testing with Different Conditions

Test MR pipeline:

gitlab-ci-local --variable CI_PIPELINE_SOURCE=merge_request_event

Test specific branch:

gitlab-ci-local --variable CI_COMMIT_BRANCH=main

Test draft MR behavior:

gitlab-ci-local --variable CI_MERGE_REQUEST_TITLE="Draft: WIP feature"

Best Practices

1. Always Validate Before Pushing

Workflow:

# 1. Edit pipeline vim .gitlab-ci.yml # 2. Syntax check yq eval .gitlab-ci.yml > /dev/null # 3. Lint gitlab-ci-lint # Custom script or pre-commit # 4. Test locally (if available) gitlab-ci-local build # 5. Push git add .gitlab-ci.yml git commit -m "ci: update build step" git push

2. Use Pre-commit Hooks

Setup once, benefit forever:

pip install pre-commit pre-commit install # Now every commit validates automatically

3. Test Incrementally

Don't test the entire pipeline at once. Test jobs individually:

# Test just the build job gitlab-ci-local build # If that works, test build + test gitlab-ci-local --needs test # Finally, test entire pipeline gitlab-ci-local

4. Maintain a Test Project

Create a sandbox project for experimenting with pipeline changes:

  1. Clone production project config
  2. Test changes in sandbox
  3. Apply to production only when validated

Cost savings: Test complex changes in sandbox before affecting production pipelines.

5. Document Pipeline Behavior

Add comments explaining complex logic:

# Only run on MRs, skip on draft test: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^Draft:/ when: on_success - when: never script: npm test

6. Version Control for Pipeline Configs

Use branches for pipeline changes:

git checkout -b ci/add-caching # Make changes git commit -m "ci: add dependency caching" git push # Create MR, review pipeline results # Merge only if successful

7. Monitor Pipeline Changes

After pushing pipeline changes, actively monitor:

  • Pipeline success rate
  • Job duration changes
  • CI minute consumption
  • Error patterns

Use CI/CD Analytics: Analytics CI/CD Analytics

Common Validation Errors

Syntax Errors

Error:

Invalid syntax at line 15: unexpected token '}'

Fix: Check YAML syntax (indentation, colons, quotes)

Prevention: Use editor with YAML schema validation

Undefined Variables

Error:

undefined variable: $DEPLOY_TOKEN

Fix: Define in GitLab project settings or .gitlab-ci.yml

Local testing: Provide in .gitlab-ci-local-variables.yml

Missing Dependencies

Error:

needs: job 'build' not found

Fix: Ensure referenced job exists and is not conditionally skipped

Validation:

gitlab-ci-local --list # Check if 'build' is listed

Invalid Rules

Error:

rules: when: invalid value 'maybe'

Fix: Use valid values (on_success, manual, always, never, delayed)

Prevention: Use schema validation in editor

Troubleshooting

gitlab-ci-local Issues

Issue: "Docker not found"

Solution: Ensure Docker is installed and running:

docker ps

Issue: "Job failed with unknown error"

Solution: Add --debug flag for verbose output:

gitlab-ci-local --debug build

Issue: "Cache not working locally"

Solution: gitlab-ci-local cache behavior differs from GitLab. Use volumes for testing:

gitlab-ci-local --volume $PWD/node_modules:/app/node_modules

Pre-commit Hook Issues

Issue: "Pre-commit hook not running"

Solution: Verify installation:

pre-commit install # Re-run installation ls -la .git/hooks/pre-commit # Check hook file exists

Issue: "Hook failing on valid config"

Solution: Update hook version:

pre-commit autoupdate

API Validation Issues

Issue: "401 Unauthorized"

Solution: For private projects, provide API token:

curl --header "PRIVATE-TOKEN: your-token" \ --header "Content-Type: application/json" \ --data "{\"content\":...}" \ https://gitlab.com/api/v4/ci/lint

Summary Checklist

Essential Validation Steps

  • Install gitlab-ci-local for local testing
  • Set up pre-commit hooks for automatic validation
  • Configure editor with GitLab CI schema
  • Create validation script for manual checks
  • Document pipeline testing process for team

Before Every Push

  • Validate YAML syntax (yq eval)
  • Run pre-commit hooks (pre-commit run)
  • Test locally if possible (gitlab-ci-local)
  • Review pipeline logic for common pitfalls
  • Commit with descriptive message

After Pushing

  • Monitor pipeline execution
  • Review job logs for warnings
  • Check CI minute consumption
  • Verify expected behavior (caching, artifacts, etc.)

Additional Resources


Last Updated: 2026-01-08 Priority: HIGH - Prevent wasted minutes from failed pipelines