Skip to content

GitLab CI/CD Cheatsheet

Installation

GitLab Runner Installation

Platform Command
Ubuntu/Debian curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" \| sudo bash && sudo apt-get install gitlab-runner
CentOS/RHEL curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh" \| sudo bash && sudo yum install gitlab-runner
macOS sudo curl --output /usr/local/bin/gitlab-runner "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64" && sudo chmod +x /usr/local/bin/gitlab-runner
Windows Invoke-WebRequest -Uri "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe" -OutFile "C:\GitLab-Runner\gitlab-runner.exe"
Docker docker run -d --name gitlab-runner --restart always -v /srv/gitlab-runner/config:/etc/gitlab-runner -v /var/run/docker.sock:/var/run/docker.sock gitlab/gitlab-runner:latest
Kubernetes (Helm) helm repo add gitlab https://charts.gitlab.io && helm install gitlab-runner gitlab/gitlab-runner --set gitlabUrl=https://gitlab.com/

Verification

Command Description
gitlab-runner --version Check installed GitLab Runner version
gitlab-runner verify Verify runner can connect to GitLab

Basic Commands

Runner Management

Command Description
gitlab-runner register Register a new runner interactively
gitlab-runner list List all registered runners
gitlab-runner start Start the runner service
gitlab-runner stop Stop the runner service
gitlab-runner restart Restart the runner service
gitlab-runner status Check current runner status
gitlab-runner unregister --url <URL> --token <TOKEN> Unregister a specific runner
gitlab-runner unregister --all-runners Unregister all runners
gitlab-runner run Run runner in foreground for debugging
gitlab-runner exec docker <job_name> Execute a single job locally for testing

Pipeline Operations (GitLab CLI)

Command Description
glab ci lint Validate .gitlab-ci.yml syntax locally
glab ci view View CI/CD configuration for current project
glab ci trace <job-id> View real-time logs for a specific job
glab ci list List all pipelines for current project
glab ci retry <pipeline-id> Retry a failed pipeline
glab ci cancel <pipeline-id> Cancel a running pipeline

API-Based Pipeline Triggers

Command Description
curl -X POST -F token=<TOKEN> -F ref=main https://gitlab.com/api/v4/projects/<ID>/trigger/pipeline Trigger pipeline via API
curl -X POST -F token=<TOKEN> -F ref=main -F "variables[KEY]=value" <URL> Trigger pipeline with variables
curl --header "PRIVATE-TOKEN: <token>" "https://gitlab.com/api/v4/projects/<ID>/pipelines/<PID>" Get pipeline status via API

Advanced Usage

Advanced Runner Registration

Command Description
gitlab-runner register --non-interactive --url <URL> --registration-token <TOKEN> --executor docker --docker-image alpine:latest Register runner with inline parameters
gitlab-runner register --executor docker --docker-privileged --docker-volumes /var/run/docker.sock:/var/run/docker.sock Register Docker-in-Docker (DinD) runner
gitlab-runner register --cache-type s3 --cache-s3-bucket-name <bucket> --cache-s3-bucket-location us-east-1 Configure runner with S3 cache
gitlab-runner register --executor kubernetes --kubernetes-namespace gitlab-runner Register Kubernetes executor
gitlab-runner register --docker-network-mode <network> Configure runner with specific Docker network
gitlab-runner register --tag-list "docker,aws" --run-untagged=true Register runner with specific tags

Advanced Pipeline Operations

Command Description
curl --header "PRIVATE-TOKEN: <token>" "https://gitlab.com/api/v4/projects/<ID>/jobs/<JID>/artifacts" -o artifacts.zip Download job artifacts via API
curl --request POST --header "PRIVATE-TOKEN: <token>" --form "description=Nightly" --form "ref=main" --form "cron=0 2 * * *" <URL> Create pipeline schedule via API
curl --request POST --form "token=$CI_JOB_TOKEN" --form "ref=main" <DOWNSTREAM_URL> Trigger multi-project pipeline
gitlab-runner exec docker --docker-image alpine:latest <job_name> Test runner executor locally

Monitoring and Debugging

Command Description
sudo journalctl -u gitlab-runner -f View runner logs in real-time (Linux)
gitlab-runner --debug run Run runner with verbose debug logging
curl http://localhost:9252/metrics Access runner Prometheus metrics
docker logs -f gitlab-runner View Docker runner logs
kubectl logs -f -n gitlab-runner <pod-name> View Kubernetes runner logs

Configuration Management

Command Description
sudo nano /etc/gitlab-runner/config.toml Edit runner configuration file (Linux)
gitlab-runner verify --delete Remove invalid runners from config
gitlab-runner run-single --url <URL> --token <TOKEN> Run a single job without registration

Configuration

Basic .gitlab-ci.yml Structure

# Define pipeline stages
stages:
  - build
  - test
  - deploy

# Global variables
variables:
  DOCKER_DRIVER: overlay2
  DATABASE_URL: "postgres://localhost/db"

# Global scripts executed before each job
before_script:
  - echo "Pipeline started at $(date)"
  - export PATH=$PATH:/custom/bin

# Global scripts executed after each job
after_script:
  - echo "Cleaning up..."

# Basic job definition
build_app:
  stage: build
  image: node:16-alpine
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
  only:
    - main
    - merge_requests
  tags:
    - docker

Advanced Pipeline Configuration

# Include external configurations
include:
  - project: 'my-group/ci-templates'
    ref: main
    file: '/templates/.gitlab-ci-template.yml'
  - remote: 'https://example.com/ci-template.yml'
  - local: '/templates/security-scan.yml'
  - template: Security/SAST.gitlab-ci.yml

# Workflow rules for pipeline execution
workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == "main"'
    - if: '$CI_COMMIT_TAG'
    - when: never

# Job with complex rules
deploy_production:
  stage: deploy
  script:
    - ./deploy.sh production
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: manual
    - if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
      when: on_success
  environment:
    name: production
    url: https://prod.example.com
    on_stop: stop_production

Parallel and Matrix Jobs

# Parallel execution
test:
  stage: test
  parallel: 5
  script:
    - bundle exec rspec

# Matrix builds
test_matrix:
  parallel:
    matrix:
      - NODE_VERSION: ['14', '16', '18']
        OS: ['linux', 'windows']
  image: node:${NODE_VERSION}
  script:
    - npm test

Artifacts and Cache Configuration

build:
  stage: build
  script:
    - make build
  artifacts:
    name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME"
    paths:
      - binaries/
      - build/
    exclude:
      - binaries/**/*.tmp
    reports:
      junit: test-results.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
    expire_in: 30 days
    when: on_success
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull-push

Docker and Services Configuration

build_docker:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

Dynamic Child Pipelines

generate_config:
  stage: build
  script:
    - ./generate-ci-config.sh > generated-config.yml
  artifacts:
    paths:
      - generated-config.yml

trigger_child:
  stage: deploy
  trigger:
    include:
      - artifact: generated-config.yml
        job: generate_config
    strategy: depend

Runner Configuration File (config.toml)

concurrent = 10
check_interval = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "docker-runner"
  url = "https://gitlab.com/"
  token = "TOKEN"
  executor = "docker"
  [runners.custom_build_dir]
  [runners.cache]
    Type = "s3"
    Path = "cache"
    Shared = true
    [runners.cache.s3]
      ServerAddress = "s3.amazonaws.com"
      BucketName = "runner-cache"
      BucketLocation = "us-east-1"
  [runners.docker]
    tls_verify = false
    image = "alpine:latest"
    privileged = true
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"]
    shm_size = 0

Common Use Cases

Use Case 1: Build and Test Node.js Application

stages:
  - build
  - test
  - deploy

variables:
  NODE_ENV: production

build:
  stage: build
  image: node:16-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
      - node_modules/
    expire_in: 1 hour
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - .npm/

test:unit:
  stage: test
  image: node:16-alpine
  dependencies:
    - build
  script:
    - npm run test:unit
  coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
  artifacts:
    reports:
      junit: junit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

test:integration:
  stage: test
  image: node:16-alpine
  services:
    - postgres:13
  variables:
    POSTGRES_DB: test_db
    POSTGRES_USER: test_user
    POSTGRES_PASSWORD: test_password
  script:
    - npm run test:integration

Use Case 2: Docker Build and Push to Registry

stages:
  - build
  - scan
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

build_image:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $IMAGE_TAG .
    - docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
    - docker push $IMAGE_TAG
    - docker push $CI_REGISTRY_IMAGE:latest
  only:
    - main
    - tags

