GitLab CI â .gitlab-ci.yml Complete Guide
In this tutorial, you'll learn about GitLab CI. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
GitLab CI is a built-in continuous integration and delivery platform that uses a .gitlab-ci.yml file to define pipelines â stages, jobs, dependencies, and deployment targets â all version-controlled alongside your code in every GitLab Repository.
What You'll Learn
Why It Matters
GitLab CI eliminates the need for external CI servers by integrating pipelines directly into the GitLab platform. Every push, merge request, or schedule triggers a pipeline automatically. DodaTech runs 500+ pipelines daily across 60 repositories using GitLab CI, with parallel test execution cutting feedback time from 45 minutes to under 8 minutes.
Real-World Use
The Durga Antivirus Pro team uses GitLab CI to build Windows, macOS, and Linux installers in parallel, run malware signature tests across 10,000+ samples, publish packages to a GitLab Package Registry, and deploy to staging environments â all defined in a single .gitlab-ci.yml file.
flowchart LR
A[Git Push] --> B[.gitlab-ci.yml]
B --> C[Stage: build]
B --> D[Stage: test]
B --> E[Stage: package]
C --> F[Job: compile]
D --> G[Job: unit]
D --> H[Job: integration]
E --> I[Job: docker-build]
F --> G
F --> H
G --> I
H --> I
style B fill:#FC6D26,color:#fff
Prerequisites: A GitLab account and Repository. GitLab runners configured (shared or self-hosted).
Basic Pipeline Structure
# .gitlab-ci.yml
stages:
- build
- test
- package
- deploy
variables:
DOCKER_DRIVER: overlay2
APP_NAME: user-service
REGISTRY: registry.gitlab.com/dodatech
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
before_script:
- node --version
- npm --version
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
unit-test:
stage: test
script:
- npm run test:unit
dependencies:
- build
integration-test:
stage: test
script:
- npm run test:integration
dependencies:
- build
services:
- postgres:16-alpine
- redis:7-alpine
package:
stage: package
script:
- docker build -t ${REGISTRY}/${APP_NAME}:${CI_COMMIT_SHORT_SHA} .
- docker push ${REGISTRY}/${APP_NAME}:${CI_COMMIT_SHORT_SHA}
only:
- main
deploy-staging:
stage: deploy
script:
- kubectl set image deployment/${APP_NAME} ${APP_NAME}=${REGISTRY}/${APP_NAME}:${CI_COMMIT_SHORT_SHA} -n staging
environment:
name: staging
url: https://staging.dodatech.com
only:
- main
needs:
- package
deploy-production:
stage: deploy
script:
- kubectl set image deployment/${APP_NAME} ${APP_NAME}=${REGISTRY}/${APP_NAME}:${CI_COMMIT_SHORT_SHA} -n production
environment:
name: production
url: https://app.dodatech.com
when: manual
only:
- main
needs:
- package
DAG Pipelines with needs
The needs keyword defines a directed acyclic graph (DAG), allowing jobs to start as soon as their dependencies complete:
stages:
- build
- test
- deploy
build:
stage: build
script:
- npm ci && npm run build
artifacts:
paths:
- dist/
lint:
stage: test
script:
- npm run lint
needs: []
unit-test-backend:
stage: test
script:
- npm run test:backend
needs:
- build
unit-test-frontend:
stage: test
script:
- npm run test:frontend
needs:
- build
e2e-test:
stage: test
script:
- npm run test:e2e
needs:
- build
deploy:
stage: deploy
script:
- kubectl apply -f k8s/
needs:
- lint
- unit-test-backend
- unit-test-frontend
- e2e-test
Rules and Conditions
variables:
DOCKER_TLS_CERTDIR: ""
workflow:
rules:
- if: $CI_MERGE_REQUEST_IID
when: always
- if: $CI_COMMIT_TAG
when: always
- if: $CI_COMMIT_BRANCH == "main"
when: always
- when: never
deploy-review-app:
stage: deploy
script:
- kubectl apply -f k8s/review/${CI_MERGE_REQUEST_IID}/
environment:
name: review/${CI_MERGE_REQUEST_IID}
url: https://review-${CI_MERGE_REQUEST_IID}.dodatech.com
on_stop: stop-review-app
rules:
- if: $CI_MERGE_REQUEST_IID
when: manual
stop-review-app:
stage: deploy
script:
- kubectl delete -f k8s/review/${CI_MERGE_REQUEST_IID}/
environment:
name: review/${CI_MERGE_REQUEST_IID}
action: stop
rules:
- if: $CI_MERGE_REQUEST_IID
when: manual
Self-Hosted Runners
# Register a GitLab runner
sudo gitlab-runner register \
--url https://gitlab.com \
--registration-token GL-REG-TOKEN \
--executor docker \
--description "DodaTech GPU Runner" \
--docker-image "docker:24" \
--docker-privileged \
--tag-list "gpu,production" \
--run-untagged="false" \
--locked="false"
# Use tagged runners
build-gpu:
stage: build
tags:
- gpu
script:
- nvidia-smi
- npm run build:gpu
variables:
NVIDIA_VISIBLE_DEVICES: all
Child Pipelines
Trigger child pipelines for microservice deployments:
# Parent .gitlab-ci.yml
trigger-service-a:
trigger:
include: services/service-a/.gitlab-ci.yml
strategy: depend
variables:
DEPLOY_ENV: staging
trigger-service-b:
trigger:
include: services/service-b/.gitlab-ci.yml
strategy: depend
variables:
DEPLOY_ENV: staging
Common Configuration Mistakes
Not using
needsfor dependency ordering: Withoutneeds, jobs wait for all previous stage jobs to complete.needsenables DAG execution where jobs start as soon as their dependencies finish.Missing
cachekey for npm/maven dependencies: Without caching, every pipeline downloads all dependencies from scratch. Usecache: key: ${CI_COMMIT_REF_SLUG}to cache per-branch.Overusing
only/exceptinstead ofrules: GitLab recommendsrulesover legacyonly/exceptsyntax for more flexible conditional job execution.Storing CI variables in
.gitlab-ci.yml: Secrets should be set in GitLab's CI/CD Settings > Variables, not committed to the Repository.Not cleaning up review environments: Review apps that aren't stopped when a merge request closes leave stale resources. Use
on_stopandaction: stopfor cleanup.
Practice Questions
What is the difference between
stagesandjobsin GitLab CI? Answer:stagesdefines the ordered phases of a pipeline. Each stage contains one or morejobsthat run in parallel within that stage.How does the
needskeyword change pipeline execution? Answer:needscreates a DAG where a job starts as soon as its listed dependencies finish, rather than waiting for all jobs in the previous stage.What is the purpose of
artifactsin a job? Answer:artifactspass files between jobs (e.g., build output to test jobs). They are stored on the GitLab server and available in dependent jobs.How do you define manual deployment approvals? Answer: Add
when: manualto the deploy job. The pipeline pauses at that job until a user clicks the play button in the GitLab UI.
Challenge
Create a complete .gitlab-ci.yml for a microservice that builds and tests in parallel across Node.js 18, 20, and 22, caches dependencies, runs E2E tests against a review environment, deploys to staging on main branch pushes, deploys to production with manual approval after passing integration tests against the staging environment, and cleans up review environments on merge request close.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro