GitLab CI/CD¶
Scope¶
GitLab CI/CD pipeline platform. Covers .gitlab-ci.yml pipeline configuration, runner architecture (shared, group, project, instance runners), executor types (Docker, Kubernetes, shell, custom), GitLab Container Registry, Auto DevOps, security scanning (SAST, DAST, dependency scanning, container scanning, secret detection, license compliance), merge request pipelines, multi-project pipelines, parent-child pipelines, compliance pipelines, GitLab Pages, environments and deployments, feature flags, release management, and GitLab vs GitHub comparison for CI/CD workflows.
Checklist¶
- [Critical] Design .gitlab-ci.yml pipeline structure: define stages (build, test, security, deploy) with jobs assigned to each stage; use
needs:keyword for directed acyclic graph (DAG) execution to parallelize jobs that do not depend on each other; configurerules:(preferred overonly/except) for conditional job execution based on branch, tag, merge request, or variable; useinclude:to compose pipelines from reusable template files (local, project, remote, template) - [Critical] Configure GitLab Runners appropriately: select executor type based on workload -- Docker executor for isolated container-based builds (most common), Kubernetes executor for dynamic pod-based builds in K8s clusters, shell executor only for legacy workloads requiring host access; size runner instances for expected concurrency (
concurrentsetting in config.toml); use tags to route jobs to specific runners; register runners at the appropriate scope (instance for shared, group for team, project for dedicated) - [Critical] Implement security scanning in CI pipelines: enable SAST (semgrep-based for 20+ languages), dependency scanning (gemnasium analyzer), container scanning (trivy-based), secret detection (gitleaks-based), and DAST (browser-based or proxy-based) by including GitLab-managed templates; configure vulnerability allowlists and severity thresholds; use
allow_failure: trueon scanning jobs initially, then enforce blocking once baselines are established; review results in merge request Security widget and Vulnerability Report - [Critical] Secure CI/CD variables and secrets: store sensitive values as masked and protected CI/CD variables at the group or project level; use file-type variables for certificates and keys; restrict protected variables to protected branches and tags only; never echo secrets in job logs; consider external secrets managers (HashiCorp Vault integration via
secrets:vault:keyword) for production credentials; audit variable access through CI/CD variable audit events - [Recommended] Design runner autoscaling for cost optimization: use Docker Machine autoscaling (legacy but functional) or the newer fleeting plugin architecture for cloud-based runners; configure
IdleCount,IdleTime, andMaxBuildsin config.toml for scale-to-zero behavior; for Kubernetes executor, leverage cluster autoscaler to scale node pools based on pending pods; use spot/preemptible instances for non-critical jobs with appropriate retry configuration - [Recommended] Implement merge request pipelines and merge trains: configure
rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event"for MR-scoped pipelines that test the merged result (not just the source branch); enable merge trains to serialize merges ensuring each MR is tested against the actual target branch state; useinterruptible: trueon jobs that should be cancelled when a newer pipeline runs on the same MR - [Recommended] Configure caching and artifacts for build performance: use
cache:with key based on lock file hash ($CI_COMMIT_REF_SLUGplus file hash) for dependency caches (node_modules, .m2, pip); useartifacts:for build outputs passed between stages; set appropriateexpire_in:on artifacts to control storage costs; use distributed caching (S3, GCS) for runners that do not share local storage; separate cache per runner tag when build environments differ - [Recommended] Set up GitLab Container Registry with lifecycle policies: push images using
$CI_REGISTRY_IMAGEpredefined variable withdocker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY; tag images with both commit SHA and semantic version; configure cleanup policies to remove untagged images and images older than a threshold; use multi-stage Docker builds to minimize image size; enable container scanning on pushed images - [Recommended] Implement compliance pipelines for regulated environments: use compliance frameworks (Ultimate tier) to enforce required jobs that cannot be skipped or modified by project developers; define compliance pipeline configuration in a separate project that is automatically included; use protected environments with required approvals for production deployments; audit pipeline execution through audit events API
- [Recommended] Design multi-project and parent-child pipeline architecture: use parent-child pipelines (
trigger: include:) to break monorepo builds into per-component child pipelines with independent failure domains; use multi-project pipelines (trigger: project:) for cross-repository orchestration (e.g., deploying after upstream library release); pass variables between pipelines usingtrigger:withvariables:or bridge job artifacts - [Optional] Evaluate Auto DevOps for standardized deployment: Auto DevOps provides automatic build (Dockerfile or buildpack), test, security scan, review app, staging, and production deployment; suitable for teams wanting convention-over-configuration CI/CD; customize by overriding specific stages with project-level .gitlab-ci.yml; works best with Kubernetes deployment targets and Helm-based releases
- [Optional] Configure GitLab Pages for documentation hosting: use a
pagesjob that creates artifacts inpublic/directory; configure custom domains with DNS CNAME and TLS certificates (Let's Encrypt auto or custom); useful for project documentation (MkDocs, Sphinx, Hugo) deployed from CI pipelines; access control available on Premium/Ultimate tiers - [Optional] Implement feature flags with GitLab: use GitLab feature flags (based on Unleash) for gradual rollout of changes; define feature flags in GitLab UI, evaluate in application code using Unleash client SDKs; integrate with CI/CD pipelines to enable flags per environment; useful for decoupling deployment from release
Why This Matters¶
GitLab CI/CD is tightly integrated with the GitLab platform, providing a single application for source control, CI/CD, security scanning, container registry, and deployment management. This integration eliminates the toolchain sprawl common with separate CI/CD systems but creates significant platform lock-in. Pipeline configuration in .gitlab-ci.yml is powerful but can become complex -- teams frequently create deeply nested YAML files with hundreds of lines that become difficult to maintain without proper use of include: and template composition.
Runner architecture is the most operationally significant decision. Shared runners (SaaS or instance-level) provide zero-maintenance builds but suffer from queue contention during peak hours and cannot access private networks. Group or project runners with autoscaling provide dedicated capacity and VPC access but require infrastructure management. The Docker Machine autoscaling driver is officially deprecated in favor of the newer fleeting plugin system, but many organizations still rely on it -- migration planning is essential.
GitLab's built-in security scanning (SAST, DAST, dependency scanning, container scanning, secret detection) is a major differentiator, especially on the Ultimate tier. These scanners run as CI jobs and report findings directly in merge requests, enabling shift-left security. However, the scanners produce false positives that require tuning. Organizations that enable all scanners without establishing baselines and allowlists create alert fatigue that leads developers to ignore security findings entirely.
Pipeline minutes consumption is a critical cost factor on GitLab SaaS. Shared runners on SaaS consume pipeline minutes from the namespace allocation (400 free minutes on Free tier, more on paid tiers). Self-managed runners do not consume pipeline minutes. Organizations with heavy CI workloads on SaaS frequently find that self-hosted runner infrastructure is significantly cheaper than purchasing additional pipeline minute packs.
Common Decisions (ADR Triggers)¶
- GitLab SaaS (gitlab.com) vs self-managed GitLab -- SaaS eliminates infrastructure management and provides automatic updates but limits runner customization, network access, and data residency control. Self-managed provides full control over runners, networking, storage, and data sovereignty but requires patching, backup, and scaling of the GitLab instance itself. Use SaaS for teams without strict data residency requirements. Use self-managed for regulated industries, air-gapped environments, or when private network access from CI jobs is essential.
- GitLab CI/CD vs GitHub Actions -- GitLab provides integrated security scanning, built-in container registry, environments with deployment tracking, and compliance pipelines in a single platform. GitHub Actions provides a larger marketplace of community actions, simpler workflow syntax, and OIDC federation to cloud providers. GitLab is stronger for organizations wanting a single platform with integrated DevSecOps. GitHub Actions is stronger for open-source projects and organizations already using GitHub for source control.
- Shared runners vs dedicated group/project runners -- Shared runners require zero management and scale automatically but have queue delays, no VPC access, and limited customization. Dedicated runners provide predictable performance, private network access, custom tooling, and GPU/specialized hardware but require provisioning and maintenance. Use shared runners for open-source and small teams. Use dedicated runners for production CI/CD with private resource access requirements.
- Docker executor vs Kubernetes executor -- Docker executor runs each job in a container on the runner host, is simpler to configure, and supports Docker-in-Docker for container builds. Kubernetes executor creates a pod per job in a K8s cluster, provides better isolation and autoscaling but adds latency for pod scheduling and requires K8s operational knowledge. Use Docker executor for most workloads. Use Kubernetes executor when an existing K8s cluster is available and dynamic scaling is a priority.
- Monorepo parent-child pipelines vs polyrepo independent pipelines -- Parent-child pipelines allow a monorepo to trigger per-component child pipelines based on changed paths, providing independent failure domains while keeping code together. Polyrepo independent pipelines are simpler but require multi-project pipelines for cross-repository orchestration. Use parent-child pipelines for monorepos with clear component boundaries. Use polyrepo with multi-project triggers for independently versioned services.
- Auto DevOps vs custom pipeline configuration -- Auto DevOps provides convention-over-configuration CI/CD with automatic build, test, scan, and deploy stages. Custom pipelines provide full control over every aspect of the pipeline. Use Auto DevOps for standardized microservices with Kubernetes deployment targets. Use custom pipelines for workloads with non-standard build processes, multiple deployment targets, or complex testing requirements.
Reference Links¶
- GitLab CI/CD Documentation
- .gitlab-ci.yml Reference
- GitLab Runner Documentation
- GitLab Runner Autoscaling
- GitLab Security Scanning
- GitLab Container Registry
- Merge Trains
- Compliance Pipelines
- Parent-Child Pipelines
- GitLab Auto DevOps
- GitLab Feature Flags
See Also¶
general/ci-cd.md-- general CI/CD pipeline patterns, platform comparison, branching strategiesgeneral/deployment.md-- deployment strategies (rolling, blue-green, canary) and rollback proceduresgeneral/compliance-automation.md-- compliance automation patterns applicable to GitLab compliance pipelinesproviders/github/actions.md-- GitHub Actions for comparison with GitLab CI/CDproviders/harbor/registry.md-- Harbor as an alternative container registryproviders/kubernetes/security.md-- Kubernetes security for runner and deployment security