Skip to main content
Back to Blog
9 August 202514 min read

GitLab CI/CD: Enterprise Pipeline Configuration

GitLabCI/CDDevOpsKubernetes

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.xml

Pipeline 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.com

Multi-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: staging

Dynamic 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: depend

Environment 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: production

Review 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: manual

Security 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_BRANCH

Container 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_BRANCH

Using 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/cache

Advanced 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.sh

Parallel 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.xml

Pipeline 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-production

Auto 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

  1. Use includes: Modularise pipeline configuration with includes

  2. DAG pipelines: Use needs for parallel execution

  3. Environments: Leverage environments for deployment tracking and protection

  4. Review apps: Automatic environment per merge request

  5. Security scanning: Include GitLab security templates

  6. Resource groups: Prevent concurrent deployments

  7. Caching: Optimise build times with proper caching

  8. 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.

Share this article