stagesDefines the ordered list of stages. Jobs in the same stage run in parallel; the next stage starts only after all jobs in the current stage succeed. Any job without a `stage` key defaults to the `test` stage.
stages: - build - test - deploy
GitLab CI/CD cheat sheet — 80+ entries covering pipeline structure, job config, rules, variables, artifacts, runners, templates, security, and the GitLab API.
stagesDefines the ordered list of stages. Jobs in the same stage run in parallel; the next stage starts only after all jobs in the current stage succeed. Any job without a `stage` key defaults to the `test` stage.
stages: - build - test - deploy
workflow:rulesControls whether a pipeline is created at all. Evaluated before any job. Use to prevent duplicate pipelines (e.g., both a branch push and an MR event creating separate pipelines for the same commit).
⚠ Gotcha: Without `workflow:rules`, GitLab creates both a branch pipeline and an MR pipeline for the same commit. This is the most common cause of duplicate pipeline runs.
workflow:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: '$CI_COMMIT_TAG'defaultSets default values for all jobs in the pipeline. Supports: `image`, `before_script`, `after_script`, `tags`, `retry`, `timeout`, `interruptible`, `artifacts`, `cache`. Job-level keys override these defaults.
default:
image: node:20-alpine
before_script:
- npm ci --prefer-offline
retry:
max: 2
when: runner_system_failure
interruptible: trueincludeImports YAML from external files into the current pipeline. Types: `local` (same repo), `project` (another GitLab repo), `remote` (HTTP URL), `template` (GitLab-provided templates). Files are merged, with later files overriding earlier ones.
include:
- local: '.gitlab/ci/build.yml'
- template: 'Security/SAST.gitlab-ci.yml'
- project: 'my-group/ci-templates'
ref: main
file: '/templates/docker-build.yml'trigger (multi-project pipeline)Triggers a pipeline in another project. The downstream pipeline runs asynchronously unless `strategy: depend` is set, which causes the trigger job to mirror the downstream pipeline status.
⚠ Gotcha: Without `strategy: depend`, the trigger job always shows "passed" regardless of what happens in the downstream pipeline — broken deployments can go undetected.
deploy-downstream:
trigger:
project: my-group/deploy-repo
branch: main
strategy: depend
variables:
IMAGE_TAG: $CI_COMMIT_SHAtrigger (child pipeline)Triggers a child pipeline from a generated or static YAML file within the same project. Child pipelines appear nested under the parent. Used for dynamic pipeline generation (`generate-config` job writes YAML, trigger reads it).
generate:
script: python generate_pipeline.py > pipeline.yml
artifacts:
paths: [pipeline.yml]
run-child:
trigger:
include:
- artifact: pipeline.yml
job: generate.gitlab-ci.yml skeletonMinimal working pipeline with three stages. The `image` key selects the Docker image for all jobs by default. Each stage block can contain multiple jobs that run in parallel.
stages: [build, test, deploy]
image: alpine:3.19
build-job:
stage: build
script:
- echo "Building…"
test-job:
stage: test
script:
- echo "Testing…"
deploy-job:
stage: deploy
script:
- echo "Deploying…"
rules:
- if: '$CI_COMMIT_BRANCH == "main"'include:componentReferences a CI/CD component from the GitLab catalog (GitLab 16.0+). Components are versioned, self-documented pipeline building blocks. Use `~latest` or pin a specific version tag for stability.
include:
- component: gitlab.com/components/sast/sast@~latest
inputs:
stage: securityscript / before_script / after_script`script` is the only required key in a job — a list of shell commands. `before_script` runs before `script` (and overrides the global default). `after_script` always runs after `script`, even if `script` fails, so use it for teardown.
test-unit:
stage: test
before_script:
- cp .env.test .env
script:
- npm test
after_script:
- rm -f .envallow_failure`allow_failure: true` lets the pipeline continue even if this job fails — the job shows an orange warning icon instead of red. Use for non-blocking quality checks. `allow_failure:exit_codes` lets you specify which exit codes are allowed.
lint:
script: npm run lint
allow_failure: true
# Allow only specific exit code
dependency-check:
script: ./check-deps.sh
allow_failure:
exit_codes: [2, 3]retryRetries a failed job automatically. `max` sets the retry count (1 or 2). `when` restricts retries to specific failure types: `runner_system_failure`, `runner_unsupported`, `stuck_or_timeout_failure`, `script_failure`, `api_failure`, `always`.
deploy:
script: ./deploy.sh
retry:
max: 2
when:
- runner_system_failure
- stuck_or_timeout_failuretimeoutSets the maximum duration for a job. Overrides the project-level and runner-level timeout. Uses Go duration format: `30m`, `1h 30m`, `2h`. The job is cancelled if it exceeds the timeout.
e2e-tests: script: npm run test:e2e timeout: 2h 30m unit-tests: script: npm test timeout: 10 minutes
when`when` controls when a job runs in relation to the previous stage's status. Values: `on_success` (default), `on_failure` (only if previous failed), `always` (regardless), `manual` (needs human click), `delayed` (after `start_in` delay), `never`.
⚠ Gotcha: `when: manual` blocks the pipeline at that stage unless `allow_failure: true` is also set. Without it, subsequent stages will not run until someone clicks the job.
notify-on-failure: script: ./notify.sh when: on_failure deploy-to-prod: script: ./deploy-prod.sh when: manual allow_failure: false canary-deploy: script: ./canary.sh when: delayed start_in: 30 minutes
environmentAssociates a job with a GitLab environment (creates it if it does not exist). Enables deployment tracking, environment-scoped variables, and the Environments dashboard. Use `environment:url` to show a live link in the GitLab UI.
deploy-staging:
script: ./deploy.sh staging
environment:
name: staging
url: https://staging.example.com
deploy-review:
script: ./deploy.sh review/$CI_COMMIT_REF_SLUG
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop-reviewcoverageExtracts a coverage percentage from the job's script output using a regex. The captured number is shown in the GitLab UI, MR widget, and pipeline badges. Pair with `artifacts:reports:coverage_report` for line-level coverage.
test: script: pytest --cov=myapp --cov-report=term-missing coverage: '/TOTAL.*s+(d+%)$/' test-node: script: npm test -- --coverage coverage: '/Liness*:s*(d+.?d*)%/'
pagesSpecial job name that deploys to GitLab Pages. Must produce an `artifacts:paths: [public]` directory. Pages jobs must be in the `pages` or `pages:` namespace and run on the default branch.
pages:
stage: deploy
script:
- npm run build
- mv dist/ public/
artifacts:
paths:
- public
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'inheritControls whether job-level `variables` and `default` values are inherited from the global scope. `inherit:variables: false` prevents global variables from leaking into a job. `inherit:default: false` skips global `default` keys.
deploy:
script: ./deploy.sh
inherit:
default: false
variables:
- DEPLOY_TOKEN
- TARGET_ENVrules:ifConditional job inclusion using CI/CD variable expressions. Rules are evaluated top-to-bottom; the first matching rule wins. If no rule matches, the job is excluded by default. Use `when: never` as a catch-all to explicitly exclude.
deploy:
script: ./deploy.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: never
- when: neverrules:changesRuns a job only when specified files have changed relative to the comparison target. Ideal for monorepo pipelines where only affected services should build. Use with `compare_to` to specify the base branch.
⚠ Gotcha: In new branches with no comparison target, `rules:changes` always returns true. Add a fallback `if` clause or use `compare_to` to prevent unnecessary runs.
build-frontend:
script: npm run build
rules:
- changes:
paths:
- frontend/**/*
- package-lock.json
compare_to: 'refs/heads/main'rules:existsRuns a job only when specified files exist in the repository. Useful for optional build steps (e.g., only run a Docker build if a `Dockerfile` exists). Accepts glob patterns.
docker-build:
script: docker build .
rules:
- exists:
- Dockerfile
- exists:
- 'services/*/Dockerfile'needsSpecifies job dependencies for DAG (directed acyclic graph) pipelines. A job with `needs` starts as soon as all listed jobs finish, ignoring stage order. Dramatically reduces pipeline duration for large pipelines.
test-unit:
stage: test
needs: [build]
test-e2e:
stage: test
needs:
- job: build
artifacts: true
- job: seed-db
artifacts: falsedependenciesLimits which previous jobs' artifacts are downloaded. By default every job downloads all artifacts from previous stages. Set `dependencies: []` to download no artifacts, or list specific jobs to download only theirs.
⚠ Gotcha: When using `needs`, set `artifacts: true/false` on each need entry instead of using the top-level `dependencies` key — they conflict when both are present.
deploy:
stage: deploy
script: ./deploy.sh
dependencies:
- build-app
e2e-tests:
stage: test
script: npm run test:e2e
dependencies: [] # don't download any artifactsonly / except (legacy)**Deprecated in favor of `rules`.** `only:branches` / `only:refs` limits jobs to specific branches. `except` excludes. Does not support boolean expressions. Cannot handle MR pipeline deduplication cleanly.
# Legacy — prefer rules:
deploy:
script: ./deploy.sh
only:
- main
- tags
except:
- /^experiment/.*/Predefined variables (common)GitLab sets dozens of `CI_*` variables automatically. Most-used: `$CI_COMMIT_BRANCH` (current branch name), `$CI_PIPELINE_SOURCE` (push/merge_request_event/schedule/api/trigger), `$CI_COMMIT_TAG`, `$CI_DEFAULT_BRANCH`, `$CI_COMMIT_SHA`, `$CI_ENVIRONMENT_NAME`.
# Common rule conditions: rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' - if: '$CI_PIPELINE_SOURCE == "schedule"' - if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/' - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
rules:variablesSets job variables conditionally based on which rule matched. Allows a single job definition to behave differently across environments without duplicate jobs.
build:
script: docker build -t $IMAGE_TAG .
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:latest
- if: '$CI_COMMIT_TAG'
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
- variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHAvariables (global)Declares variables available to all jobs. Can be overridden at the job level. Variables set here are visible in the UI pipeline view (avoid secrets). Use GitLab CI/CD Settings → Variables for secrets.
variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "/certs" NODE_ENV: production FF_USE_FASTZIP: "true" # GitLab feature flag
variables (job-level)Scoped to a single job. Overrides global variables with the same name. Can reference other variables with `$VAR` or `${VAR}` syntax. Supports `expand` flag to disable variable expansion for values that contain `$`.
test-postgres:
image: postgres:15
variables:
POSTGRES_DB: test_db
POSTGRES_USER: ci
POSTGRES_PASSWORD: ""
CONN_STR: "postgres://${POSTGRES_USER}@postgres/${POSTGRES_DB}"$CI_REGISTRY / Docker image variables`$CI_REGISTRY` is the GitLab Container Registry hostname. `$CI_REGISTRY_IMAGE` is the full image path for the current project. `$CI_REGISTRY_USER` and `$CI_REGISTRY_PASSWORD` are temporary credentials for the current pipeline.
build-image:
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHAdotenv artifact (pass vars between jobs)Writes `KEY=VALUE` pairs to a file and declares it as a `dotenv` report artifact. Downstream jobs that reference this job in `needs` automatically receive the variables as environment variables.
build:
script:
- VERSION=$(git describe --tags)
- echo "VERSION=$VERSION" > build.env
- echo "IMAGE=$CI_REGISTRY_IMAGE:$VERSION" >> build.env
artifacts:
reports:
dotenv: build.env
deploy:
script:
- echo "Deploying $IMAGE version $VERSION"
needs:
- job: build
artifacts: truevariables:description / value / optionsAdds metadata for pipeline-level variables used in manually-triggered pipelines. `description` shows a tooltip, `value` sets the default, `options` provides a dropdown. Useful for parameterized release pipelines.
variables:
ENVIRONMENT:
value: "staging"
options:
- "staging"
- "production"
description: "Target environment for deployment"
LOG_LEVEL:
value: "info"
description: "Application log level"$CI_JOB_TOKENA short-lived token GitLab generates for each job. Used to authenticate against the GitLab API, Container Registry, Package Registry, and other projects that allow CI job token access. Expires when the job finishes.
download-package:
script:
- curl --header "JOB-TOKEN: $CI_JOB_TOKEN"
"https://gitlab.example.com/api/v4/projects/123/packages/generic/myapp/1.0/app.tar.gz"
-o app.tar.gzMasked variablesVariables marked "Masked" in GitLab Settings → CI/CD → Variables are redacted from job logs. The value must be at least 8 characters, consist of printable ASCII, and contain no newlines. Use masking for tokens, passwords, and keys.
⚠ Gotcha: Masked variables are NOT encrypted at rest in GitLab CE/EE — they are just redacted from logs. For secrets at rest, use HashiCorp Vault or a secrets management integration.
# In .gitlab-ci.yml, just reference the variable name:
deploy:
script:
- curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com/deploy
# DEPLOY_TOKEN is masked in GitLab Settings → CI/CD → Variables$CI_COMMIT_REF_SLUGURL-safe, lowercase version of the branch or tag name. Slashes are replaced with hyphens. Useful for dynamic environment names and Docker tags that must be DNS-compatible.
# Branch "feature/my-feature" → "feature-my-feature"
deploy-review:
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_COMMIT_REF_SLUG.review.example.com
script:
- kubectl create namespace $CI_COMMIT_REF_SLUG || trueartifacts:pathsUploads matching files/directories to GitLab at job completion. Available for download in the UI and to downstream jobs. Use `artifacts:name` to set the archive filename, `artifacts:when` to upload on failure.
build:
script: npm run build
artifacts:
name: "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG"
paths:
- dist/
- coverage/
when: always # upload even if script fails
expire_in: 1 weekartifacts:expire_inSets when artifacts are automatically deleted. Accepts: `never`, `30 seconds`, `5 minutes`, `1 hour`, `1 day`, `2 weeks`, `1 month`, `1 year`. If omitted, uses the project-level or instance-level default.
build:
artifacts:
paths: [dist/]
expire_in: 30 days
test:
artifacts:
paths: [coverage/]
expire_in: never # keep test reports foreverartifacts:reports:junitUploads a JUnit XML file to GitLab. Test results appear in the MR Test Summary panel and in the pipeline Test tab. Failed tests are highlighted inline in the MR diff view.
test:
script:
- pytest --junitxml=report.xml
- npm test -- --reporters=junit --outputFile=junit.xml
artifacts:
reports:
junit:
- report.xml
- junit.xmlartifacts:reports:coverage_reportUploads a Cobertura or clover XML coverage report. GitLab renders line-level coverage highlighting in MR diffs, showing which lines are covered by tests.
test:
script: pytest --cov=myapp --cov-report=xml:coverage.xml
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xmlartifacts:reports:sast / dependency_scanningSecurity report artifacts. SAST, dependency scanning, container scanning, and secret detection results appear in the MR Security tab and in the Security Dashboard. Produced automatically by the `Security/SAST.gitlab-ci.yml` template.
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
# To consume manually:
semgrep-sast:
artifacts:
reports:
sast: gl-sast-report.jsoncacheStores directories between pipeline runs to speed up dependency installation. `key` determines cache isolation — a changed key creates a new cache entry. `policy: pull` (download only) or `push` (upload only) skips unnecessary operations.
test:
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
policy: pull-push # default: download, run, uploadcache:key:filesCreates a cache key from the hash of one or more files. The cache is automatically invalidated when the file changes (e.g., when `package-lock.json` is updated). Best practice for dependency caches.
build:
cache:
- key:
files: [package-lock.json]
paths: [node_modules/]
- key:
files: [Gemfile.lock]
paths: [vendor/bundle/]artifacts:excludeExcludes matching files from the uploaded artifact archive while keeping the broader `paths` pattern. Useful for excluding test fixtures, sourcemaps, or debug binaries from release artifacts.
build:
script: npm run build
artifacts:
paths: [dist/]
exclude:
- dist/**/*.map
- dist/**/*.test.jstagsSelects which registered runner executes the job. A runner is assigned if it has ALL the listed tags. Group/project-specific runners can be tagged (e.g., `docker`, `kubernetes`, `linux`, `gpu`) for workload routing.
build:
tags:
- docker
- linux
gpu-train:
tags:
- gpu
- cuda-12imageSpecifies the Docker image for the job (when using the Docker executor). Can be any public or private registry image. Append `@sha256:...` for digest-pinned images in security-sensitive pipelines.
test:
image: python:3.12-slim
build-go:
image: golang:1.22-alpine
security-scan:
image:
name: aquasec/trivy:0.50.1
entrypoint: [""]servicesRuns additional Docker containers alongside the job container. Services share a network namespace and are accessible by their image name as a hostname. Use for databases, message queues, and other test dependencies.
test-integration:
image: node:20
services:
- name: postgres:15
alias: db
- name: redis:7-alpine
alias: cache
variables:
POSTGRES_DB: test
POSTGRES_USER: ci
DATABASE_URL: "postgres://ci@db/test"parallel: NCreates N identical concurrent job instances. Each instance gets `$CI_NODE_INDEX` (1-based) and `$CI_NODE_TOTAL` to partition work. Use to shard a large test suite across multiple runners.
rspec:
script:
- bundle exec rspec --format progress
--only-failures-at-index $((CI_NODE_INDEX - 1))
--shard-count $CI_NODE_TOTAL
parallel: 5parallel:matrixCreates one job per combination of variable values. GitLab automatically generates unique job names for each combination. Matrix jobs run concurrently and are visible as separate rows in the pipeline graph.
build:
script: make build TARGET=$TARGET ARCH=$ARCH
parallel:
matrix:
- TARGET: [linux, darwin, windows]
ARCH: [amd64, arm64]
- TARGET: linux
ARCH: [386]runner executor typesExecutors determine how the runner runs jobs. `shell`: directly on the host. `docker`: in a fresh container per job. `kubernetes`: in a Kubernetes pod. `docker-autoscaler` / `instance` (GitLab 16+): ephemeral VMs. `docker` with `privileged: true` enables Docker-in-Docker (DinD).
# In /etc/gitlab-runner/config.toml:
[[runners]]
name = "docker-runner"
executor = "docker"
[runners.docker]
image = "alpine:latest"
privileged = true # needed for DinDDOCKER_HOST / dind serviceDocker-in-Docker pattern for building Docker images inside a CI job. Uses the `docker:dind` service and sets `DOCKER_HOST` to connect to it. Alternative: use `kaniko` or `buildah` for rootless image building.
⚠ Gotcha: DinD requires the runner to be in `privileged` mode, which is a significant security risk on shared runners. Prefer `kaniko` for shared/untrusted environments.
build-image:
image: docker:24
services:
- docker:24-dind
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
script:
- docker build -t myapp .
- docker push myappartifacts:untrackedUploads all files not tracked by git. Useful for capturing build outputs in interpreted or compiled language projects where the build tool generates files GitLab should archive.
build:
script: go build ./...
artifacts:
untracked: true
paths:
- bin/extendsInherits the configuration of another job (or hidden job starting with `.`). Deep-merges the parent and child YAML. Lists in child completely replace the parent list (no list-merge). Use hidden jobs (`.`) as abstract base templates.
.deploy-base:
stage: deploy
before_script:
- apt-get install -y curl
retry:
max: 2
deploy-staging:
extends: .deploy-base
script: ./deploy.sh staging
environment:
name: staging
deploy-prod:
extends: .deploy-base
script: ./deploy.sh prod
when: manualYAML anchors (&, *, <<)Native YAML anchors for reusing configuration within the same file. `&anchor-name` defines an anchor, `*anchor-name` references it, `<<: *anchor-name` merges it. Unlike `extends`, anchors cannot cross-file and only work within a single YAML document.
.node-setup: &node-setup
image: node:20-alpine
cache:
key:
files: [package-lock.json]
paths: [node_modules/]
test:
<<: *node-setup
script: npm test
lint:
<<: *node-setup
script: npm run lint!referenceGitLab-specific tag that references a nested key in another job or map. More powerful than YAML anchors because it can reach into nested structures. Used to compose complex `script` or `rules` blocks from multiple sources.
.setup-steps:
script:
- apt-get update -qq
- apt-get install -y curl
.prod-rules:
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
deploy:
script:
- !reference [.setup-steps, script]
- ./deploy.sh
rules: !reference [.prod-rules, rules]include:template (GitLab CI templates)GitLab ships hundreds of built-in CI templates at `gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates`. Common ones: `Security/SAST.gitlab-ci.yml`, `Code-Quality.gitlab-ci.yml`, `Docker.gitlab-ci.yml`, `Auto-DevOps.gitlab-ci.yml`.
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
- template: Code-Quality.gitlab-ci.yml
# Override a template job:
semgrep-sast:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'include:inputsPasses named inputs to a CI component or includable configuration. Components declare their inputs with `spec:inputs`; callers provide values via `inputs:`. This replaces the pattern of overriding template variables.
spec:
inputs:
stage:
default: test
image:
default: alpine
---
my-job:
stage: $[[ inputs.stage ]]
image: $[[ inputs.image ]]
script: echo "running"Hidden jobs (.job-name)Jobs prefixed with `.` are never picked up by the runner directly. They serve as abstract bases for `extends` or as anchor definitions. Use them to define shared configurations without creating actual pipeline jobs.
.docker-login:
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
.only-main:
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
build-image:
extends:
- .docker-login
- .only-main
script: docker build -t $CI_REGISTRY_IMAGE .extends: [multiple]A job can extend multiple hidden jobs or base configurations. GitLab merges them left-to-right with later entries taking priority. Avoids deep YAML nesting while composing pipeline building blocks.
.base-node:
image: node:20
cache:
key:
files: [package-lock.json]
paths: [node_modules/]
.mr-only:
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
test-on-mr:
extends:
- .base-node
- .mr-only
script: npm testid_tokens (OIDC)Issues an OpenID Connect JWT for the job. Use to authenticate against cloud providers (AWS, GCP, Azure) or Vault without storing long-lived secrets. The JWT contains claims like `sub` (project path + ref) and is valid only for the job duration.
deploy-aws:
id_tokens:
AWS_OIDC_TOKEN:
aud: https://sts.amazonaws.com
script:
- export AWS_ROLE_ARN=arn:aws:iam::123456789012:role/GitLabCI
- aws sts assume-role-with-web-identity
--role-arn $AWS_ROLE_ARN
--web-identity-token $AWS_OIDC_TOKEN
--role-session-name gitlab-ciProtected branches / environmentsVariables can be scoped to protected branches and protected environments in GitLab Settings. Only jobs running on those branches/tags/environments can access the variable. Use to prevent production secrets from leaking to feature-branch pipelines.
# In GitLab UI: Settings → CI/CD → Variables # Variable: PROD_DB_URL # Environments: production ← scoped # Protected: ✓ ← only runs on protected branches deploy-prod: environment: production script: ./deploy.sh $PROD_DB_URL # only available here
secrets (Vault integration)Fetches secrets from HashiCorp Vault using JWT authentication. The `vault` keyword requires the GitLab Vault integration (GitLab Premium or the community JWT auth method). Secrets are injected as environment variables.
deploy:
secrets:
DATABASE_PASSWORD:
vault: production/db/password@ops
API_KEY:
vault:
engine:
name: kv-v2
path: secrets
path: myapp/prod
field: api_key
script: ./deploy.shenvironment:action: stopDefines a teardown job that stops a review environment. The `stop` job must have `when: manual` or `when: always` and reference the same environment name. GitLab can trigger it automatically when an MR is merged.
deploy-review:
script: kubectl apply -f k8s/review.yaml
environment:
name: review/$CI_COMMIT_REF_SLUG
on_stop: stop-review
stop-review:
script: kubectl delete namespace $CI_COMMIT_REF_SLUG
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
when: manualCI_JOB_TOKEN allowlistBy default, `$CI_JOB_TOKEN` only authenticates against its own project. Use **Settings → CI/CD → Token Access** to grant specific projects access. This prevents token abuse across projects in a group.
# Reference another project's API using allowlisted CI_JOB_TOKEN:
fetch-shared-lib:
script:
- curl --header "JOB-TOKEN: $CI_JOB_TOKEN"
"$CI_API_V4_URL/projects/456/repository/files/lib.sh/raw?ref=main"
-o lib.shSAST / Secret Detection templatesGitLab-provided security scanning templates. Include them to get automatic vulnerability reports in MRs and the Security Dashboard. `SAST.gitlab-ci.yml` analyzes source code; `Secret-Detection.gitlab-ci.yml` finds credentials in git history.
include: - template: Security/SAST.gitlab-ci.yml - template: Security/Secret-Detection.gitlab-ci.yml - template: Security/Dependency-Scanning.gitlab-ci.yml - template: Security/Container-Scanning.gitlab-ci.yml
interruptibleMarks a job as safe to cancel when a newer pipeline starts for the same ref. When `interruptible: true` on all jobs, GitLab auto-cancels older in-progress pipelines on push, saving runner minutes on outdated commits.
default: interruptible: true # applies to all jobs # Override for deploy jobs that must not be interrupted: deploy-prod: interruptible: false script: ./deploy.sh
resource_groupPrevents concurrent runs of jobs sharing the same resource group name. When a job starts, it acquires an exclusive lock on the group. Subsequent pipeline jobs with the same group queue and run in order. Essential for serializing deploys.
deploy-prod: script: ./deploy.sh production resource_group: production-deploy environment: production # Only one deploy-prod can run at a time across all pipelines
[skip ci] / [ci skip]Including `[skip ci]` or `[ci skip]` in the commit message prevents a pipeline from being created. Useful for documentation-only commits, version bumps, or automated commits that should not trigger CI.
git commit -m "chore: update changelog [skip ci]" # Or use the CI_SKIP variable in API triggers: curl --request POST \ --form "token=$CI_JOB_TOKEN" \ --form "ref=main" \ --form "variables[CI_SKIP]=true" \ "https://gitlab.example.com/api/v4/projects/1/trigger/pipeline"
Cache policy: pullSets `policy: pull` on jobs that only read the cache (e.g., parallel test jobs that all need the same `node_modules`). These jobs skip the cache-upload step, reducing S3/MinIO traffic and job duration.
install:
script: npm ci
cache:
key:
files: [package-lock.json]
paths: [node_modules/]
policy: pull-push # download + upload
test:
script: npm test
needs: [install]
cache:
key:
files: [package-lock.json]
paths: [node_modules/]
policy: pull # download only — don't re-uploadSparse checkout / shallow cloneControl git clone depth with `GIT_DEPTH` to speed up jobs on repos with long history. `GIT_STRATEGY: clone|fetch|none` controls whether the runner does a full clone, incremental fetch, or skips cloning entirely (use with artifact-only jobs).
variables:
GIT_DEPTH: 1 # shallow clone — fastest
GIT_STRATEGY: fetch # reuse existing checkout
deploy:
variables:
GIT_STRATEGY: none # no checkout needed, uses artifacts only
script: ./deploy.shDAG pipeline patternFull DAG pipeline: build fans out to multiple parallel test types, then integration tests need all unit tests, and finally deploy needs all tests. No stages needed — `needs` drives the execution order.
stages: [build, test, deploy] build-app: stage: build script: npm run build test-unit: stage: test needs: [build-app] script: npm test test-lint: stage: test needs: [build-app] script: npm run lint test-e2e: stage: test needs: [test-unit, test-lint] script: npm run test:e2e deploy: stage: deploy needs: [test-e2e] script: ./deploy.sh
rules:if with $CI_COMMIT_MESSAGEUse `$CI_COMMIT_MESSAGE` in rules to create conditional pipelines based on commit message keywords. Useful for triggering extended test suites, forcing a release build, or skipping expensive jobs from commits.
full-test-suite:
script: npm run test:all
rules:
- if: '$CI_COMMIT_MESSAGE =~ /\[full-tests\]/'
- if: '$CI_COMMIT_BRANCH == "main"'
nightly-security-scan:
script: ./security-scan.sh
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'POST /projects/:id/trigger/pipelineTriggers a pipeline via the API using a pipeline trigger token. Variables can be passed as form fields. Use in external systems (GitHub Actions, webhooks, Slack bots) to start GitLab CI pipelines programmatically.
curl -X POST \ --form "token=$TRIGGER_TOKEN" \ --form "ref=main" \ --form "variables[DEPLOY_ENV]=production" \ "https://gitlab.example.com/api/v4/projects/123/trigger/pipeline"
GET /projects/:id/pipelinesLists pipelines for a project. Filter by `status` (created/waiting_for_resource/preparing/pending/running/success/failed/canceled/skipped/manual/scheduled), `ref`, `sha`, `username`, `source`, `updated_after`.
# List recent failed pipelines on main: curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.example.com/api/v4/projects/123/pipelines?ref=main&status=failed&per_page=10"
GET /projects/:id/jobs/:job_id/artifactsDownloads a job's artifact archive. Use the optional `artifact_path` query param to download a specific file instead of the entire archive. Requires authentication or a public project.
# Download the entire artifact archive: curl --header "PRIVATE-TOKEN: $TOKEN" \ "https://gitlab.example.com/api/v4/projects/123/jobs/456/artifacts" \ -o artifacts.zip # Download a specific file: curl --header "PRIVATE-TOKEN: $TOKEN" \ "https://gitlab.example.com/api/v4/projects/123/jobs/456/artifacts/dist/app.js" \ -o app.js
GET /projects/:id/pipelines/:pipeline_id/jobsLists all jobs for a pipeline. Useful for CI/CD reporting scripts, dashboards, and finding job IDs for subsequent artifact downloads. Filter by `scope` to get only failed, running, or manual jobs.
# List all failed jobs in a pipeline: curl --header "PRIVATE-TOKEN: $TOKEN" \ "https://gitlab.example.com/api/v4/projects/123/pipelines/789/jobs?scope[]=failed"
POST /projects/:id/pipelineCreates a pipeline for a specific ref without a trigger token (uses a personal/group/project access token). Supports passing variables as a JSON array. Preferred over the trigger endpoint for authenticated scripts.
curl -X POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"ref": "main",
"variables": [
{"key": "DEPLOY_ENV", "value": "production"},
{"key": "IMAGE_TAG", "value": "v1.2.3"}
]
}' \
"https://gitlab.example.com/api/v4/projects/123/pipeline"POST /projects/:id/jobs/:job_id/playManually triggers a `when: manual` job via the API. Use in release automation scripts to promote artifacts through environments without clicking the GitLab UI.
# Trigger a manual deploy job: curl -X POST \ --header "PRIVATE-TOKEN: $TOKEN" \ "https://gitlab.example.com/api/v4/projects/123/jobs/456/play"
GET /projects/:id/pipelines/:id (status polling)Gets the status of a specific pipeline. Poll this endpoint to wait for a triggered pipeline to complete. Check the `status` field: `success`, `failed`, `canceled`, `running`, `pending`.
#!/bin/bash
PIPELINE_ID=$1
while true; do
STATUS=$(curl -s --header "PRIVATE-TOKEN: $TOKEN" \
"https://gitlab.example.com/api/v4/projects/123/pipelines/$PIPELINE_ID" \
| jq -r '.status')
echo "Status: $STATUS"
[[ "$STATUS" =~ ^(success|failed|canceled)$ ]] && break
sleep 10
donePOST /projects/:id/statuses/:shaSets a commit status from an external system. Useful for integrating third-party CI tools, code quality gates, or security scanners with GitLab's commit status API so their results appear in MRs.
curl -X POST \ --header "PRIVATE-TOKEN: $TOKEN" \ --data "state=success&name=external-quality-check&description=All checks passed" \ "https://gitlab.example.com/api/v4/projects/123/statuses/$CI_COMMIT_SHA"
Searchable GitLab CI/CD cheat sheet with 80+ entries across ten sections. Pipeline structure: `.gitlab-ci.yml` skeleton, `stages`, `workflow:rules`, `default`, global `include`, multi-project pipelines. Job configuration: `script`, `before_script`, `after_script`, `allow_failure`, `retry`, `timeout`, `when`, `environment`. Rules: `rules:if` with predefined variables, `rules:changes`, `rules:exists`, `needs` DAG, merge-request pipelines, `only`/`except`. Variables: job-level and global variables, predefined `CI_*` vars, masked and protected variables, `dotenv` artifacts. Artifacts: `artifacts:paths`, `artifacts:reports` (JUnit, coverage, SAST), `artifacts:expire_in`, `cache` with `key` and `policy`. Runners: `tags`, Docker `image`, `services`, `parallel`, `parallel:matrix`. Templates: `extends`, `!reference`, `include:template`, `include:project`, `include:component`, YAML anchors. Security: protected variables, environments, `id_tokens` (OIDC), masked variables, secrets from HashiCorp Vault / GCP / AWS. Optimization: `interruptible`, `resource_group`, `needs` for DAG, cache strategies, `inherit:variables`, skipping pipelines. HTTP API: trigger pipeline, list jobs, download artifacts, pipeline status. Every entry has bilingual text, copy-ready examples, and pitfall callouts. Search, category chips, one-click copy — all in-browser.
Paste or drop your content into the tool panel.
Click the button. All processing is local in your browser.
Copy the result or download to disk in one click.
Use it in the small gaps between coding, reviewing, debugging, and shipping.
These links move the current task into a more complete workflow.
You keep seeing pipelines fire on feature branches. Open the cheat sheet, grab the `workflow:rules` block with `$CI_COMMIT_BRANCH == "main"`, paste it at the top of your `.gitlab-ci.yml`, and the problem is gone in one commit. The cheat sheet also shows the MR-pipeline variant and the combined rule that runs on both main and MR events.
Your CI takes 20 minutes because tests run sequentially. Open the cheat sheet, copy the `parallel:matrix` example with `NODE_VERSION: [16, 18, 20]`, paste it into your test job, and all three versions run concurrently. Total time drops from 20 min to 7 min.
Your build job generates a Docker image tag. Open the cheat sheet, find the `dotenv` artifact entry, copy the two-step pattern (write to `.env` → `artifacts:reports:dotenv`), and the deploy job picks up `$IMAGE_TAG` automatically via `needs`. No sed hacks, no pipeline triggers.
Using `only: [main]` instead of `rules:if: '$CI_COMMIT_BRANCH == "main"'` — `only` doesn't work correctly with merge-request pipelines and may cause duplicate runs.
Putting artifacts and cache in the wrong job — cache is for reusing dependencies across runs; artifacts are for passing files between jobs in the same pipeline.
Forgetting `needs` when using DAG — without `needs`, all jobs in a stage still wait for the previous stage to complete, nullifying any parallelism benefit.
Using `when: always` on deploy jobs — this causes deploys even after failed tests. Use `when: on_success` (the default) or add `rules:if` guards.
Setting CI_*_ predefined variables as job variables — you cannot override most GitLab-managed `CI_` prefixed variables; use custom names instead.
Everything runs in your browser. The cheat sheet is a static in-memory array and the search box, category chips, and copy button never make a network request. Nothing you type is logged or sent anywhere. Works offline or on a jump host.
Folks in your role tend to reach for these alongside this tool.