Comprehensive toolkit for generating best practice GitLab CI/CD pipelines and configurations following current standards and conventions. Use this skill when creating new GitLab CI/CD resources, implementing CI/CD pipelines, or building GitLab pipelines from scratch.
Overall
score
93%
Does it follow best practices?
Validation for skill structure
Comprehensive reference for GitLab CI/CD .gitlab-ci.yml configuration syntax.
Global keywords control pipeline-wide behavior and configuration.
stagesDefines the order of pipeline stages. Jobs in the same stage run in parallel.
stages:
- build
- test
- deployDefault stages:
stages:
- .pre # Special stage, runs before everything
- build
- test
- deploy
- .post # Special stage, runs after everythingdefaultSets default values for all jobs. Job-level configurations override defaults completely (no merging).
default:
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- echo "Starting job"
tags:
- docker
interruptible: true
retry:
max: 1
when:
- runner_system_failureincludeImports external YAML configuration files.
# Local file from same repository
include:
- local: '.gitlab/ci/build-jobs.yml'
# File from another project
include:
- project: 'group/ci-templates'
ref: main
file: 'templates/build.yml'
# Remote file via HTTP
include:
- remote: 'https://example.com/ci-template.yml'
# GitLab CI/CD template
include:
- template: 'Security/SAST.gitlab-ci.yml'
# CI/CD component
include:
- component: gitlab.com/my-org/components/build@1.0.0variablesDefines CI/CD variables available to all jobs.
variables:
NODE_VERSION: "20"
DOCKER_DRIVER: overlay2
CACHE_VERSION: "v1"Variable types:
$VARIABLE - Simple substitution${VARIABLE} - Explicit variable reference$$VARIABLE - Escaped variable (passed to script)workflowControls when pipelines run and their auto-cancellation behavior.
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_MERGE_REQUEST_ID
- if: $CI_COMMIT_TAG
auto_cancel:
on_new_commit: interruptibleJobs are the basic building blocks that define what to execute.
job-name:
stage: build
image: node:20-alpine
script:
- npm ci
- npm run buildReserved job names:
image, services, stages, before_script, after_script, variables, cache, includestageAssigns the job to a pipeline stage.
build-job:
stage: build # Default: test
script: make buildscript (required)Shell commands to execute. At least one of script, trigger, or extends is required.
build:
script:
- echo "Building..."
- make build
- make package
# Multi-line script
test:
script:
- |
echo "Running tests"
npm test
echo "Tests complete"before_scriptCommands executed before script. Runs in the same shell context as script.
test:
before_script:
- echo "Setting up..."
- npm ci
script:
- npm testafter_scriptCommands executed after script, runs in a separate shell. Cannot affect job exit code.
deploy:
script:
- deploy.sh
after_script:
- echo "Cleaning up..."
- rm -rf temp/Note: after_script has a separate 5-minute timeout by default.
imageDocker image for job execution.
# Simple image
build:
image: node:20-alpine
# Image with entrypoint override
test:
image:
name: my-image:latest
entrypoint: [""]servicesDocker service containers (databases, caches, etc.).
test:
image: node:20-alpine
services:
- postgres:15-alpine
- redis:7-alpine
variables:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpasstagsSelects runners with matching tags.
deploy:
tags:
- docker
- production
script: deploy.shwhenControls when jobs run.
Values:
on_success (default) - Run when all previous stage jobs succeedon_failure - Run when at least one previous stage job failsalways - Always run regardless of statusmanual - Requires manual triggerdelayed - Run after delaynever - Don't runcleanup:
when: always
script: cleanup.sh
deploy:
when: manual
script: deploy.sh
delayed-job:
when: delayed
start_in: 30 minutes
script: echo "Running after delay"allow_failureAllows job to fail without blocking pipeline.
lint:
script: npm run lint
allow_failure: true
# Conditional allow_failure
test-experimental:
script: npm test
allow_failure:
exit_codes: [1, 137]retryConfigures automatic retry on failure.
# Simple retry
deploy:
retry: 2
# Advanced retry configuration
integration-test:
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failure
- api_failureRetry conditions:
always - Retry on any failurerunner_system_failure - Runner system failedstuck_or_timeout_failure - Job stuck or timed outscript_failure - Script failedapi_failure - API failureunknown_failure - Unknown failuretimeoutJob-specific timeout override.
test-quick:
timeout: 10 minutes
script: npm run test:unit
test-e2e:
timeout: 1 hour
script: npm run test:e2einterruptibleMarks job as cancellable when superseded by newer pipelines.
test:
interruptible: true # Can be canceled
script: npm test
deploy:
interruptible: false # Cannot be canceled
script: deploy.shresource_groupLimits job concurrency for resource-sensitive operations.
deploy-production:
resource_group: production
script: deploy.sh
deploy-staging:
resource_group: staging
script: deploy.shparallelRuns multiple job instances in parallel.
# Parallel with count
test:
parallel: 5
script: npm test -- --shard=${CI_NODE_INDEX}/${CI_NODE_TOTAL}
# Parallel with matrix
test-matrix:
parallel:
matrix:
- NODE_VERSION: ['18', '20', '22']
OS: ['alpine', 'bookworm-slim']
image: node:${NODE_VERSION}-${OS}
script: npm test# Using |
test:
script:
- |
echo "Line 1"
echo "Line 2"
echo "Line 3"
# Using >
deploy:
script:
- >
kubectl apply -f deployment.yaml
--namespace production
--timeout 5m# Stop on first error (default)
build:
script:
- command1
- command2 # Won't run if command1 fails
# Continue on error
test:
script:
- command1 || true
- command2 # Runs even if command1 failsartifactsFiles/directories to preserve after job completion.
build:
script: make build
artifacts:
paths:
- dist/
- build/
exclude:
- "**/*.map"
- dist/temp/
expire_in: 1 hour
when: on_success # on_success, on_failure, always
name: "build-${CI_COMMIT_SHORT_SHA}"Artifact types:
test:
script: npm test
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
dotenv: build.envExpiration values:
30 minutes, 1 hour, 2 hours1 day, 2 days, 1 week, 1 monthnever - Keep forevercachePreserves files between pipeline runs.
build:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
policy: pull-push
when: on_successCache policies:
pull-push (default) - Download and upload cachepull - Only download cachepush - Only upload cacheCache keys:
# Branch-based key
cache:
key: ${CI_COMMIT_REF_SLUG}
# File-based key
cache:
key:
files:
- package-lock.json
prefix: npm
# Multiple caches
cache:
- key: npm-cache
paths:
- node_modules/
- key: build-cache
paths:
- dist/rulesDetermines when to create jobs and which attributes to apply.
deploy:
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: always
- if: $CI_MERGE_REQUEST_ID
when: manual
- when: never # Default: don't runRule clauses:
if - Variable expressionschanges - File modificationsexists - File existencewhen - Execution timingvariables - Dynamic variablesallow_failure - Failure behavior# Run on specific branch
deploy:
rules:
- if: $CI_COMMIT_BRANCH == "main"
# Run on file changes
test-frontend:
rules:
- changes:
- frontend/**/*
# Run if file exists
docs-build:
rules:
- exists:
- docs/mkdocs.yml
# Complex rules
deploy:
rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TAG == null
when: manual
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: on_success
- when: neveronly / except (Deprecated)Note: Use rules instead. only and except are deprecated.
# ❌ Deprecated
deploy:
only:
- main
except:
- tags
# ✅ Use rules instead
deploy:
rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TAG == nulldependenciesRestricts artifact downloads from specific jobs.
build:
stage: build
script: make build
artifacts:
paths:
- dist/
test:
stage: test
dependencies: [build] # Only download artifacts from build
script: test dist/
deploy:
stage: deploy
dependencies: [] # Don't download any artifacts
script: deployneedsCreates Directed Acyclic Graph (DAG) for faster pipelines.
build-frontend:
stage: build
script: build frontend
build-backend:
stage: build
script: build backend
test-frontend:
stage: test
needs: [build-frontend] # Starts as soon as build-frontend completes
script: test frontend
test-backend:
stage: test
needs: [build-backend]
script: test backend
deploy:
stage: deploy
needs: [test-frontend, test-backend]
script: deployNeeds with artifacts:
deploy:
needs:
- job: build
artifacts: true # Default
- job: test
artifacts: false # Don't download artifactsbuild-docker:
image: docker:24-dind
services:
- docker:24-dind
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker build -t myimage .build-kaniko:
image:
name: gcr.io/kaniko-project/executor:v1.21.0-debug
entrypoint: [""]
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:latest"environmentMarks jobs that deploy to environments.
deploy-staging:
environment:
name: staging
url: https://staging.example.com
on_stop: stop-staging
auto_stop_in: 1 day
action: start # start, prepare, stop
script: deploy staging
stop-staging:
environment:
name: staging
action: stop
when: manual
script: stop stagingKubernetes integration:
deploy-k8s:
environment:
name: production
url: https://example.com
kubernetes:
namespace: production
script: kubectl apply -f deployment.yamlDynamic environments:
review:
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop-review
auto_stop_in: 1 week
script: deploy review
rules:
- if: $CI_MERGE_REQUEST_IDextendsInherits configuration from other jobs.
.deploy-template:
image: alpine:3.19
before_script:
- apk add --no-cache curl
retry:
max: 2
deploy-staging:
extends: .deploy-template
script: deploy staging
deploy-production:
extends: .deploy-template
script: deploy productionMultiple inheritance:
.base:
image: alpine:3.19
.retry:
retry: 2
deploy:
extends:
- .base
- .retry
script: deploytriggerTriggers downstream pipelines.
# Trigger another project
trigger-downstream:
trigger:
project: group/downstream-project
branch: main
strategy: depend # Wait for downstream pipeline
# Trigger child pipeline
trigger-child:
trigger:
include: child-pipeline.yml
strategy: depend
# Trigger with variables
trigger-deploy:
trigger:
project: group/deploy-project
variables:
VERSION: $CI_COMMIT_SHORT_SHA
ENVIRONMENT: productioncoverageExtracts code coverage percentage from job output.
test:
script: npm test
coverage: '/Coverage: \d+\.\d+%/'releaseCreates GitLab releases.
release:
stage: deploy
image: registry.gitlab.com/gitlab-org/release-cli:latest
rules:
- if: $CI_COMMIT_TAG
script:
- echo "Creating release"
release:
tag_name: $CI_COMMIT_TAG
name: 'Release $CI_COMMIT_TAG'
description: 'Release notes for $CI_COMMIT_TAG'secretsRetrieves secrets from external sources.
deploy:
secrets:
DATABASE_PASSWORD:
vault: production/db/password@secret
file: falseinheritControls inheritance of global defaults.
job:
inherit:
default: false # Don't inherit default settings
variables: [VAR1, VAR2] # Only inherit specific variablesCommon GitLab CI/CD variables:
$CI_PIPELINE_ID - Pipeline ID$CI_PIPELINE_IID - Pipeline IID (internal ID)$CI_PIPELINE_SOURCE - Pipeline source (push, merge_request_event, etc.)$CI_PIPELINE_URL - Pipeline URL$CI_COMMIT_SHA - Full commit SHA$CI_COMMIT_SHORT_SHA - Short commit SHA (8 chars)$CI_COMMIT_BRANCH - Branch name$CI_COMMIT_TAG - Tag name (if pipeline for tag)$CI_COMMIT_REF_NAME - Branch or tag name$CI_COMMIT_REF_SLUG - Slugified branch/tag name$CI_COMMIT_MESSAGE - Commit message$CI_COMMIT_AUTHOR - Commit author$CI_JOB_ID - Job ID$CI_JOB_NAME - Job name$CI_JOB_STAGE - Job stage$CI_JOB_URL - Job URL$CI_JOB_TOKEN - Job token for API access$CI_NODE_INDEX - Job index in parallel jobs (1-based)$CI_NODE_TOTAL - Total number of parallel jobs$CI_PROJECT_ID - Project ID$CI_PROJECT_NAME - Project name$CI_PROJECT_PATH - Project path (group/project)$CI_PROJECT_DIR - Working directory$CI_PROJECT_URL - Project URL$CI_REGISTRY - GitLab Container Registry URL$CI_REGISTRY_IMAGE - Full image path$CI_REGISTRY_USER - Registry username$CI_REGISTRY_PASSWORD - Registry password$CI_MERGE_REQUEST_ID - MR ID$CI_MERGE_REQUEST_IID - MR IID$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME - Source branch$CI_MERGE_REQUEST_TARGET_BRANCH_NAME - Target branchYAML anchors for reusing configuration within a file.
# Define anchor
.retry-config: &retry-config
retry:
max: 2
when:
- runner_system_failure
# Use anchor
job1:
<<: *retry-config
script: command1
job2:
<<: *retry-config
script: command2Merge multiple anchors:
.cache: &cache
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
.image: &image
image: node:20-alpine
build:
<<: [*cache, *image]
script: npm run build# Global configuration
stages:
- build
- test
- security
- deploy
variables:
NODE_VERSION: "20"
DOCKER_DRIVER: overlay2
default:
image: node:${NODE_VERSION}-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
tags:
- docker
interruptible: true
# Hidden template
.deploy-template:
before_script:
- echo "Deploying to ${ENVIRONMENT}"
retry:
max: 2
when:
- runner_system_failure
resource_group: ${ENVIRONMENT}
# Build job
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
# Test jobs
test-unit:
stage: test
needs: []
script:
- npm ci
- npm test
coverage: '/Coverage: \d+\.\d+%/'
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
test-lint:
stage: test
needs: []
script:
- npm ci
- npm run lint
allow_failure: true
# Security scanning
include:
- template: Security/SAST.gitlab-ci.yml
# Deployment jobs
deploy-staging:
extends: .deploy-template
stage: deploy
variables:
ENVIRONMENT: staging
needs: [build, test-unit]
environment:
name: staging
url: https://staging.example.com
script:
- ./deploy.sh staging
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy-production:
extends: .deploy-template
stage: deploy
variables:
ENVIRONMENT: production
needs: [build, test-unit]
environment:
name: production
url: https://example.com
script:
- ./deploy.sh production
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manualUse this reference when generating or troubleshooting GitLab CI/CD configurations.
Install with Tessl CLI
npx tessl i pantheon-ai/gitlab-ci-generator@0.1.0