scan_image:
  stage: scan
  image: aquasec/trivy:latest
  script:
    - trivy image --severity HIGH,CRITICAL $IMAGE_TAG
  allow_failure: true

deploy_k8s:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context $KUBE_CONTEXT
    - kubectl set image deployment/myapp myapp=$IMAGE_TAG
    - kubectl rollout status deployment/myapp
  environment:
    name: production
    url: https://myapp.example.com
  only:
    - main

Use Case 3: Multi-Environment Deployment with Manual Approval

stages:
  - build
  - deploy_staging
  - deploy_production

build:
  stage: build
  script:
    - ./build.sh
  artifacts:
    paths:
      - build/

deploy_staging:
  stage: deploy_staging
  script:
    - ./deploy.sh staging
  environment:
    name: staging
    url: https://staging.example.com
    on_stop: stop_staging
  only:
    - main

stop_staging:
  stage: deploy_staging
  script:
    - ./cleanup.sh staging
  environment:
    name: staging
    action: stop
  when: manual

deploy_production:
  stage: deploy_production
  script:
    - ./deploy.sh production
  environment:
    name: production
    url: https://www.example.com
  when: manual
  only:
    - main
  needs:
    - deploy_staging

Use Case 4: Terraform Infrastructure Deployment

stages:
  - validate
  - plan
  - apply

variables:
  TF_ROOT: ${CI_PROJECT_DIR}/terraform
  TF_STATE_NAME: default

.terraform:
  image: hashicorp/terraform:latest
  before_script:
    - cd ${TF_ROOT}
    - terraform init -backend-config="address=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}"

validate:
  extends: .terraform
  stage: validate
  script:
    - terraform validate
    - terraform fmt -check

plan:
  extends: .terraform
  stage: plan
  script:
    - terraform plan -out=plan.tfplan
  artifacts:
    paths:
      - ${TF_ROOT}/plan.tfplan
    expire_in: 1 day

apply:
  extends: .terraform
  stage: apply
  script:
    - terraform apply -auto-approve plan.tfplan
  dependencies:
    - plan
  when: manual
  only:
    - main
  environment:
    name: production

Use Case 5: Monorepo with Selective Job Execution

workflow:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    - if: '$CI_COMMIT_BRANCH == "main"'

variables:
  FRONTEND_PATH: "apps/frontend"
  BACKEND_PATH: "apps/backend"

.changes_frontend: &changes_frontend
  changes:
    - "${FRONTEND_PATH}/**/*"
    - "package.json"
    - ".gitlab-ci.yml"

.changes_backend: &changes_backend
  changes:
    - "${BACKEND_PATH}/**/*"
    - "requirements.txt"
    - ".gitlab-ci.yml"

build_frontend:
  stage: build
  image: node:16
  script:
    - cd $FRONTEND_PATH
    - npm ci
    - npm run build
  rules:
    - <<: *changes_frontend

build_backend:
  stage: build
  image: python:3.9
  script:
    - cd $BACKEND_PATH
    - pip install -r requirements.txt
    - python -m pytest
  rules:
    - <<: *changes_backend

deploy_all:
  stage: deploy
  script:
    - ./deploy-all.sh
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      changes:
        - "${FRONTEND_PATH}/**/*"
        - "${BACKEND_PATH}/**/*"

Best Practices

  • Use cache for dependencies and artifacts for build outputs: Cache speeds up subsequent pipeline runs by storing dependencies like node_modules/, while artifacts pass build outputs between stages. Never cache build artifacts.

  • Implement proper workflow rules to avoid unnecessary pipeline runs: Use workflow:rules to control when pipelines execute, preventing waste on draft MRs or documentation-only changes. This saves runner resources and reduces costs.

  • Tag runners and jobs appropriately: Use specific tags (docker, kubernetes, gpu) to route jobs to appropriate runners. This ensures jobs run on infrastructure with required capabilities and prevents resource contention.

  • Use needs keyword for DAG pipelines: Instead of sequential stages, use needs: to create directed acyclic graphs (DAGs) that run jobs as soon as dependencies complete, significantly reducing total pipeline time.

  • Store sensitive data in CI/CD variables, never in code: Use protected and masked variables for secrets like API keys, passwords, and tokens. Enable protection to restrict access to protected branches/tags only.

  • Implement security scanning early in the pipeline: Include SAST, dependency scanning, and container scanning in early stages. Use allow_failure: true initially to avoid blocking development while teams address findings.

  • Use only:changes or rules:changes for monorepos: Trigger jobs only when relevant files change, preventing unnecessary builds and tests. This is critical for large monorepos with multiple applications.

  • Set appropriate artifact expiration times: Default artifacts to short expiration (1-7 days) to save storage costs. Use expire_in: never only for release artifacts that need permanent retention.

  • Leverage includes and templates for DRY configuration: Create reusable templates in separate repositories and include them using include:project or include:remote. This ensures consistency across projects.

  • Monitor runner capacity and scale appropriately: Track runner queue times and job wait times. Configure concurrent setting in config.toml based on available resources, and scale runners horizontally during peak times.


Troubleshooting

Issue Solution
Runner not picking up jobs Verify runner is active: gitlab-runner verify. Check runner tags match job tags. Ensure runner isn't paused in GitLab UI. Check concurrent setting in /etc/gitlab-runner/config.toml.
"This job is stuck" error No runner available with matching tags. Either add tags to job, register runner with those tags, or enable run_untagged: true in runner config.
Docker-in-Docker (DinD) permission errors Add privileged: true to runner config or use Docker socket binding: --docker-volumes /var/run/docker.sock:/var/run/docker.sock. Ensure runner has proper permissions.
Cache not working between jobs Verify cache key is consistent: use ${CI_COMMIT_REF_SLUG} or file-based keys. Check cache storage configuration (S3, GCS, etc.). Ensure cache:policy is set correctly (pull-push for read/write).
Pipeline fails with "yaml invalid" Validate syntax with glab ci lint or GitLab's CI Lint tool (CI/CD > Pipelines > CI Lint). Check indentation (use spaces, not tabs). Verify all required fields are present.
Artifacts not available in downstream jobs Use dependencies: or needs: to explicitly declare artifact dependencies. Check artifact paths are correct. Verify artifacts haven't expired (expire_in).
Jobs timing out Increase timeout in job definition: timeout: 3h. Check for hanging processes. Review runner's concurrent setting if system resources are exhausted.
"Cannot connect to Docker daemon" error Ensure Docker service is running on runner host. For Docker executor, add -v /var/run/docker.sock:/var/run/docker.sock. For DinD, use docker:dind service.
Kubernetes runner pods failing to start Check namespace exists and runner has permissions. Verify resource requests/limits. Review pod logs: kubectl logs -n gitlab-runner <pod-name>. Check image pull secrets for private registries.
Variables not being passed to jobs Check variable scope (project, group, instance). Ensure variables aren't masked when trying to print them. For protected variables, job must run on protected branch/tag. Use $ prefix: $VARIABLE_NAME.
Runner registration token invalid Token may have expired or been revoked. Get new token from GitLab UI: Settings > CI/CD > Runners. For project runners, use project-specific token. For group/instance runners, use appropriate token.
High runner CPU/memory usage Reduce concurrent value in config.toml. Implement job resource limits. Use cache to reduce redundant downloads. Consider distributing load across multiple runners.
SSL certificate verification failures Add tls_verify = false to runner config (not recommended for production). Install proper CA certificates. Use CI_SERVER_TLS_CA_FILE variable to specify CA bundle.

Important CI/CD Variables

Variable Description
CI_COMMIT_SHA Full commit SHA that triggered the pipeline
CI_COMMIT_SHORT_SHA First 8 characters of commit SHA
CI_COMMIT_REF_NAME Branch or tag name
CI_COMMIT_REF_SLUG Lowercase branch/tag name, suitable for URLs
CI_PROJECT_ID Unique project ID in GitLab
CI_PROJECT_NAME Project name
CI_PROJECT_PATH Project namespace with project name
CI_PIPELINE_ID Unique pipeline ID
CI_JOB_ID Unique job ID
CI_JOB_TOKEN Token for authenticating with GitLab API
CI_REGISTRY GitLab Container Registry address
CI_REGISTRY_IMAGE Full image path for project's container registry
CI_REGISTRY_USER Username for container registry authentication
CI_REGISTRY_PASSWORD Password for container registry authentication
CI_ENVIRONMENT_NAME Name of the environment (if