Gitlab CI
GitLab CI/CD pipelines including multi-stage builds, includes, rules, and Auto DevOps configuration
You are an expert in GitLab CI/CD for continuous integration and deployment. ## Key Points - local: .gitlab/ci/build.yml - local: .gitlab/ci/test.yml - project: 'my-group/ci-templates' - template: Security/SAST.gitlab-ci.yml - Use `rules` instead of `only/except` for clearer, more predictable pipeline control. - Define `default` settings (image, cache, retry) to reduce duplication across jobs. - Use `needs` (DAG) to allow jobs to start as soon as their actual dependencies finish, not waiting for the entire stage. - Set `artifacts:expire_in` to avoid filling up storage with old build outputs. - Use `include` to break large `.gitlab-ci.yml` files into manageable pieces. - Leverage GitLab Environments with deployment tracking and rollback. - Use `interruptible: true` on jobs that can be safely cancelled when new commits are pushed. - Use `resource_group` to prevent concurrent deployments to the same environment.
skilldb get cicd-patterns-skills/Gitlab CIFull skill: 244 linesGitLab CI — CI/CD
You are an expert in GitLab CI/CD for continuous integration and deployment.
Overview
GitLab CI/CD is configured through a .gitlab-ci.yml file at the repository root. Pipelines consist of stages that run sequentially, with jobs within a stage running in parallel. GitLab provides shared runners or supports self-managed runners. It offers deep integration with GitLab's merge requests, environments, container registry, and package registry.
Setup & Configuration
The pipeline is defined in .gitlab-ci.yml. GitLab automatically detects this file and runs pipelines on push events.
Basic pipeline:
stages:
- build
- test
- deploy
default:
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
test:
stage: test
script:
- npm ci
- npm test
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
deploy-staging:
stage: deploy
script:
- ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
Core Patterns
Rules-Based Pipeline Control
rules replaces the older only/except syntax for controlling when jobs run:
deploy-production:
stage: deploy
script:
- ./deploy.sh production
environment:
name: production
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
- if: $CI_PIPELINE_SOURCE == "schedule"
when: never
- when: never
Includes and Templates
Split large configs into reusable files:
include:
- local: .gitlab/ci/build.yml
- local: .gitlab/ci/test.yml
- project: 'my-group/ci-templates'
ref: main
file: '/templates/deploy.yml'
- template: Security/SAST.gitlab-ci.yml
Define reusable job templates with extends:
.deploy-template:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl
script:
- ./deploy.sh $ENVIRONMENT
deploy-staging:
extends: .deploy-template
variables:
ENVIRONMENT: staging
environment:
name: staging
deploy-production:
extends: .deploy-template
variables:
ENVIRONMENT: production
environment:
name: production
when: manual
Multi-Project Pipelines
Trigger pipelines in other projects:
trigger-downstream:
stage: deploy
trigger:
project: my-group/downstream-project
branch: main
strategy: depend
variables:
UPSTREAM_VERSION: $CI_COMMIT_TAG
Parent-Child Pipelines
Generate dynamic pipeline configurations:
generate-config:
stage: build
script:
- python generate-pipeline.py > child-pipeline.yml
artifacts:
paths:
- child-pipeline.yml
trigger-child:
stage: test
trigger:
include:
- artifact: child-pipeline.yml
job: generate-config
strategy: depend
Parallel and Matrix Jobs
test:
stage: test
parallel:
matrix:
- RUBY_VERSION: ["3.1", "3.2", "3.3"]
DB: ["postgres", "mysql"]
image: ruby:${RUBY_VERSION}
script:
- bundle install
- bundle exec rspec
services:
- name: ${DB}:latest
DAG (Directed Acyclic Graph) Pipelines
Control job dependencies independent of stage ordering:
build-frontend:
stage: build
script: npm run build:frontend
build-backend:
stage: build
script: npm run build:backend
test-frontend:
stage: test
needs: [build-frontend]
script: npm run test:frontend
test-backend:
stage: test
needs: [build-backend]
script: npm run test:backend
Core Philosophy
GitLab CI's strength is its deep integration with the entire GitLab platform — merge requests, environments, container registry, package registry, and security scanning all connect through the pipeline without third-party plugins or marketplace actions. This integration means the pipeline is not just a build system but a unified delivery platform where every stage from code to production is visible in a single interface. The trade-off is vendor coupling: the more GitLab-specific features you use, the harder it becomes to migrate. Use the integration where it adds genuine value (environments, merge request pipelines, security templates) and keep the core build logic portable.
The rules system is GitLab CI's most important configuration primitive and the one most teams underuse. Rules replace the older only/except keywords with a declarative, composable way to control when jobs run. The critical mental model is that rules are evaluated top-to-bottom and the first matching rule wins. This means you can express complex conditional logic — run on tags but not schedules, run manually on main but automatically on feature branches — without nested conditionals or scripting. Mastering rules is the difference between a pipeline that does what you expect and one that surprises you.
Parent-child pipelines and includes transform GitLab CI from a single-file configuration into a modular system. For monorepos, a parent pipeline can detect which components changed and trigger only the relevant child pipelines, keeping build times proportional to the change rather than the repository size. For multi-team organizations, shared CI templates published in a central project let platform teams provide vetted, secure pipeline components that product teams consume without copying YAML. This separation of concerns — platform teams own the how, product teams own the what — scales CI governance across the organization.
Anti-Patterns
-
Using
only/exceptinstead ofrules. The legacyonly/exceptsyntax is less expressive, harder to reason about, and cannot be combined withrulesin the same job. Standardize onrulesfor all pipeline control logic to avoid confusing evaluation behavior. -
Stage-based parallelism without
needs. By default, GitLab runs all jobs in a stage after every job in the previous stage finishes. Withoutneeds(DAG mode), a fast frontend test waits for a slow backend build to complete even though they are independent. Useneedsto express actual dependencies so jobs start as soon as their prerequisites finish. -
Monolithic
.gitlab-ci.ymlfiles. A single configuration file with hundreds of jobs is unmaintainable and hard to review. Useincludeto split configuration by component, team, or concern, and useextendswith hidden jobs (.template-name) to reduce duplication. -
Cache-dependent correctness. GitLab CI caches are best-effort and per-runner by default. A pipeline that fails on cache miss is a pipeline that will fail unpredictably. Use artifacts for files that must be passed between jobs; use caches only for speeding up dependency installation.
-
Unprotected manual production deploys. Using
when: manualon a production deploy job withoutallow_failure: falsemeans the pipeline shows as passed even if no one clicks the deploy button. Combine manual triggers withallow_failure: falseand protected branch/variable restrictions to enforce the deployment gate.
Best Practices
- Use
rulesinstead ofonly/exceptfor clearer, more predictable pipeline control. - Define
defaultsettings (image, cache, retry) to reduce duplication across jobs. - Use
needs(DAG) to allow jobs to start as soon as their actual dependencies finish, not waiting for the entire stage. - Set
artifacts:expire_into avoid filling up storage with old build outputs. - Use
includeto break large.gitlab-ci.ymlfiles into manageable pieces. - Leverage GitLab Environments with deployment tracking and rollback.
- Use
interruptible: trueon jobs that can be safely cancelled when new commits are pushed. - Use
resource_groupto prevent concurrent deployments to the same environment. - Configure
retryon flaky jobs with specific failure reasons:retry: { max: 2, when: [runner_system_failure] }. - Use protected branches and protected variables to restrict secret access.
Common Pitfalls
- Jobs in the same stage run in parallel by default; assuming sequential execution within a stage causes race conditions.
only/exceptandrulescannot be mixed in the same job; this causes a validation error.- Cache is best-effort and not guaranteed; never rely on cache for correctness—use artifacts for required files.
- Variables defined in
variables:at the job level override project-level CI/CD variables of the same name. when: manualjobs do not block the pipeline by default; useallow_failure: falseto make them blocking.- Not setting
GIT_DEPTHorGIT_STRATEGYwastes time cloning full history when only shallow clones are needed. - Using
dependencies: []to skip artifact downloads but forgetting thatneedsalso controls artifact downloads. - Dynamic child pipelines require the generated YAML to be a valid artifact before triggering.
Install this skill directly: skilldb add cicd-patterns-skills
Related Skills
Artifact Management
Build artifact handling, dependency caching, container image management, and artifact registry patterns for CI/CD
Buildkite
Buildkite pipelines including dynamic pipeline generation, agent targeting, plugins, and hybrid cloud CI/CD
Circleci
CircleCI configuration including orbs, workflows, executors, and pipeline parameterization for CI/CD
Deployment Strategies
Blue/green, canary, rolling, and feature-flag deployment strategies with platform-specific implementation patterns
Environment Management
Managing secrets, environment variables, deployment environments, and configuration across CI/CD pipelines
Github Actions
GitHub Actions workflows for CI/CD automation including reusable workflows, matrix builds, and deployment pipelines