GitLab CI/CD: DevOps-Native Platform
GitLab CI/CD is part of the GitLab platform (Git repo + CI/CD + monitoring all integrated). It's known for:
- ✅ Single-file configuration (
.gitlab-ci.yml) - ✅ Powerful autoscaling runners (efficient resource usage)
- ✅ Built-in container registry & package management
- ✅ Advanced features like parent-child pipelines
- ✅ Native Kubernetes integration
- ✅ Feature flags, scheduled pipelines, manual gates
Structure: The .gitlab-ci.yml File
Everything defined in one place:
# Global configuration
image: node:18
variables:
NPM_REGISTRY_URL: https://registry.npmjs.org
DOCKER_DRIVER: overlay2
stages:
- build
- test
- deploy
# ... job definitions belowComprehensive .gitlab-ci.yml Example
image: node:18-alpine
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
CI_REGISTRY_IMAGE: registry.gitlab.com/mycompany/myapp
NPM_CACHE_FOLDER: $CI_PROJECT_DIR/.npm
cache:
key:
files:
- package-lock.json
prefix: "${CI_COMMIT_REF_SLUG}"
paths:
- .npm
- node_modules/
stages:
- lint
- build
- test
- coverage
- deploy-staging
- deploy-production
# ============ LINT JOBS ============
lint-code:
stage: lint
script:
- npm ci --cache $NPM_CACHE_FOLDER
- npm run lint
only:
- merge_requests
- main
- develop
allow_failure: false
lint-dependencies:
stage: lint
script:
- npm ci --cache $NPM_CACHE_FOLDER
- npm audit --production --verbose
artifacts:
reports:
dependency_scanning: gl-dependency-scanning-report.json
allow_failure: false
# ============ BUILD JOBS ============
build-node-app:
stage: build
script:
- npm ci --cache $NPM_CACHE_FOLDER
- npm run build
artifacts:
paths:
- dist/
- node_modules/
expire_in: 1 day
cache:
key:
files:
- package-lock.json
paths:
- .npm
- node_modules/
build-docker-image:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
- docker build
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--tag $CI_REGISTRY_IMAGE:latest
.
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
only:
- main
- develop
cache: {}
# ============ TEST JOBS ============
test-unit:
stage: test
script:
- npm ci --cache $NPM_CACHE_FOLDER
- npm test -- --coverage --watchAll=false
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
paths:
- coverage/
expire_in: 1 day
coverage: '/All files[^|]*\|[^|]*\ (\d+\.?\d*)/' # Parse coverage percentage
test-e2e:
stage: test
script:
- npm ci --cache $NPM_CACHE_FOLDER
- npm run build
- npm run test:e2e
artifacts:
when: always
paths:
- cypress/videos/
- cypress/screenshots/
expire_in: 1 week
only:
- merge_requests
- main
- develop
test-performance:
stage: test
script:
- npm ci --cache $NPM_CACHE_FOLDER
- npm run build
- npm run test:performance
artifacts:
paths:
- lighthouse-report.html
expire_in: 1 week
only:
- main
- develop
# ============ CODE COVERAGE ============
coverage-report:
stage: coverage
image: haynes/jacoco2cobertura:1.0.8
script:
- python /opt/cover2cover.py coverage/coverage-final.json src --output coverage/cobertura-coverage.xml
coverage: '/Total.*?([0-9]{1,3})%/'
dependencies:
- test-unit
only:
- merge_requests
- main
# ============ STAGING DEPLOYMENT ============
deploy-staging:
stage: deploy-staging
image: alpine:latest
before_script:
- apk add --no-cache kubectl curl
- mkdir -p $HOME/.kube
- echo "$KUBE_CONFIG_STAGING" | base64 -d > $HOME/.kube/config
script:
- kubectl config use-context staging
- kubectl set image deployment/myapp-staging
myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
-n staging
- kubectl rollout status deployment/myapp-staging -n staging --timeout=5m
# Run smoke tests
- apt-get update && apt-get install -y curl
- for i in {1..30}; do curl -f https://staging.myapp.com/health && echo "✅ Staging is healthy" && break || (sleep 2 && echo "Waiting for health check ($i/30)..."); done
environment:
name: staging
url: https://staging.myapp.com
auto_stop_in: 1 day
only:
- develop
when: on_success
tags:
- kubernetes
# ============ PRODUCTION DEPLOYMENT ============
deploy-production:
stage: deploy-production
image: alpine:latest
before_script:
- apk add --no-cache kubectl curl
- mkdir -p $HOME/.kube
- echo "$KUBE_CONFIG_PRODUCTION" | base64 -d > $HOME/.kube/config
script:
- kubectl config use-context production
# Blue-green deployment
- |
CURRENT_COLOR=$(kubectl get deployment myapp-blue -n production &>/dev/null && echo "blue" || echo "green")
NEW_COLOR=$([ "$CURRENT_COLOR" = "blue" ] && echo "green" || echo "blue")
echo "Current active: $CURRENT_COLOR → Deploying to: $NEW_COLOR"
kubectl set image deployment/myapp-$NEW_COLOR \
myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
-n production
kubectl rollout status deployment/myapp-$NEW_COLOR -n production --timeout=5m
# Smoke tests on new deployment
kubectl run smoke-test-$NEW_COLOR \
--image=curlimages/curl:latest \
--rm -i --restart=Never \
-- curl -f http://myapp-$NEW_COLOR:3000/health || exit 1
# Switch traffic if tests pass
kubectl patch service myapp -n production \
-p '{"spec":{"selector":{"version":"'$NEW_COLOR'"}}}'
echo "✅ Production deployment successful"
environment:
name: production
url: https://myapp.com
only:
- tags # Only deploy on git tags (e.g., v1.2.3)
when: manual # Manual approval required
tags:
- kubernetes
deploy-production-rollback:
stage: deploy-production
image: alpine:latest
before_script:
- apk add --no-cache kubectl
- mkdir -p $HOME/.kube
- echo "$KUBE_CONFIG_PRODUCTION" | base64 -d > $HOME/.kube/config
script:
- kubectl config use-context production
- kubectl rollout undo deployment/myapp -n production
- kubectl rollout status deployment/myapp -n production --timeout=5m
environment:
name: production
action: rollback
only:
- main
when: manual # Manual trigger for rollback
tags:
- kubernetes
# ============ BEFORE/AFTER HOOKS ============
.build-setup: &build-setup
before_script:
- npm ci --cache $NPM_CACHE_FOLDER
- npm run lint
.test-setup: &test-setup
before_script:
- npm ci --cache $NPM_CACHE_FOLDER
- npm run buildGitLab Runners: The Execution Engine
Running Locally (Development)
# Install GitLab Runner
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | bash
sudo apt-get install gitlab-runner
# Register runner with GitLab
sudo gitlab-runner register \
--url https://gitlab.com/ \
--registration-token $REGISTRATION_TOKEN \
--executor docker \
--docker-image node:18 \
--description "Docker runner"
# Start runner
sudo gitlab-runner startRunner Types
| Type | Use Case | Setup |
|---|---|---|
| Shared Runners | Public builds, quick feedback | Pre-configured by GitLab admins |
| Group Runners | Shared by multiple projects | Configure once for entire group |
| Project Runners | Private/secure builds | Per-project configuration |
| Container Runners | Kubernetes/Docker | Auto-scaling on demand |
Kubernetes Runner (Auto-Scaling)
# Install GitLab Runner Helm chart
helm repo add gitlab https://charts.gitlab.io
helm repo update
helm install gitlab-runner gitlab/gitlab-runner \
--set gitlabUrl=https://gitlab.company.com/ \
--set gitlabRegistrationToken=$REGISTRATION_TOKEN \
--set runners.image=ubuntu:22.04 \
--set runners.privileged=true \
--set runners.tags={kubernetes,docker} \
--set rbac.create=trueAdvanced Features
Parent-Child Pipelines
# .gitlab-ci.yml
include:
- local: '/ci/build.yml'
- local: '/ci/test.yml'
- local: '/ci/deploy.yml'
# ci/build.yml
stages:
- build
build-trigger:
stage: build
trigger:
include: ci/test.yml
strategy: depend # Wait for child pipelineFeature Flags
test-new-feature:
stage: test
script:
- npm test -- --feature-flag=new-checkout
only:
variables:
- $ENABLE_NEW_CHECKOUT == "true"Scheduled Pipelines
# Run daily security scan
security-scan:
stage: test
script:
- npm audit
only:
- schedulesGitLab CI vs Jenkins vs GitHub Actions
| Feature | GitLab CI | Jenkins | GitHub Actions |
|---|---|---|---|
| Setup Complexity | Simple (1 file) | Complex (GUI + Groovy) | Medium (YAML) |
| Cloud Runners | Yes (free tier) | Self-hosted | Yes (included) |
| Kubernetes Native | Excellent ✅ | Good | Good |
| Container Registry | Built-in ✅ | Plugins | Docker Hub only |
| Cost | $0 (shared runners) | Free (self-host) | Free/per-minute |
| Best For | DevOps teams | Enterprise flexibility | GitHub users |
| Learning Curve | Gentle | Steep | Medium |