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
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:
- mainStages, 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
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
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 UIRunners
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-runneron 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
deploy-production:
stage: deploy
tags:
- production-server # only runs on runners tagged 'production-server'
script:
- ./deploy.shGitLab 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
deploy-production:
stage: deploy
script:
- echo "Deploying with key: $DEPLOY_API_KEY"
# GitLab injects $DEPLOY_API_KEY from protected variables
only:
- mainMasked 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.
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
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.shAuto 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.
Includes and templates for DRY pipelines
Reuse pipeline config with include
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