GitGitLab CI/CD

GitLab CI/CD

GitLab CI/CD is a powerful, fully integrated continuous integration and deployment system built directly into GitLab. Unlike GitHub Actions (which was added to GitHub later), CI/CD has been a core part of GitLab since its early days — it is the platform's defining feature. A single file, .gitlab-ci.yml, in the root of your repository defines your entire pipeline.

The .gitlab-ci.yml file

When you push to GitLab and a .gitlab-ci.yml file exists, GitLab automatically detects it and queues a pipeline run. No external service or webhook setup needed.

Minimal .gitlab-ci.yml

YAML
stages:
  - build
  - test
  - deploy

build-job:
  stage: build
  script:
    - echo "Compiling code..."
    - npm ci
    - npm run build

test-job:
  stage: test
  script:
    - npm run test

deploy-job:
  stage: deploy
  script:
    - ./deploy.sh
  only:
    - main
Stages, jobs, and execution order

The stages: list defines the order of execution. All jobs in the same stage run in parallel. The next stage only starts when all jobs in the previous stage pass.

Stages run in order; jobs within a stage run in parallel

YAML
stages:
  - install
  - quality
  - test
  - build
  - deploy

install-deps:
  stage: install
  script: npm ci

lint:
  stage: quality
  script: npm run lint

type-check:
  stage: quality     # runs in parallel with 'lint'
  script: npm run typecheck

unit-tests:
  stage: test
  script: npm run test:unit

integration-tests:
  stage: test        # runs in parallel with 'unit-tests'
  script: npm run test:integration

build-app:
  stage: build
  script: npm run build
Full production pipeline for a web application

.gitlab-ci.yml — complete web app pipeline

YAML
image: node:20-alpine    # default Docker image for all jobs

stages:
  - install
  - quality
  - test
  - build
  - deploy

variables:
  NODE_ENV: test
  CACHE_KEY: "${CI_COMMIT_REF_SLUG}"

# Cache node_modules between jobs using a key based on package-lock.json
cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

install-dependencies:
  stage: install
  script:
    - npm ci
  artifacts:
    paths:
      - node_modules/
    expire_in: 1 hour

lint:
  stage: quality
  needs: [install-dependencies]
  script:
    - npm run lint

typecheck:
  stage: quality
  needs: [install-dependencies]
  script:
    - npm run typecheck

unit-tests:
  stage: test
  needs: [install-dependencies]
  script:
    - npm run test -- --coverage
  coverage: '/Statementss*:s*(d+.?d*)%/'  # extract coverage %
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
    paths:
      - coverage/
    expire_in: 1 week

build:
  stage: build
  needs: [lint, typecheck, unit-tests]
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

deploy-staging:
  stage: deploy
  needs: [build]
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - npm run deploy:staging
  only:
    - develop

deploy-production:
  stage: deploy
  needs: [build]
  environment:
    name: production
    url: https://example.com
  script:
    - npm run deploy:production
  only:
    - main
  when: manual    # require a human to click "Deploy" in GitLab UI
Runners

A runner is the machine that executes your CI jobs. GitLab offers:

  • GitLab.com shared runners: pre-provisioned Linux, Windows, macOS VMs. Free tier: 400 minutes/month.

  • Self-managed runners: install gitlab-runner on your own server for unlimited free minutes.

  • Group runners: shared across multiple projects in a GitLab group.

  • Project-specific runners: only available to a single project.

Targeting a specific runner with tags

YAML
deploy-production:
  stage: deploy
  tags:
    - production-server   # only runs on runners tagged 'production-server'
  script:
    - ./deploy.sh
GitLab CI vs GitHub Actions

Feature

GitLab CI/CD

GitHub Actions

Config file

.gitlab-ci.yml

.github/workflows/*.yml

Free tier (private)

400 min/month (GitLab.com)

2,000 min/month

Public repos

Unlimited

Unlimited

Self-hosted runners

Yes (gitlab-runner)

Yes (self-hosted runners)

Reusable components

CI Components, templates

Actions (Marketplace)

Matrix builds

Yes (parallel:matrix)

Yes (strategy.matrix)

Environments

Built-in with deployment tracking

Via environments + protection rules

Container registry

Built-in per project

GitHub Container Registry (ghcr.io)

Package registry

Built-in (npm, PyPI, Maven, etc.)

GitHub Packages

Security scanning

Built-in (SAST, DAST, dependency scanning)

Third-party actions

Auto DevOps

Yes (opinionated default pipeline)

No equivalent

Merge request CI

Yes (merge_requests event)

Yes (pull_request event)

Protected variables (secrets)

GitLab calls them "CI/CD variables" rather than secrets. They are set in Settings → CI/CD → Variables.

Using CI/CD variables

YAML
deploy-production:
  stage: deploy
  script:
    - echo "Deploying with key: $DEPLOY_API_KEY"
    # GitLab injects $DEPLOY_API_KEY from protected variables
  only:
    - main
  • Masked variables: the value is never shown in job logs.

  • Protected variables: only available to protected branches and tags.

  • File variables: the value is written to a temp file; useful for SSH keys and certificates.

  • Environment-scoped variables: different values for staging vs production.

Warning
Always mark sensitive variables as both **Masked** and **Protected**. Masked alone prevents display in logs but does not restrict which branches can access the variable.
Environments and deployment tracking

GitLab has first-class environments. Every time a job deploys to an environment, GitLab records it and shows a deployment history timeline.

Environment configuration

YAML
deploy-staging:
  environment:
    name: staging
    url: https://staging.example.com
    on_stop: stop-staging   # optional: define a job to tear down the env

stop-staging:
  environment:
    name: staging
    action: stop
  when: manual
  script:
    - ./teardown-staging.sh
Auto DevOps

Auto DevOps is GitLab's opinionated default pipeline. When enabled, it automatically detects your project language and runs: build, test, code quality, SAST, dependency scanning, container scanning, DAST, and deploy — all with zero configuration.

  • Enable per-project: Settings → CI/CD → Auto DevOps.

  • Override individual stages by adding them to .gitlab-ci.yml.

  • Best suited for standard web applications following language conventions.

  • Can be enabled at the group or instance level to apply to all projects.

Tip
Start with Auto DevOps on a new project to see what a full security-scanning pipeline looks like. Then graduate to a custom `.gitlab-ci.yml` as your requirements become more specific.
Includes and templates for DRY pipelines

Reuse pipeline config with include

YAML
include:
  # Include GitLab's official SAST template
  - template: Security/SAST.gitlab-ci.yml

  # Include a shared pipeline from another project
  - project: 'myorg/shared-pipelines'
    ref: main
    file: '/templates/node.gitlab-ci.yml'

  # Include a local file
  - local: '.gitlab/deploy.yml'

stages:
  - test
  - security   # populated by the SAST template
  - deploy