GitLab CI/CD Validator
Validates .gitlab-ci.yml syntax, detects security misconfigurations in job definitions, checks for deprecated keywords, ensures proper stage ordering, and audits pipeline configurations for best practices. Use when working with .gitlab-ci.yml files, validating GitLab CI/CD pipeline syntax, debugging configuration errors, checking for hardcoded secrets or credentials in pipeline jobs, optimizing pipeline performance with DAG or cache, or performing security audits on GitLab CI/CD configurations.
npx skills add github:pantheon-org/tekhne --skill ci-cd--gitlab-ci--validatorValidates, lints, tests, and secures GitLab CI/CD pipeline configurations (.gitlab-ci.yml files) across three layers: syntax/schema validation, best practices analysis, and security scanning.
Philosophy
- Validate in layer order: syntax first, then best practices, then security. A syntax error can hide later findings.
- Treat security findings as blockers and best-practice findings as guidance that tightens over time.
- Prefer fast static checks before slower local execution with
gitlab-ci-local. - Validate everything the pipeline pulls in, not just the entry-point file.
When to Use
- Reviewing a
.gitlab-ci.ymlchange before merging. - Debugging a pipeline that fails at queue time with a configuration error.
- Auditing jobs for hardcoded secrets,
curl | bashpatterns or SSL bypasses. - Checking
include:entries, components andneedsgraphs for problems. - Adding a validation job to a GitLab CI pipeline.
When Not to Use
- Running the pipeline on GitLab runners: this skill checks configuration statically.
- Writing a new pipeline from scratch: use the GitLab CI generator skill instead.
- Validating Azure Pipelines or GitHub Actions files: use the matching validator skill.
- Diagnosing application test failures inside a job: the validator only reads the YAML.
Core Validation Workflow
1. Syntax Validation (Required first)
bash scripts/validate_gitlab_ci.sh --syntax-only .gitlab-ci.yml
Checks: YAML structure, GitLab CI schema compliance, job definitions, stage references, dependency graphs (needs/dependencies/extends), include configurations (component, project, remote, local, template), circular dependency detection, and GitLab limits (500 jobs max, 255-char job names, 50 max needs, 100 max components).
Action: Fix all syntax errors before proceeding.
2. Best Practices Review (Recommended)
bash scripts/validate_gitlab_ci.sh --best-practices .gitlab-ci.yml
Checks: Cache usage for dependency installation, artifact expiration settings, DAG optimization with needs, parallel execution opportunities, Docker image version pinning, deprecated only/except → rules migration, missing timeouts and retries, resource group usage.
Action: Review suggestions and apply relevant optimizations.
3. Security Audit (Required)
bash scripts/validate_gitlab_ci.sh --security-only .gitlab-ci.yml
Checks: Hardcoded secrets and credentials, component security (version pinning, trusted sources), remote include integrity, insecure script patterns (curl | bash, eval), SSL/TLS verification bypasses, dangerous file permissions (chmod 777), overly broad artifact paths, variable masking, path traversal in local includes.
Action: Fix all critical and high-severity issues immediately.
4. Local Pipeline Testing (Optional)
# Install gitlab-ci-local first (requires Docker and Node.js)
bash scripts/install_tools.sh
# Test pipeline locally
gitlab-ci-local
# Or via the validator script
bash scripts/validate_gitlab_ci.sh --test-only .gitlab-ci.yml
Simulates local pipeline execution to test job ordering, dependencies, and environment setup. Requires Docker and gitlab-ci-local.
5. Complete Validation
# Full validation (all three layers)
bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml
# Strict mode (fail on warnings)
bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml --strict
Usage
Validation Options
bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml --syntax-only
bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml --best-practices
bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml --security-only
bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml --no-best-practices
bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml --no-security
bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml --strict
Individual Validators
python3 scripts/validate_syntax.py .gitlab-ci.yml
python3 scripts/check_best_practices.py .gitlab-ci.yml
python3 scripts/check_security.py .gitlab-ci.yml
Output Example
════════════════════════════════════════════════════════════════════════════════
Validation Summary
════════════════════════════════════════════════════════════════════════════════
Syntax Validation: PASSED
Best Practices: WARNINGS
Security Scan: PASSED
✓ All validation checks passed
CI/CD Integration
stages:
- validate
validate_pipeline:
stage: validate
script:
- pip3 install PyYAML
- bash scripts/validate_gitlab_ci.sh .gitlab-ci.yml --strict
Adding Custom Validation Rules
Add custom rules directly to the relevant script:
# In check_best_practices.py
def _check_custom_rule(self):
"""Check for custom organization rule"""
for job_name, job in self.config.items():
if not self._is_job(job_name):
continue
if 'tags' not in job:
self.issues.append(BestPracticeIssue(
'warning',
self._get_line(job_name),
f"Job '{job_name}' should specify runner tags",
'custom-missing-tags',
"Add 'tags' to select appropriate runners"
))
- Syntax rules:
scripts/validate_syntax.py - Best practice rules:
scripts/check_best_practices.py - Security rules:
scripts/check_security.py
Requirements
- Python 3.10+
- PyYAML:
pip3 install PyYAML - Bash: For the orchestrator script
Anti-Patterns
NEVER skip syntax validation before running gitlab-ci-local
WHY: Local execution without pre-validation produces obscure runtime errors; resolve schema and syntax errors first to get actionable output.
BAD: Run gitlab-ci-local on an untested config and debug runtime failures.
GOOD: Run bash scripts/validate_gitlab_ci.sh --syntax-only .gitlab-ci.yml first; proceed to local testing only after syntax passes.
NEVER ignore only/except deprecation warnings
WHY: Pipelines using deprecated keywords will break when GitLab removes them; treat these as errors, not warnings.
BAD: Leave only: [main] after the validator flags it as deprecated.
GOOD: Migrate to rules: syntax during the same fix session.
NEVER validate the pipeline file without also checking all referenced include: targets
WHY: A valid base file with an invalid include template causes mysterious pipeline failures at queue time.
BAD: Validate only .gitlab-ci.yml and skip templates/*.yml includes.
GOOD: Validate all local include files as well; the validator checks include:local paths automatically.
NEVER run --strict as the first validation step on an unfamiliar pipeline
WHY: Strict mode fails on warnings and produces noise that obscures real errors; establish a baseline first.
BAD: Run --strict on an inherited pipeline and skip the output because it has 50 warnings.
GOOD: Run without --strict first; fix errors and critical warnings, then enable strict mode as a CI gate.
NEVER merge a pipeline with hardcoded secrets in job definitions
WHY: The security layer flags hardcoded credentials and tokens in scripts and variables (for example [variable-hardcoded-secret], [api-key]). Committed secrets persist in history even after removal.
BAD: variables: { API_KEY: "abc123" } in .gitlab-ci.yml.
GOOD: Define the value as a masked, protected CI/CD variable in project settings and reference $API_KEY.
NEVER pipe a remote download straight into a shell
WHY: The scan reports [curl-pipe-bash] and [wget-pipe-bash]. The job runs whatever the remote host serves at that moment.
BAD: script: - curl -s https://example.com/install.sh | bash.
GOOD: Use a pinned image that already contains the tool, or download, verify and then run the file.
NEVER use unpinned images or latest tags
WHY: The best-practices layer reports [image-latest-tag] and [image-no-version]. A floating tag means the same commit can build differently on different days.
BAD: image: node:latest.
GOOD: image: node:20.11-alpine, or a digest.
NEVER include components or project files without a pinned version
WHY: The validator reports [include-component-no-version], [include-component-latest-version] and [include-project-unpinned]. An upstream change to the included file alters your pipeline with no change in your repository.
BAD: include: - project: group/ci-templates with no ref.
GOOD: Pin ref: to a tag or commit, and pin component versions to a semantic version.
NEVER leave artifacts without an expiry
WHY: The best-practices layer reports [artifact-no-expiration]. Artifacts with no expiry accumulate and consume storage.
BAD: artifacts: { paths: [dist/] } with no expire_in.
GOOD: Add expire_in: 1 week (or a value that fits the job).
NEVER disable SSL verification in job scripts
WHY: The security layer flags verification bypasses ([insecure-ssl], [skip-verification]). It removes protection against tampering with downloaded content.
BAD: script: - git config --global http.sslVerify false.
GOOD: Fix the certificate chain or install the corporate CA in the job image.
References
references/gitlab-ci-reference.md: Complete GitLab CI/CD YAML syntax referencereferences/best-practices.md: Detailed best practices guidereferences/common-issues.md: Common issues and solutionsscripts/: rule definitions live invalidate_syntax.py,check_best_practices.pyandcheck_security.pyassets/basic-pipeline.gitlab-ci.yml: Simple three-stage pipelineassets/docker-build.gitlab-ci.yml: Docker build and push workflowassets/multi-stage.gitlab-ci.yml: Multi-stage pipeline with DAGassets/complex-workflow.gitlab-ci.yml: Advanced workflow with all featuresassets/component-pipeline.gitlab-ci.yml: GitLab 17.0+ pipeline using CI/CD components
# Test with examples
bash scripts/validate_gitlab_ci.sh assets/basic-pipeline.gitlab-ci.yml
bash scripts/validate_gitlab_ci.sh assets/component-pipeline.gitlab-ci.yml
Fetching Latest Documentation
When encountering custom GitLab features or version-specific requirements, this skill can:
- Use Context7 MCP to fetch version-aware GitLab documentation
- Use WebSearch to find latest GitLab CI/CD documentation
- Use WebFetch to retrieve specific pages from docs.gitlab.com
Note: This skill validates GitLab CI/CD configurations but does not execute pipelines. Use GitLab’s CI Lint tool or gitlab-ci-local for testing actual pipeline execution.
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Runtime validation via eval scenarios proving the skill actually changes agent behaviour. Requires evals/ directory, instructions.json, ≥3 scenarios, and ≥80% instruction coverage in summary.json.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
Expert-only knowledge the AI couldn't derive on its own — domain-specific patterns, production gotchas, decision frameworks, and non-obvious tool behaviour. High scores mean <5% redundancy with what the model already knows.
A clear philosophy plus numbered step-by-step workflows with explicit entry/exit points. Should answer: what is the core principle, how to execute it, and when (and when not) to apply it.
Explicit NEVER/WHY/consequence triplets with side-by-side bad/good examples. Generic warnings score low — concrete failure modes with real impact (security, correctness, performance) score high.
Proper frontmatter, single-task focus, activation keywords, and cross-harness portability. Skills must work across 40+ agentic harnesses without hardcoded paths or agent-specific references.
SKILL.md as a <100-line navigation hub pointing to focused references/ files. Content should be lazy-loaded on demand, not front-loaded. Includes actionable 'When to Use' conditions per reference.
Appropriate rigidity for the skill type: strict rules for critical/security skills, balanced steps for process skills, flexible options for tool skills. Mismatched calibration (too rigid or too loose) reduces score.
Rich activation keywords and trigger scenarios in the description so the skill fires when needed. Best descriptions read like an exhaustive list of the tasks and concepts that should invoke this skill.
Copy-paste-ready code, complete runnable examples, real file paths and commands. Pseudocode and abstract descriptions score low — concrete, executable content scores high.
No eval scenarios available.
GitLab CI/CD Best Practices
Pipeline Design
Use Stages Effectively
Organize jobs into logical stages that represent your development workflow:
stages:
- .pre # Setup and validation
- build # Compilation and asset generation
- test # Testing and quality checks
- scan # Security scanning
- deploy # Deployment
- .post # Cleanup and notifications
Leverage DAG with needs
Create directed acyclic graphs to run jobs as soon as their dependencies complete:
stages:
- build
- test
- deploy
build_frontend:
stage: build
script: npm run build:frontend
build_backend:
stage: build
script: go build ./cmd/server
test_frontend:
stage: test
needs: [build_frontend] # Starts immediately after build_frontend
script: npm test
test_backend:
stage: test
needs: [build_backend] # Runs in parallel with test_frontend
script: go test ./...
deploy:
stage: deploy
needs:
- test_frontend
- test_backend
script: ./deploy.sh
Benefits:
- Faster pipeline execution
- Parallel job execution
- Reduced waiting time
Use rules Instead of only/except
The rules keyword is more powerful and flexible:
# ❌ Deprecated approach
deploy_job:
script: ./deploy.sh
only:
- main
- tags
except:
- schedules
# ✅ Modern approach
deploy_job:
script: ./deploy.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_TAG'
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: never
Performance Optimization
Implement Effective Caching
Cache dependencies to avoid repeated downloads:
variables:
CACHE_VERSION: "v1"
.npm_cache:
cache:
key: ${CACHE_VERSION}-${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
policy: pull-push
install_deps:
extends: .npm_cache
script:
- npm ci --cache .npm
test_job:
extends: .npm_cache
cache:
policy: pull # Only download, don't upload
needs: [install_deps]
script:
- npm test
Best practices:
- Use version prefixes in cache keys for invalidation
- Use
pullpolicy for read-only jobs - Cache package manager files (.npm, .pip, .gem)
- Don’t cache artifacts (use
artifactsinstead)
Optimize Artifact Usage
Only save what you need and set appropriate expiration:
build_job:
script:
- npm run build
artifacts:
paths:
- dist/
- public/
exclude:
- dist/**/*.map # Exclude source maps if not needed
expire_in: 1 week # Clean up old artifacts
test_job:
script:
- npm test
artifacts:
paths:
- coverage/
expire_in: 2 days
when: always # Save even on failure
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura.xml
Use Parallel Execution
Speed up testing with parallel jobs:
test_job:
script:
- npm test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
parallel: 5
# Or with matrix for multiple configurations
test_matrix:
script:
- npm test
parallel:
matrix:
- NODE_VERSION: ['16', '18', '20']
OS: ['ubuntu-latest', 'macos-latest']
Make Jobs Interruptible
Allow automatic cancellation of redundant jobs:
test_job:
script:
- npm test
interruptible: true # Cancel if newer pipeline starts
deploy_production:
script:
- ./deploy.sh
interruptible: false # Never cancel production deployments
Security Best Practices
Never Hardcode Secrets
# ❌ NEVER do this
deploy_job:
script:
- export AWS_SECRET_KEY="AKIAIOSFODNN7EXAMPLE"
- ./deploy.sh
# ✅ Use CI/CD variables or secrets managers
deploy_job:
script:
- ./deploy.sh
variables:
AWS_REGION: "us-east-1"
secrets:
AWS_SECRET_KEY:
vault: production/aws/credentials@ops
Pin Docker Image Versions
Always use specific versions or SHA digests:
# ❌ Avoid using latest tags
build_job:
image: node:latest
script: npm build
# ✅ Pin to specific versions
build_job:
image: node:18.17.0-alpine
script: npm build
# ✅ Even better: Use SHA digest
build_job:
image: node@sha256:a6385a6bb2fdcb7c48fc871e35e32af8daaa82c518f934fcd0e5a42c0dd6ed71
script: npm build
Mask Sensitive Variables
Protect sensitive information in logs:
variables:
PUBLIC_API_URL: "https://api.example.com"
# In GitLab UI, mark these as:
# - Protected (only available on protected branches)
# - Masked (hidden in logs)
# - Hidden (not visible in settings)
Validate External Inputs
When using pipeline variables or inputs, validate them:
validate_input:
stage: .pre
script:
- |
if [[ ! "$DEPLOY_ENV" =~ ^(staging|production)$ ]]; then
echo "Invalid DEPLOY_ENV: $DEPLOY_ENV"
exit 1
fi
Lock Dependencies
Pin exact versions to avoid supply chain attacks:
# For npm
install_deps:
script:
- npm ci # Uses package-lock.json
# For Python with hash verification
install_deps:
script:
- pip install -r requirements.txt --require-hashes
# For Go
verify_deps:
script:
- go mod verify
Pin Include References
Reference specific commits or protected tags:
# ❌ Avoid branch references
include:
- project: 'my-group/templates'
file: '/templates/.gitlab-ci.yml'
ref: main
# ✅ Use specific commit SHAs
include:
- project: 'my-group/templates'
file: '/templates/.gitlab-ci.yml'
ref: 'a1b2c3d4e5f6'
# ✅ Or protected tags
include:
- project: 'my-group/templates'
file: '/templates/.gitlab-ci.yml'
ref: 'v1.2.3'
Code Organization
Use Templates and Extends
Create reusable job templates:
.base_deploy:
stage: deploy
script:
- ./deploy.sh
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
before_script:
- echo "Deploying to $ENVIRONMENT"
deploy_staging:
extends: .base_deploy
variables:
ENVIRONMENT: staging
environment:
name: staging
url: https://staging.example.com
deploy_production:
extends: .base_deploy
variables:
ENVIRONMENT: production
environment:
name: production
url: https://example.com
when: manual
only:
- main
Use YAML Anchors
Reduce repetition with YAML anchors:
.default_retry: &default_retry
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
.node_cache: &node_cache
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
build_job:
<<: *default_retry
<<: *node_cache
script:
- npm run build
Organize with Include
Split large configurations into multiple files:
# .gitlab-ci.yml
include:
- local: '.gitlab/ci/build.yml'
- local: '.gitlab/ci/test.yml'
- local: '.gitlab/ci/deploy.yml'
- local: '.gitlab/ci/security.yml'
stages:
- build
- test
- deploy
Resource Management
Set Appropriate Timeouts
Prevent jobs from hanging indefinitely:
# Project default: Set in UI under Settings > CI/CD
# Job-specific timeout
long_running_job:
script:
- ./long_process.sh
timeout: 3h
quick_job:
script:
- ./quick_check.sh
timeout: 5m
Use Resource Groups
Prevent concurrent deployments:
deploy_production:
stage: deploy
script:
- ./deploy.sh
resource_group: production
environment:
name: production
Control Runner Selection
Use tags to select appropriate runners:
build_job:
tags:
- docker
- high-cpu
script:
- make build
deploy_job:
tags:
- deployment
- protected
script:
- ./deploy.sh
Error Handling
Use allow_failure Strategically
# Experimental features that shouldn't block pipeline
experimental_test:
script:
- ./experimental_feature_test.sh
allow_failure: true
# Allow specific exit codes
integration_test:
script:
- ./integration_tests.sh
allow_failure:
exit_codes:
- 137 # OOM killed
- 143 # SIGTERM
Implement Retry Logic
Retry on transient failures:
flaky_test:
script:
- npm run e2e
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
- unknown_failure
Use after_script for Cleanup
Ensure cleanup happens regardless of job status:
integration_test:
services:
- postgres:14
script:
- ./run_tests.sh
after_script:
- ./cleanup_test_data.sh
Environment Management
Use Dynamic Environments
Create review apps for merge requests:
deploy_review:
stage: deploy
script:
- ./deploy_review_app.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_ENVIRONMENT_SLUG.example.com
on_stop: stop_review
auto_stop_in: 3 days
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
stop_review:
stage: deploy
script:
- ./stop_review_app.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
Use Environment-Specific Variables
variables:
GLOBAL_VAR: "value"
deploy_staging:
stage: deploy
variables:
DEPLOY_URL: "https://staging.example.com"
DEBUG_MODE: "true"
script:
- ./deploy.sh
environment:
name: staging
deploy_production:
stage: deploy
variables:
DEPLOY_URL: "https://example.com"
DEBUG_MODE: "false"
script:
- ./deploy.sh
environment:
name: production
Testing Best Practices
Separate Test Types
Organize tests by scope and speed:
unit_test:
stage: test
script:
- npm run test:unit
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura.xml
integration_test:
stage: test
script:
- npm run test:integration
needs: [build_job]
e2e_test:
stage: test
script:
- npm run test:e2e
needs: [deploy_staging]
allow_failure: true # E2E tests can be flaky
Use Test Reports
Leverage GitLab’s test report features:
test_job:
script:
- npm test
artifacts:
when: always
reports:
junit: test-results.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura.xml
Documentation
Comment Your Pipeline
Add clear comments explaining complex logic:
# This job deploys to production only on the main branch
# and requires manual approval for safety
deploy_production:
stage: deploy
script:
- ./deploy.sh
environment:
name: production
when: manual
rules:
# Only run on main branch
- if: '$CI_COMMIT_BRANCH == "main"'
# Skip on scheduled pipelines
- if: '$CI_PIPELINE_SOURCE == "schedule"'
when: never
Use Meaningful Job Names
Choose descriptive names that explain purpose:
# ❌ Unclear names
job1:
script: npm test
job2:
script: ./script.sh
# ✅ Clear names
unit_tests:
script: npm run test:unit
deploy_to_staging:
script: ./deploy.sh staging
Workflow Optimization
Control Pipeline Creation
Prevent unnecessary pipelines:
workflow:
rules:
# Run for merge requests
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
# Run for main branch
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
# Run for tags
- if: '$CI_COMMIT_TAG'
# Don't run otherwise
- when: never
Use Changes Detection
Run jobs only when relevant files change:
frontend_test:
script:
- npm run test:frontend
rules:
- changes:
- frontend/**/*
- package.json
- package-lock.json
backend_test:
script:
- go test ./...
rules:
- changes:
- backend/**/*.go
- go.mod
- go.sum
Maintenance
Version Your Pipeline
Track pipeline configuration changes:
# Add version comments
# Pipeline version: 2.1.0
# Last updated: 2025-01-15
# Changelog: Added security scanning job
variables:
PIPELINE_VERSION: "2.1.0"
Regular Audits
Periodically review and optimize:
- Remove unused jobs and stages
- Update Docker image versions
- Review cache effectiveness
- Check artifact storage usage
- Update deprecated keywords
- Review and update security practices
Monitor Pipeline Performance
Track key metrics:
- Pipeline duration
- Success rate
- Cache hit rate
- Artifact storage usage
- Runner queue times
- Job failure rates
Use GitLab’s analytics features to identify bottlenecks and optimization opportunities.
Common GitLab CI/CD Issues and Solutions
Syntax Errors
Invalid YAML Syntax
Problem: YAML formatting errors prevent pipeline execution.
Common causes:
- Inconsistent indentation (mixing tabs and spaces)
- Missing colons after keys
- Incorrect list formatting
- Unquoted special characters
Examples:
# ❌ Wrong indentation
job_name:
script:
- echo "test"
# ✅ Correct indentation
job_name:
script:
- echo "test"
# ❌ Missing colon
job_name
script:
- echo "test"
# ✅ Correct syntax
job_name:
script:
- echo "test"
# ❌ Unquoted special characters
job_name:
script:
- echo $VAR: value
# ✅ Quoted special characters
job_name:
script:
- echo "$VAR: value"
Solution: Use a YAML linter or GitLab’s CI Lint tool to validate syntax.
Reserved Keyword as Job Name
Problem: Using reserved keywords as job names causes validation errors.
Reserved keywords:
image,services,stages,typesbefore_script,after_script,variablescache,include,pages,default,workflow
# ❌ Using reserved keyword
image:
stage: build
script:
- echo "build"
# ✅ Use a different name
build_image:
stage: build
script:
- echo "build"
Job Configuration Issues
Missing script Keyword
Problem: Every job must have a script section (except some special jobs like trigger).
# ❌ Missing script
test_job:
stage: test
# ✅ With script
test_job:
stage: test
script:
- npm test
Undefined Stage Reference
Problem: Job references a stage that doesn’t exist in stages definition.
# ❌ Undefined stage
stages:
- build
- test
deploy_job:
stage: deploy # 'deploy' stage not defined
script:
- ./deploy.sh
# ✅ Stage defined
stages:
- build
- test
- deploy
deploy_job:
stage: deploy
script:
- ./deploy.sh
Invalid Job Dependencies
Problem: Referencing non-existent jobs in dependencies or needs.
# ❌ References non-existent job
test_job:
stage: test
dependencies:
- build_job # This job doesn't exist
script:
- npm test
# ✅ Valid dependency
build_job:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
test_job:
stage: test
dependencies:
- build_job
script:
- npm test
Circular Dependencies with needs
Problem: Creating circular dependencies with needs keyword.
# ❌ Circular dependency
job_a:
needs: [job_b]
script: echo "A"
job_b:
needs: [job_a]
script: echo "B"
# ✅ Valid DAG
job_a:
script: echo "A"
job_b:
needs: [job_a]
script: echo "B"
Variable Issues
Undefined Variable Reference
Problem: Referencing variables that don’t exist.
# ❌ Undefined variable
deploy_job:
script:
- echo "Deploying to $UNDEFINED_ENV"
# ✅ Define variable
variables:
DEPLOY_ENV: "staging"
deploy_job:
script:
- echo "Deploying to $DEPLOY_ENV"
Variable Scope Issues
Problem: Variables not available in expected scope.
# ❌ Job variable not available globally
job_a:
variables:
MY_VAR: "value"
script:
- echo $MY_VAR
job_b:
script:
- echo $MY_VAR # Not available here
# ✅ Use global variable
variables:
MY_VAR: "value"
job_a:
script:
- echo $MY_VAR
job_b:
script:
- echo $MY_VAR
Hardcoded Secrets
Problem: Sensitive data exposed in pipeline configuration.
# ❌ Hardcoded credentials
deploy_job:
script:
- export API_KEY="sk_live_1234567890"
- ./deploy.sh
# ✅ Use CI/CD variables or secrets
deploy_job:
script:
- ./deploy.sh # API_KEY from CI/CD variables
secrets:
API_KEY:
vault: production/api/key@ops
Artifact and Cache Issues
Artifacts Not Passed Between Jobs
Problem: Jobs can’t access files from previous jobs.
# ❌ No artifacts defined
build_job:
stage: build
script:
- npm run build
test_job:
stage: test
script:
- ls dist/ # Directory doesn't exist
# ✅ With artifacts
build_job:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test_job:
stage: test
needs: [build_job]
script:
- ls dist/ # Now available
Cache Not Working
Problem: Dependencies downloaded on every job run.
Common causes:
- Wrong cache paths
- Incorrect cache key
- Cache policy misconfiguration
- Runner doesn’t support caching
# ❌ Wrong cache configuration
test_job:
cache:
paths:
- node_modules/ # Wrong path or not created
script:
- npm ci
- npm test
# ✅ Correct cache configuration
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
test_job:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .npm/
- node_modules/
script:
- npm ci --cache .npm --prefer-offline
- npm test
Cache vs Artifacts Confusion
Problem: Using cache for job outputs instead of artifacts.
# ❌ Using cache for build outputs
build_job:
cache:
paths:
- dist/ # Should be artifacts
script:
- npm run build
# ✅ Correct usage
build_job:
cache:
paths:
- node_modules/ # Dependencies (cache)
artifacts:
paths:
- dist/ # Build outputs (artifacts)
script:
- npm ci
- npm run build
Rules and Conditions Issues
Conflicting Rules
Problem: Multiple rules that contradict each other.
# ❌ Conflicting rules
deploy_job:
script:
- ./deploy.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
- if: '$CI_COMMIT_BRANCH == "main"'
when: never # Conflicts with above
# ✅ Clear rules
deploy_job:
script:
- ./deploy.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE != "schedule"'
when: on_success
- when: never
Mixing rules with only/except
Problem: Cannot use rules with only/except in the same job.
# ❌ Mixing rules and only/except
deploy_job:
script:
- ./deploy.sh
only:
- main
rules:
- if: '$CI_COMMIT_TAG'
# ✅ Use rules only
deploy_job:
script:
- ./deploy.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
- if: '$CI_COMMIT_TAG'
Incorrect changes Usage
Problem: changes not working as expected.
# ❌ Changes with wrong pipeline source
test_job:
script:
- npm test
rules:
- changes:
- src/**/*.js
# Won't work on branch pipelines without if condition
# ✅ Correct changes usage
test_job:
script:
- npm test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- src/**/*.js
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Docker and Service Issues
Image Pull Failures
Problem: Cannot pull Docker images.
Common causes:
- Image doesn’t exist
- Authentication required
- Network issues
- Using
:latestwithout recent pull
# ❌ Non-existent or inaccessible image
test_job:
image: mycompany/nonexistent:latest
script:
- npm test
# ✅ Valid, accessible image
test_job:
image: node:18-alpine
script:
- npm test
# ✅ Private registry with authentication
test_job:
image: registry.gitlab.com/mygroup/myimage:v1.0
before_script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
- npm test
Service Connection Issues
Problem: Cannot connect to services (databases, etc.).
# ❌ Wrong service alias or missing variables
test_job:
image: node:18
services:
- postgres:14
script:
- npm run test:integration # Connection fails
# ✅ Proper service configuration
test_job:
image: node:18
services:
- name: postgres:14
alias: postgres
variables:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
DATABASE_URL: "postgres://test:test@postgres:5432/testdb"
script:
- npm run test:integration
Using :latest Tag
Problem: Unpredictable behavior with :latest tags.
# ❌ Using latest tag
build_job:
image: node:latest # Could change unexpectedly
script:
- npm run build
# ✅ Pin specific version
build_job:
image: node:18.17.0-alpine
script:
- npm run build
# ✅ Even better: Use SHA digest
build_job:
image: node@sha256:a6385a6bb2fdcb7c48fc871e35e32af8daaa82c518f934fcd0e5a42c0dd6ed71
script:
- npm run build
Performance Issues
Slow Pipeline Execution
Problem: Pipelines take too long to complete.
Solutions:
- Use caching:
.node_cache:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
build_job:
extends: .node_cache
script:
- npm ci --cache .npm
- npm run build
- Use
needsfor parallel execution:
# Instead of sequential stages
build_a:
stage: build
script: make build_a
build_b:
stage: build # Runs in parallel with build_a
script: make build_b
test_a:
stage: test
needs: [build_a] # Starts immediately after build_a
script: make test_a
- Use parallel jobs:
test_job:
script:
- npm test -- --shard=$CI_NODE_INDEX/$CI_NODE_TOTAL
parallel: 5
Cache Miss Rate High
Problem: Cache frequently invalidated or not used.
# ❌ Cache key changes too often
test_job:
cache:
key: $CI_COMMIT_SHA # Different for every commit
paths:
- node_modules/
# ✅ Stable cache key
test_job:
cache:
key: ${CI_COMMIT_REF_SLUG}-${CI_PROJECT_DIR}/package-lock.json
paths:
- node_modules/
Downloading Same Artifacts Multiple Times
Problem: Multiple jobs downloading same artifacts unnecessarily.
# ❌ All artifacts downloaded
build_a:
artifacts:
paths:
- dist_a/
build_b:
artifacts:
paths:
- dist_b/
test_job:
needs: [build_a, build_b]
script:
- test dist_a/ # Only needs dist_a
# ✅ Use dependencies to control downloads
test_job:
needs:
- build_a
- build_b
dependencies:
- build_a # Only download from build_a
script:
- test dist_a/
Security Issues
Secrets in Logs
Problem: Sensitive data visible in job logs.
# ❌ Secrets printed to logs
deploy_job:
script:
- echo "API Key: $API_KEY" # Visible in logs
- ./deploy.sh
# ✅ Mask variables and avoid printing
deploy_job:
script:
- ./deploy.sh
# Mark API_KEY as masked in CI/CD settings
Unpinned Dependencies
Problem: Vulnerable or malicious dependencies could be installed.
# ❌ Unpinned dependencies
install_job:
script:
- npm install # Could install different versions
# ✅ Locked dependencies
install_job:
script:
- npm ci # Uses package-lock.json
# ✅ With hash verification (Python)
install_job:
script:
- pip install -r requirements.txt --require-hashes
Insecure Script Patterns
Problem: Scripts vulnerable to injection or other attacks.
# ❌ Command injection risk
deploy_job:
script:
- curl $EXTERNAL_URL | bash # Dangerous
# ✅ Verify and validate
deploy_job:
script:
- curl -o script.sh $EXTERNAL_URL
- sha256sum -c script.sh.sha256
- bash script.sh
Environment and Deployment Issues
Environment Not Created
Problem: Deployment environments not showing in GitLab UI.
# ❌ Missing environment keyword
deploy_job:
script:
- ./deploy.sh staging
# ✅ With environment
deploy_job:
script:
- ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
Manual Jobs Not Stopping Pipeline
Problem: Pipeline continues without waiting for manual job.
# ❌ Pipeline continues
deploy_staging:
script:
- ./deploy.sh
when: manual
deploy_production:
needs: [deploy_staging] # Starts immediately
script:
- ./deploy.sh
# ✅ Use allow_failure: false
deploy_staging:
script:
- ./deploy.sh
when: manual
allow_failure: false # Pipeline waits
deploy_production:
needs: [deploy_staging]
script:
- ./deploy.sh
Review App Cleanup Issues
Problem: Review apps not automatically stopped.
# ❌ No cleanup
deploy_review:
script:
- ./deploy_review.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_ENVIRONMENT_SLUG.example.com
# ✅ With auto-stop and stop job
deploy_review:
script:
- ./deploy_review.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_ENVIRONMENT_SLUG.example.com
on_stop: stop_review
auto_stop_in: 3 days
stop_review:
script:
- ./stop_review.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
Runner Issues
No Runners Available
Problem: Jobs stuck in “pending” state.
Solutions:
- Check runner tags match job tags
- Verify runners are online and not paused
- Check runner capacity and queue
- Review runner permissions for project
# If job requires specific tags
build_job:
tags:
- docker
- linux
script:
- make build
# Ensure runners with these tags are available and active
Runner Timeout
Problem: Jobs fail due to timeout.
# ❌ Default timeout too short
long_running_job:
script:
- ./long_process.sh # Takes > 1 hour
# ✅ Increase timeout
long_running_job:
script:
- ./long_process.sh
timeout: 3h
Include and Template Issues
Include File Not Found
Problem: Cannot find included file.
# ❌ Wrong path
include:
- local: 'templates/ci.yml' # Missing leading slash
# ✅ Correct path
include:
- local: '/templates/ci.yml' # Absolute path from repo root
Circular Includes
Problem: Files include each other creating a loop.
# File A includes File B
# File B includes File A
# Results in: "Maximum includes depth reached"
# Solution: Restructure includes to avoid circular references
Include with Wrong Project Path
Problem: Cannot access files from other projects.
# ❌ Wrong project path or no access
include:
- project: 'wrong-group/wrong-project'
file: '/templates/ci.yml'
# ✅ Correct project path with access
include:
- project: 'my-group/templates'
ref: 'v1.2.3'
file: '/templates/ci.yml'
Debugging Tips
Enable Debug Logging
Add debug variables to get more information:
variables:
CI_DEBUG_TRACE: "true" # Enable debug mode
CI_DEBUG_SERVICES: "true" # Debug service connections
Use echo for Debugging
Print variable values and script execution:
debug_job:
script:
- echo "Branch: $CI_COMMIT_BRANCH"
- echo "Ref: $CI_COMMIT_REF_NAME"
- env | sort # Print all environment variables
- set -x # Enable command tracing
- ./my_script.sh
Test Locally
Use tools to test pipelines locally:
# Using gitlab-ci-local
npm install -g gitlab-ci-local
gitlab-ci-local
# Using gitlab-ci-validate
pip install gitlab-ci-validate
gitlab-ci-validate .gitlab-ci.yml
Use CI Lint Tool
Validate configuration before committing:
- Navigate to: CI/CD > Pipeline editor > Validate tab
- Paste your
.gitlab-ci.ymlcontent - Review validation results and errors
- Or use API:
POST /api/v4/ci/lint
GitLab CI/CD YAML Reference
Overview
GitLab CI/CD pipelines are defined in .gitlab-ci.yml files using YAML syntax. The file must be located at the root of your repository. The order of keywords is not important unless otherwise specified.
File Structure
# Global configuration
default:
# Default settings for all jobs
include:
# Import external configurations
stages:
# Define pipeline stages
variables:
# Global variables
workflow:
# Pipeline execution rules
# Job definitions
job_name:
stage: stage_name
script:
- command1
- command2
Global Keywords
default
Establishes custom default values that are copied to jobs lacking specific keyword definitions.
Supported keywords:
image,services,before_script,after_scriptcache,artifacts,retry,timeout,interruptibletags,hooks
Example:
default:
image: ruby:3.0
retry: 2
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- vendor/
include
Imports configuration from external YAML files.
Types:
local: Files in same repositoryproject: Files from other GitLab projectsremote: Files from external URLstemplate: GitLab-provided templatescomponent: CI/CD catalog components
Example:
include:
- local: '/templates/.gitlab-ci-template.yml'
- template: 'Auto-DevOps.gitlab-ci.yml'
- project: 'my-group/my-project'
file: '/templates/.gitlab-ci.yml'
- remote: 'https://example.com/.gitlab-ci.yml'
- component: $CI_SERVER_FQDN/my-org/security/secret-detection@1.0
stages
Defines the names and order of pipeline stages. Jobs in the same stage run in parallel.
Default stages (if not defined):
.prebuildtestdeploy.post
Example:
stages:
- build
- test
- deploy
- cleanup
variables
Sets CI/CD variables available to all jobs or specific jobs.
Example:
variables:
DATABASE_URL: "postgres://postgres@postgres/db"
DEPLOY_NOTE:
description: "The deployment note"
value: "Default deployment"
job_name:
variables:
DEPLOY_ENV: "production"
workflow
Controls pipeline behavior and creation rules.
Example:
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Job Keywords
Required Keywords
script
The only required keyword. Defines shell commands executed by the runner.
Syntax:
job_name:
script: "single command"
# OR multi-line
job_name:
script:
- command1
- command2
- |
multi-line
command block
Execution Control
before_script
Commands running before the script section.
Example:
job_name:
before_script:
- echo "Preparing environment"
- bundle install
script:
- bundle exec rspec
after_script
Commands running after script completion. Executes in a separate shell context.
Example:
job_name:
script:
- ./deploy.sh
after_script:
- ./cleanup.sh
stage
Assigns the job to a specific pipeline stage.
Example:
build_job:
stage: build
script:
- make build
when
Controls when jobs run.
Values:
on_success(default): Run when all previous jobs succeedon_failure: Run when at least one previous job failsalways: Always runmanual: Require manual actiondelayed: Delay job executionnever: Never run
Example:
cleanup_job:
stage: cleanup
script:
- ./cleanup.sh
when: always
deploy_job:
stage: deploy
script:
- ./deploy.sh
when: manual
Artifact Management
artifacts
Specifies files and directories to save after job completion.
Sub-keywords:
paths: File locations to includeexclude: Patterns to excludeexpire_in: Retention duration (default: 30 days)name: Archive namewhen: Upload condition (on_success, on_failure, always)reports: Collect test/coverage/security reports
Example:
test_job:
script:
- npm test
artifacts:
paths:
- coverage/
- dist/
exclude:
- coverage/**/*.tmp
expire_in: 1 week
reports:
junit: test-results.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura.xml
cache
Defines files cached between job runs for faster execution.
Sub-keywords:
paths: Items to cachekey: Cache identifierpolicy: Download/upload behavior (pull, push, pull-push)when: Cache condition (on_success, on_failure, always)untracked: Cache untracked files
Example:
build_job:
script:
- npm install
- npm run build
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
policy: pull-push
test_job:
script:
- npm test
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
policy: pull
Job Dependencies
dependencies
Restricts artifact downloads to specified jobs only.
Example:
build_job:
stage: build
script:
- make build
artifacts:
paths:
- binaries/
test_job:
stage: test
script:
- ./test.sh
dependencies:
- build_job
needs
Executes jobs earlier than stage ordering permits, creating a directed acyclic graph (DAG).
Example:
build_job:
stage: build
script:
- make build
test_job:
stage: test
script:
- make test
needs:
- build_job
deploy_job:
stage: deploy
script:
- make deploy
needs:
- test_job
Conditional Execution
rules
Determines job creation based on conditions. Replaces only/except.
Example:
deploy_job:
script:
- echo "Deploy to production"
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
- if: '$CI_COMMIT_BRANCH == "staging"'
when: on_success
- when: never
test_job:
script:
- npm test
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- changes:
- src/**/*.js
- test/**/*.js
allow_failure
Permits job failure without stopping the pipeline.
Example:
experimental_test:
script:
- experimental_command
allow_failure: true
# With exit codes
integration_test:
script:
- ./integration_tests.sh
allow_failure:
exit_codes: [137, 255]
Environment & Deployment
environment
Specifies deployment target environment.
Sub-keywords:
name: Environment nameurl: Environment URLon_stop: Job to stop environmentauto_stop_in: Auto-stop durationaction: Deployment action (start, prepare, stop)
Example:
deploy_staging:
stage: deploy
script:
- ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
on_stop: stop_staging
auto_stop_in: 1 day
deploy_review:
stage: deploy
script:
- ./deploy.sh review
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_ENVIRONMENT_SLUG.example.com
on_stop: stop_review
auto_stop_in: 1 week
stop_review:
stage: deploy
script:
- ./stop_review.sh
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manual
Container Configuration
image
Specifies Docker container image for job execution.
Example:
test_job:
image: node:18-alpine
script:
- npm test
# With specific digest (recommended for security)
secure_job:
image: node@sha256:abc123...
script:
- npm run secure-test
services
Defines Docker service images (databases, cache servers, etc.).
Example:
integration_test:
image: node:18
services:
- name: postgres:14
alias: postgres
- name: redis:7-alpine
alias: cache
variables:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
script:
- npm run integration-test
Resource Management
tags
Selects runners by labels.
Example:
build_job:
tags:
- docker
- linux
deploy_job:
tags:
- kubernetes
- production
timeout
Sets job-level timeout, overriding project settings.
Example:
long_running_job:
script:
- ./long_process.sh
timeout: 3h
resource_group
Limits job concurrency within a resource group.
Example:
deploy_production:
script:
- ./deploy.sh
resource_group: production
parallel
Runs multiple job instances in parallel.
Example:
test_job:
script:
- npm test
parallel: 5
# With matrix
test_matrix:
script:
- bundle exec rspec
parallel:
matrix:
- RUBY_VERSION: ['2.7', '3.0', '3.1']
DATABASE: ['postgres', 'mysql']
interruptible
Allows job cancellation when made redundant by newer runs.
Example:
test_job:
script:
- npm test
interruptible: true
Advanced Features
extends
Inherits configuration from other jobs or templates.
Example:
.default_retry:
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
test_job:
extends: .default_retry
script:
- npm test
retry
Auto-retry configuration on failure.
Example:
test_job:
script:
- flaky_test.sh
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
- unknown_failure
coverage
Extracts code coverage metrics via regex.
Example:
test_job:
script:
- npm test
coverage: '/Coverage: \d+\.\d+/'
secrets
Specifies required CI/CD secrets from external providers.
Example:
deploy_job:
script:
- ./deploy.sh
secrets:
DATABASE_PASSWORD:
vault: production/db/password@ops
file: false
API_KEY:
vault: production/api/key@ops
trigger
Defines downstream pipeline triggers.
Example:
trigger_downstream:
stage: deploy
trigger:
project: my-group/downstream-project
branch: main
release
Generates release objects.
Example:
release_job:
stage: release
script:
- echo "Creating release"
release:
tag_name: '$CI_COMMIT_TAG'
name: 'Release $CI_COMMIT_TAG'
description: 'Release notes here'
Predefined Variables
Common CI/CD variables available in all pipelines:
CI_COMMIT_BRANCH: Current branch nameCI_COMMIT_SHA: Current commit SHACI_COMMIT_REF_NAME: Branch or tag nameCI_COMMIT_REF_SLUG: Lowercased, shortened to 63 bytesCI_COMMIT_TAG: Commit tag nameCI_DEFAULT_BRANCH: Default branch nameCI_ENVIRONMENT_NAME: Environment nameCI_ENVIRONMENT_SLUG: Simplified environment nameCI_JOB_ID: Job IDCI_JOB_NAME: Job nameCI_JOB_STAGE: Job stageCI_PIPELINE_ID: Pipeline IDCI_PIPELINE_SOURCE: Pipeline trigger sourceCI_PROJECT_DIR: Repository clone directoryCI_PROJECT_ID: Project IDCI_PROJECT_NAME: Project nameCI_PROJECT_PATH: Project pathCI_PROJECT_URL: Project URLCI_REGISTRY: GitLab container registry URLCI_REGISTRY_IMAGE: Container registry image pathCI_RUNNER_ID: Runner IDCI_SERVER_URL: GitLab instance URL
Reserved Keywords
The following keywords cannot be used as job names:
imageservicesstagestypesbefore_scriptafter_scriptvariablescacheincludepagesdefaultworkflow
Validation
Use the GitLab CI Lint tool to validate your configuration:
- Web UI: Navigate to Build > Pipeline editor > Validate tab
- API: POST to
/api/v4/ci/lint - VS Code: Use GitLab Workflow extension
Common Patterns
Anchors and References
.default_cache: &default_cache
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
build_job:
<<: *default_cache
script:
- npm install
- npm run build
Hidden Jobs (Templates)
.deploy_template:
script:
- ./deploy.sh
only:
- main
deploy_staging:
extends: .deploy_template
environment:
name: staging
deploy_production:
extends: .deploy_template
environment:
name: production
when: manual
Best Practices
- Use
rulesinstead ofonly/except: More flexible and powerful - Leverage caching: Cache dependencies between jobs
- Use
needsfor DAG pipelines: Faster execution - Pin Docker images: Use specific versions or SHA digests
- Set artifact expiration: Avoid storage bloat
- Use templates and extends: DRY principle
- Define meaningful stage names: Clear pipeline flow
- Use
interruptible: Save resources on redundant jobs - Implement proper error handling: Use
allow_failureappropriately - Document your pipeline: Use comments in YAML