GitLab CI: Basic Pipeline

Type: Software Reference Confidence: 0.93 Sources: 7 Verified: 2026-02-28 Freshness: 2026-02-28

TL;DR

Constraints

Quick Reference

KeywordPurposeExample
stagesDefine pipeline stagesstages: [build, test, deploy]
imageDocker image for jobimage: node:20-alpine
scriptCommands to executescript: [npm ci, npm test]
stageAssign job to stagestage: test
rulesConditional executionrules: - if: $CI_COMMIT_BRANCH == "main"
needsDAG dependenciesneeds: [build_job]
artifactsFiles between jobsartifacts: paths: [dist/]
cachePersist between runscache: paths: [node_modules/]
variablesEnv variablesvariables: NODE_ENV: production
defaultDefault settingsdefault: image: node:20
includeImport external YAMLinclude: - template: ...
extendsInherit from jobextends: .base_test
environmentDeploy environmentenvironment: name: production
whenExecution conditionwhen: manual
parallelRun N timesparallel: 5
servicesLinked containersservices: [postgres:16]

Decision Tree

START
├── Simple app (single language)?
│   ├── YES → Three stages: build → test → deploy with shared image
│   └── NO ↓
├── Multiple services/microservices?
│   ├── YES → Use include: to split config, needs: for DAG
│   └── NO ↓
├── Need deployment approval?
│   ├── YES → when: manual + environment: protection
│   └── NO ↓
├── Need matrix testing?
│   ├── YES → parallel:matrix: with version variables
│   └── NO ↓
└── DEFAULT → stages [build, test, deploy] with rules:

Step-by-Step Guide

1. Create .gitlab-ci.yml

Create the file at repo root. GitLab auto-detects it. [src2]

stages:
  - build
  - test
  - deploy

default:
  image: node:20-alpine

build:
  stage: build
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

test:
  stage: test
  script:
    - npm ci
    - npm test

deploy:
  stage: deploy
  script:
    - echo "Deploy to production"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

Verify: Push → CI/CD → Pipelines → see 3-stage pipeline.

2. Add caching

Cache dependencies for faster pipelines. [src1]

default:
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/

Verify: Second run shows "Restoring cache" and faster install.

3. Add rules for conditional execution

Control when jobs run. [src1]

test:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

Verify: Create MR → test runs. Push to non-MR branch → test skipped.

4. Add parallel matrix testing

Test across versions. [src1]

test:
  parallel:
    matrix:
      - NODE_VERSION: ['18', '20', '22']
  image: node:${NODE_VERSION}-alpine
  script:
    - npm ci
    - npm test

Verify: Pipeline shows 3 parallel test jobs.

5. Add manual deployment

Deploy with approval gate. [src5]

deploy_production:
  stage: deploy
  script:
    - echo "Deploying..."
  environment:
    name: production
    url: https://example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual

Verify: Merge to main → deploy shows "play" button.

Code Examples

Complete Node.js pipeline

# .gitlab-ci.yml
stages:
  - lint
  - test
  - build
  - deploy

default:
  image: node:20-alpine
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/

lint:
  stage: lint
  script:
    - npm ci
    - npm run lint

test:
  stage: test
  script:
    - npm ci
    - npm test -- --coverage
  artifacts:
    reports:
      junit: report.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'

build:
  stage: build
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week

deploy_production:
  stage: deploy
  script:
    - echo "Deploy dist/"
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
  needs: [build]

Docker build and push pipeline

# .gitlab-ci.yml
stages:
  - build

build_image:
  stage: build
  image: docker:24
  services:
    - docker:24-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_SHORT_SHA .
    - docker build -t $CI_REGISTRY_IMAGE:latest .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Anti-Patterns

Wrong: Using only/except

# ❌ BAD — only/except is deprecated, removed in GitLab 17.0
deploy:
  script: echo "deploy"
  only:
    - main

Correct: Using rules

# ✅ GOOD — rules is current standard
deploy:
  script: echo "deploy"
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Wrong: Cache without file-based key

# ❌ BAD — cache never invalidates
cache:
  paths:
    - node_modules/

Correct: Cache keyed to lock file

# ✅ GOOD — invalidates when deps change
cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

Wrong: Secrets in YAML

# ❌ BAD — visible to anyone with repo access
variables:
  API_KEY: "sk-12345-secret"

Correct: Using CI/CD Variables

# ✅ GOOD — stored in Settings → CI/CD → Variables
deploy:
  script:
    - curl -H "Authorization: Bearer $API_KEY" https://api.example.com

Common Pitfalls

Diagnostic Commands

# Validate .gitlab-ci.yml locally
gitlab-ci-lint .gitlab-ci.yml

# Check pipeline status via API
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
  "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipelines?per_page=5"

# List CI variables available in pipeline
env | grep CI_

# Debug job locally
gitlab-runner exec docker test_job

Version History & Compatibility

VersionStatusBreaking ChangesMigration Notes
GitLab 17.xCurrentRemoved only/exceptUse rules: keyword
GitLab 16.xPreviousCI/CD Components GAAdopt include: component
GitLab 15.xEOLRemoved CI_BUILD_* varsUse CI_JOB_* equivalents
Shared runners (SaaS)Active400 min/month Free, 10K Premium

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Project on GitLabProject on GitHubGitHub Actions
Need built-in container registryComplex multi-repo orchestrationJenkins or Tekton
Want integrated DevSecOps100+ parallel jobs on free tierSelf-hosted runners
Need environment dashboardsVendor-agnostic CI requiredJenkins or Dagger

Important Caveats

Related Units