GitLab CI/CD
CI/CD Tool
GitLab CI/CD
Overview
GitLab CI/CD is a CI/CD system integrated with GitLab. It features powerful pipeline capabilities, Auto DevOps, Kubernetes integration, and security scanning, providing a comprehensive DevOps platform.
Details
GitLab CI/CD is an integrated CI/CD (Continuous Integration/Continuous Delivery) platform provided by GitLab Inc. Since GitLab's inception in 2011, it has evolved continuously and now provides a comprehensive DevOps solution beyond simple code management. Pipelines are defined in the .gitlab-ci.yml file within repositories, automating build, test, and deployment processes. Through GitLab Runner's support for diverse execution environments (Docker, Kubernetes, VM), matrix builds, and parallel execution, it enables fast and flexible CI/CD. In 2024, the CI/CD Catalog was officially released, enabling efficient pipeline construction through reusable components. The Auto DevOps feature automatically generates CI/CD pipelines without configuration, integrating comprehensive security scanning including SAST, DAST, and dependency scanning. Tight integration with GitLab's unique Kanban boards, Issue management, Merge Requests, Container Registry, and Package Registry provides a one-stop solution from development to deployment.
Pros and Cons
Pros
- All-in-One Platform: Integrates Git, CI/CD, security, and monitoring
- Auto DevOps: Automatically generates CI/CD pipelines with zero configuration
- Powerful Security Features: Built-in SAST, DAST, and dependency scanning
- Flexible Execution Environments: Support for Docker, Kubernetes, VM, and self-hosted runners
- CI/CD Catalog: Efficient pipeline construction with reusable components
- Comprehensive Integration: Tight integration with Issues, MRs, and Container Registry
- GitOps Support: GitOps workflow support in Kubernetes environments
- Free Tier: 400 minutes of free CI/CD execution time monthly on GitLab.com
Cons
- Learning Curve: Need to understand YAML syntax and complex feature set
- Execution Time Limits: Maximum 8-hour limit per job on GitLab.com
- Cost: Premium features required for large-scale use even with self-hosting
- Resource Consumption: High system resource usage due to feature richness
- Complex Debugging: Difficult to identify causes of pipeline failures
- Vendor Lock-in: Risk of dependency on GitLab-specific features
- Performance: UI response degradation in large-scale projects
Key Links
- GitLab CI/CD Official Site
- GitLab CI/CD Official Documentation
- GitLab Runners
- GitLab CI/CD Catalog
- GitLab Auto DevOps
- GitLab Examples
Code Examples
Basic Pipeline Configuration
# .gitlab-ci.yml
stages:
- build
- test
- deploy
default:
image: ubuntu:latest
variables:
DOCKER_DRIVER: overlay2
build_job:
stage: build
script:
- echo "Building the application..."
- npm install
- npm run build
artifacts:
paths:
- build/
expire_in: 1 hour
test_job:
stage: test
script:
- echo "Running tests..."
- npm run test
dependencies:
- build_job
deploy_job:
stage: deploy
script:
- echo "Deploying to production..."
- ./deploy.sh
environment:
name: production
url: https://example.com
only:
- main
Node.js Project CI/CD
# .gitlab-ci.yml
image: node:18
stages:
- install
- test
- build
- deploy
cache:
paths:
- node_modules/
- .npm/
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
install_dependencies:
stage: install
script:
- npm ci --cache .npm --prefer-offline
artifacts:
paths:
- node_modules/
expire_in: 30 minutes
lint:
stage: test
script:
- npm run lint
dependencies:
- install_dependencies
unit_tests:
stage: test
script:
- npm run test:unit
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
paths:
- coverage/
dependencies:
- install_dependencies
build_app:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
dependencies:
- install_dependencies
only:
- main
- develop
deploy_staging:
stage: deploy
script:
- echo "Deploying to staging..."
- npm run deploy:staging
environment:
name: staging
url: https://staging.example.com
dependencies:
- build_app
only:
- develop
deploy_production:
stage: deploy
script:
- echo "Deploying to production..."
- npm run deploy:production
environment:
name: production
url: https://example.com
dependencies:
- build_app
only:
- main
when: manual
Docker Image Build and Registry Push
# .gitlab-ci.yml
stages:
- build
- test
- push
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
services:
- docker:dind
before_script:
- docker info
build_image:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker save $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA > image.tar
artifacts:
paths:
- image.tar
expire_in: 1 hour
test_image:
stage: test
script:
- docker load < image.tar
- docker run --rm $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA npm test
dependencies:
- build_image
push_image:
stage: push
script:
- docker load < image.tar
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
dependencies:
- build_image
only:
- main
Matrix Builds and Multi-Platform Support
# .gitlab-ci.yml
stages:
- test
- build
test_matrix:
stage: test
image: node:${NODE_VERSION}
script:
- npm install
- npm test
parallel:
matrix:
- NODE_VERSION: ["16", "18", "20", "22"]
PLATFORM: ["linux", "darwin"]
except:
variables:
- $NODE_VERSION == "16" && $PLATFORM == "darwin"
build_multiarch:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- docker buildx create --use --name multiarch-builder
script:
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
- docker buildx build
--platform linux/amd64,linux/arm64,linux/arm/v7
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
--tag $CI_REGISTRY_IMAGE:latest
--push .
only:
- tags
Conditional Pipelines and Manual Deployment
# .gitlab-ci.yml
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_TAG
- if: $CI_PIPELINE_SOURCE == "schedule"
stages:
- test
- security
- deploy
variables:
ENVIRONMENT: "development"
.base_job: &base_job
before_script:
- echo "Starting job..."
after_script:
- echo "Job completed."
unit_tests:
<<: *base_job
stage: test
script:
- npm run test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
security_scan:
stage: security
script:
- echo "Running security scans..."
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
rules:
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_PIPELINE_SOURCE == "schedule"
deploy_staging:
<<: *base_job
stage: deploy
script:
- echo "Deploying to staging environment..."
- ./deploy.sh staging
environment:
name: staging/$CI_COMMIT_REF_SLUG
url: https://staging-$CI_COMMIT_REF_SLUG.example.com
on_stop: stop_staging
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
stop_staging:
<<: *base_job
stage: deploy
script:
- echo "Stopping staging environment..."
- ./cleanup.sh staging
environment:
name: staging/$CI_COMMIT_REF_SLUG
action: stop
when: manual
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
deploy_production:
<<: *base_job
stage: deploy
script:
- echo "Deploying to production..."
- ./deploy.sh production
environment:
name: production
url: https://example.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
- if: $CI_COMMIT_TAG
when: manual
Security Scanning and Compliance
# .gitlab-ci.yml
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Security/DAST.gitlab-ci.yml
stages:
- build
- test
- security
- deploy
variables:
SAST_EXCLUDED_PATHS: "tests/, spec/, vendor/"
SECURE_LOG_LEVEL: "debug"
build_app:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
container_scanning:
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
dependencies:
- build_app
dast_scan:
stage: security
variables:
DAST_WEBSITE: https://staging.example.com
DAST_AUTH_URL: https://staging.example.com/login
script:
- echo "Running DAST scan..."
artifacts:
reports:
dast: gl-dast-report.json
only:
- schedules
security_report:
stage: security
script:
- echo "Generating security compliance report..."
- ./generate-security-report.sh
artifacts:
reports:
junit: security-report.xml
paths:
- security-report.html
dependencies:
- sast
- dependency_scanning
- container_scanning
Kubernetes Deployment
# .gitlab-ci.yml
stages:
- build
- deploy
variables:
KUBE_NAMESPACE: $CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG
ROLLOUT_RESOURCE_TYPE: deployment
build:
stage: build
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
.deploy_template: &deploy_template
image: bitnami/kubectl:latest
before_script:
- kubectl config use-context $KUBE_CONTEXT
- kubectl create namespace $KUBE_NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
- envsubst < k8s/deployment.yaml | kubectl apply -f -
- envsubst < k8s/service.yaml | kubectl apply -f -
script:
- kubectl rollout status $ROLLOUT_RESOURCE_TYPE/$CI_PROJECT_NAME -n $KUBE_NAMESPACE
- kubectl get all -n $KUBE_NAMESPACE
deploy_staging:
<<: *deploy_template
stage: deploy
variables:
KUBE_CONTEXT: staging-cluster
REPLICAS: "2"
environment:
name: staging
url: https://staging.example.com
kubernetes:
namespace: $KUBE_NAMESPACE
only:
- develop
deploy_production:
<<: *deploy_template
stage: deploy
variables:
KUBE_CONTEXT: production-cluster
REPLICAS: "5"
environment:
name: production
url: https://example.com
kubernetes:
namespace: $KUBE_NAMESPACE
only:
- main
when: manual