GitLab CI/CD: Enterprise Pipeline Configuration
Building robust CI/CD pipelines with GitLab. Pipeline architecture, stages, environments, and integration with Kubernetes deployments.
GitLab CI/CD: Enterprise Pipeline Configuration
GitLab CI/CD provides integrated pipeline capabilities with powerful features for enterprise deployments. Understanding stages, environments, and advanced configurations enables building robust, scalable CI/CD workflows.
Pipeline Fundamentals
Basic Pipeline Structure
# .gitlab-ci.yml
default:
image: node:20-alpine
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
stages:
- validate
- build
- test
- security
- deploy
# Validate stage
lint:
stage: validate
script:
- npm ci
- npm run lint
# Build stage
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 week
# Test stage
test:
stage: test
script:
- npm ci
- npm run test:coverage
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xmlPipeline Rules
# Conditional job execution
deploy-staging:
stage: deploy
script:
- deploy-to-staging.sh
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: always
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
- when: never
deploy-production:
stage: deploy
script:
- deploy-to-production.sh
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
environment:
name: production
url: https://app.example.comMulti-Stage Pipelines
Parent-Child Pipelines
# .gitlab-ci.yml (parent)
stages:
- triggers
trigger-backend:
stage: triggers
trigger:
include: backend/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- backend/**/*
trigger-frontend:
stage: triggers
trigger:
include: frontend/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- frontend/**/*
trigger-infra:
stage: triggers
trigger:
include: infrastructure/.gitlab-ci.yml
strategy: depend
rules:
- changes:
- infrastructure/**/*# backend/.gitlab-ci.yml (child)
stages:
- build
- test
- deploy
build:
stage: build
image: golang:1.21
script:
- go build -o app ./cmd/server
artifacts:
paths:
- app
test:
stage: test
image: golang:1.21
script:
- go test -v -coverprofile=coverage.out ./...
coverage: '/coverage: \d+\.\d+%/'
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl apply -f k8s/
environment:
name: stagingDynamic Child Pipelines
generate-pipeline:
stage: prepare
script:
- |
cat > generated-pipeline.yml << EOF
stages:
- deploy
$(for env in $(cat environments.txt); do
echo "deploy-$env:"
echo " stage: deploy"
echo " script:"
echo " - deploy.sh $env"
echo " environment:"
echo " name: $env"
echo ""
done)
EOF
artifacts:
paths:
- generated-pipeline.yml
trigger-deployments:
stage: deploy
trigger:
include:
- artifact: generated-pipeline.yml
job: generate-pipeline
strategy: dependEnvironment Management
Environment Configuration
variables:
STAGING_URL: "https://staging.example.com"
PRODUCTION_URL: "https://app.example.com"
.deploy-template: &deploy-template
image: bitnami/kubectl:latest
before_script:
- kubectl config set-cluster k8s --server="$KUBE_SERVER" --certificate-authority="$KUBE_CA_PEM"
- kubectl config set-credentials deployer --token="$KUBE_TOKEN"
- kubectl config set-context default --cluster=k8s --user=deployer --namespace="$KUBE_NAMESPACE"
- kubectl config use-context default
deploy-staging:
<<: *deploy-template
stage: deploy
variables:
KUBE_NAMESPACE: staging
script:
- kubectl apply -f k8s/staging/
- kubectl rollout status deployment/app -n staging
environment:
name: staging
url: $STAGING_URL
on_stop: stop-staging
rules:
- if: $CI_COMMIT_BRANCH == "develop"
stop-staging:
<<: *deploy-template
stage: deploy
variables:
KUBE_NAMESPACE: staging
script:
- kubectl delete -f k8s/staging/ --ignore-not-found
environment:
name: staging
action: stop
rules:
- if: $CI_COMMIT_BRANCH == "develop"
when: manual
deploy-production:
<<: *deploy-template
stage: deploy
variables:
KUBE_NAMESPACE: production
script:
- kubectl apply -f k8s/production/
- kubectl rollout status deployment/app -n production
environment:
name: production
url: $PRODUCTION_URL
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
resource_group: productionReview Apps
deploy-review:
stage: deploy
image: bitnami/kubectl:latest
script:
- |
cat k8s/review.yml | envsubst | kubectl apply -f -
kubectl rollout status deployment/app-$CI_ENVIRONMENT_SLUG
environment:
name: review/$CI_COMMIT_REF_SLUG
url: https://$CI_ENVIRONMENT_SLUG.review.example.com
on_stop: stop-review
auto_stop_in: 1 week
rules:
- if: $CI_MERGE_REQUEST_IID
stop-review:
stage: deploy
image: bitnami/kubectl:latest
script:
- kubectl delete namespace review-$CI_ENVIRONMENT_SLUG --ignore-not-found
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
rules:
- if: $CI_MERGE_REQUEST_IID
when: manualSecurity Integration
Security Scanning Pipeline
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Security/License-Scanning.gitlab-ci.yml
variables:
SAST_EXCLUDED_ANALYZERS: "semgrep"
SECRET_DETECTION_EXCLUDED_PATHS: "tests/"
DS_EXCLUDED_ANALYZERS: "gemnasium-python"
# Override container scanning
container_scanning:
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
CS_SEVERITY_THRESHOLD: HIGH
# Custom security job
security-audit:
stage: security
image: node:20-alpine
script:
- npm ci
- npm audit --audit-level=high
- npx snyk test --severity-threshold=high
allow_failure: false
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHContainer Registry Integration
Building and Pushing Images
variables:
DOCKER_HOST: tcp://docker:2376
DOCKER_TLS_CERTDIR: "/certs"
CONTAINER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
CONTAINER_IMAGE_LATEST: $CI_REGISTRY_IMAGE:latest
build-image:
stage: build
image: docker:24
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build --pull -t $CONTAINER_IMAGE .
- docker push $CONTAINER_IMAGE
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
tag-latest:
stage: build
image: docker:24
services:
- docker:24-dind
needs:
- build-image
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker pull $CONTAINER_IMAGE
- docker tag $CONTAINER_IMAGE $CONTAINER_IMAGE_LATEST
- docker push $CONTAINER_IMAGE_LATEST
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHUsing Kaniko (rootless builds)
build-image:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.19.0-debug
entrypoint: [""]
script:
- |
cat > /kaniko/.docker/config.json << EOF
{
"auths": {
"$CI_REGISTRY": {
"auth": "$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64)"
}
}
}
EOF
- /kaniko/executor
--context $CI_PROJECT_DIR
--dockerfile $CI_PROJECT_DIR/Dockerfile
--destination $CONTAINER_IMAGE
--cache=true
--cache-repo=$CI_REGISTRY_IMAGE/cacheAdvanced Configurations
DAG (Directed Acyclic Graph) Pipelines
stages:
- build
- test
- deploy
build-frontend:
stage: build
script:
- npm run build:frontend
artifacts:
paths:
- frontend/dist/
build-backend:
stage: build
script:
- npm run build:backend
artifacts:
paths:
- backend/dist/
test-frontend:
stage: test
needs: [build-frontend]
script:
- npm run test:frontend
test-backend:
stage: test
needs: [build-backend]
script:
- npm run test:backend
test-integration:
stage: test
needs: [build-frontend, build-backend]
script:
- npm run test:integration
deploy:
stage: deploy
needs: [test-frontend, test-backend, test-integration]
script:
- deploy.shParallel Matrix Jobs
test:
stage: test
parallel:
matrix:
- BROWSER: [chrome, firefox, safari]
RESOLUTION: [desktop, mobile]
script:
- npm run test:e2e -- --browser=$BROWSER --resolution=$RESOLUTION
artifacts:
reports:
junit: test-results-$BROWSER-$RESOLUTION.xmlPipeline Includes
# .gitlab-ci.yml
include:
# Local files
- local: '.gitlab/ci/build.yml'
- local: '.gitlab/ci/test.yml'
- local: '.gitlab/ci/deploy.yml'
# Remote files
- remote: 'https://gitlab.com/company/templates/-/raw/main/nodejs.yml'
# Project files
- project: 'company/ci-templates'
ref: main
file: '/templates/docker-build.yml'
# Templates
- template: Auto-DevOps.gitlab-ci.yml# .gitlab/ci/build.yml
.build-template:
stage: build
before_script:
- npm ci
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
build:
extends: .build-template
script:
- npm run build
artifacts:
paths:
- dist/Kubernetes Integration
GitLab Agent for Kubernetes
deploy-k8s:
stage: deploy
image:
name: bitnami/kubectl:latest
entrypoint: [""]
script:
- kubectl config use-context company/infra:production-agent
- kubectl apply -f k8s/
- kubectl rollout status deployment/app
environment:
name: production
kubernetes:
namespace: app-productionAuto DevOps Configuration
include:
- template: Auto-DevOps.gitlab-ci.yml
variables:
AUTO_DEVOPS_BUILD_IMAGE_CNB_ENABLED: "true"
AUTO_DEVOPS_DEPLOY_DEBUG: "true"
KUBE_NAMESPACE: "app-$CI_ENVIRONMENT_SLUG"
HELM_UPGRADE_EXTRA_ARGS: "--set replicas=3"Key Takeaways
-
Use includes: Modularise pipeline configuration with includes
-
DAG pipelines: Use needs for parallel execution
-
Environments: Leverage environments for deployment tracking and protection
-
Review apps: Automatic environment per merge request
-
Security scanning: Include GitLab security templates
-
Resource groups: Prevent concurrent deployments
-
Caching: Optimise build times with proper caching
-
Parent-child pipelines: Scale complex pipelines with triggers
GitLab CI/CD provides enterprise-grade pipeline capabilities with deep Kubernetes integration. Proper configuration of environments and security scanning enables secure, scalable deployments.