Skip to main content

multi project

Managing Multi-Project CI/CD

Strategies for managing CI/CD across 70+ projects efficiently.

Table of Contents

Challenges at Scale

Common Problems with 70+ Projects

  1. Configuration Drift: Each project has slightly different pipeline config
  2. Duplicate Logic: Same build/test/deploy patterns copied across projects
  3. Inconsistent Practices: Some projects use caching, others don't
  4. Update Overhead: Updating 70 pipelines manually is time-consuming
  5. Visibility: Hard to track CI health across all projects
  6. Cost: Inefficient pipelines across many projects = high CI minute costs

Solution Strategies

StrategyUse CaseMaintenanceFlexibility
ComponentsShared logic across all projectsLowHigh
Parent-ChildMonorepo with multiple servicesLowMedium
Multi-ProjectDependent projects (library consumer)MediumHigh
Templates (deprecated)Legacy shared configHighLow

Recommended: Components + Parent-Child pipelines

Parent-Child Pipelines

What are Parent-Child Pipelines?

A pipeline that triggers other pipelines within the same project, all running with the same commit SHA.

Use case: Monorepo with multiple packages/services

Benefits:

  • Break large pipelines into manageable pieces
  • Run only relevant portions based on changes
  • Each child has its own DAG and can run in parallel
  • Keeps pipeline visualization clean

Source: GitLab Parent-Child Pipelines

Basic Example

Project structure:

monorepo/
 services/
    api/
    frontend/
    worker/
 .gitlab-ci.yml

Parent pipeline (.gitlab-ci.yml):

stages: - trigger trigger-api: stage: trigger trigger: include: - local: services/api/.gitlab-ci.yml strategy: depend rules: - changes: - services/api/**/* trigger-frontend: stage: trigger trigger: include: - local: services/frontend/.gitlab-ci.yml strategy: depend rules: - changes: - services/frontend/**/* trigger-worker: stage: trigger trigger: include: - local: services/worker/.gitlab-ci.yml strategy: depend rules: - changes: - services/worker/**/*

Child pipeline (services/api/.gitlab-ci.yml):

stages: - build - test - deploy build-api: stage: build script: - cd services/api - npm ci - npm run build test-api: stage: test needs: [build-api] script: - cd services/api - npm test deploy-api: stage: deploy needs: [test-api] script: - cd services/api - ./deploy.sh

Timeline:

Parent Pipeline
   (triggers in parallel)
API Child    Frontend Child    Worker Child
(only if api/ changed)  (only if frontend/ changed)  (only if worker/ changed)

Cost savings: Only build/test changed services

Dynamic Child Pipeline Generation

Generate child pipelines based on changes:

generate-pipeline: stage: .pre script: - | cat > generated.yml <<EOF stages: - build - test EOF # Detect changed services for service in services/*/; do if git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA | grep -q "$service"; then service_name=$(basename $service) cat >> generated.yml <<EOF build-$service_name: stage: build script: - cd $service && npm run build test-$service_name: stage: test needs: [build-$service_name] script: - cd $service && npm test EOF fi done artifacts: paths: - generated.yml trigger-child: stage: trigger trigger: include: - artifact: generated.yml job: generate-pipeline strategy: depend

Benefit: Completely dynamic - add new services without updating pipeline config

Source: GitLab CI Parent-Child for Monorepo

Child Pipeline with Shared Logic

Share common setup across children:

Parent (.gitlab-ci.yml):

include: - local: .gitlab/ci/common.yml trigger-services: script: - ./generate-child-pipelines.sh artifacts: paths: - child-pipelines/*.yml .trigger-template: &trigger-template stage: trigger trigger: strategy: depend trigger-api: <<: *trigger-template trigger: include: - artifact: child-pipelines/api.yml job: trigger-services

Common config (.gitlab/ci/common.yml):

.build-template: image: node:18 cache: key: files: - package-lock.json paths: - node_modules/ before_script: - npm ci

Child pipeline uses template:

include: - local: .gitlab/ci/common.yml build: extends: .build-template script: - npm run build

Multi-Project Pipelines

What are Multi-Project Pipelines?

A pipeline that triggers pipelines in different projects.

Use case: Dependencies between projects (library consumer, microservices, infrastructure application)

Difference from parent-child:

  • Different repositories
  • Different commit SHAs
  • Can trigger specific branch/tag
  • Cross-project visibility

Downstream Trigger

Project A triggers Project B:

# Project A: .gitlab-ci.yml trigger-project-b: stage: deploy trigger: project: my-group/project-b branch: main strategy: depend # Wait for downstream pipeline to complete

Flow:

Project A Pipeline
   (triggers)
Project B Pipeline (branch: main)
   (result)
Project A continues (if strategy: depend)

With Variables

Pass data to downstream project:

trigger-deployment: stage: deploy trigger: project: my-group/infrastructure branch: main strategy: depend variables: DEPLOY_VERSION: $CI_COMMIT_TAG DEPLOY_ENVIRONMENT: production

Downstream project (.gitlab-ci.yml in infrastructure project):

deploy: stage: deploy script: - ./deploy.sh $DEPLOY_VERSION $DEPLOY_ENVIRONMENT

Bridge Jobs for Complex Workflows

Conditional triggers:

trigger-staging: stage: deploy trigger: project: my-group/infrastructure branch: main variables: ENVIRONMENT: staging rules: - if: $CI_COMMIT_BRANCH == "development" trigger-production: stage: deploy trigger: project: my-group/infrastructure branch: main variables: ENVIRONMENT: production when: manual rules: - if: $CI_COMMIT_BRANCH == "main"

Multi-Project with Artifacts

Pass artifacts between projects:

# Project A: Build artifact build-library: stage: build script: - npm run build artifacts: paths: - dist/ # Trigger Project B with artifact trigger-consumer: stage: trigger trigger: project: my-group/consumer-app strategy: depend needs: - job: build-library artifacts: true

Project B can access artifacts via API:

# Project B: .gitlab-ci.yml download-dependency: stage: .pre script: - | curl --header "PRIVATE-TOKEN: $CI_JOB_TOKEN" \ "${CI_API_V4_URL}/projects/PROJECT_A_ID/jobs/artifacts/main/download?job=build-library" \ -o dependency.zip - unzip dependency.zip

Source: GitLab Downstream Pipelines

Monorepo Strategies

Strategy 1: Path-Based Execution

Only run jobs for changed paths:

build-frontend: script: - cd frontend && npm run build rules: - changes: - frontend/**/* - package.json build-backend: script: - cd backend && go build rules: - changes: - backend/**/* - go.mod

Benefit: Skip unnecessary builds

Strategy 2: Parent-Child with Change Detection

Combine change detection + child pipelines:

stages: - prepare - trigger detect-changes: stage: prepare script: - | # Detect changed directories CHANGED=$(git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA | cut -d/ -f1 | sort -u) echo "CHANGED_DIRS=$CHANGED" >> build.env artifacts: reports: dotenv: build.env trigger-frontend: stage: trigger trigger: include: - local: frontend/.gitlab-ci.yml strategy: depend needs: [detect-changes] rules: - if: '$CHANGED_DIRS =~ /frontend/' trigger-backend: stage: trigger trigger: include: - local: backend/.gitlab-ci.yml strategy: depend needs: [detect-changes] rules: - if: '$CHANGED_DIRS =~ /backend/'

Strategy 3: Workspace Segmentation

Different cache/artifact strategies per workspace:

.frontend-base: cache: key: frontend-$CI_COMMIT_REF_SLUG paths: - frontend/node_modules/ .backend-base: cache: key: backend-$CI_COMMIT_REF_SLUG paths: - backend/vendor/ build-frontend: extends: .frontend-base script: - cd frontend && npm ci && npm run build build-backend: extends: .backend-base script: - cd backend && composer install && php build.php

Source: GitLab Monorepo Performance

Component Libraries

Centralized Component Repository

Create shared components project:

gitlab.com/my-group/ci-components/
 README.md
 templates/
    node-build/
       template.yml
    docker-build/
       template.yml
    security-scan/
       template.yml
    k8s-deploy/
        template.yml

All 70 projects use same components:

# Project 1, 2, 3... 70 include: - component: $CI_SERVER_FQDN/my-group/ci-components/node-build@v1.0.0 inputs: node_version: "18" - component: $CI_SERVER_FQDN/my-group/ci-components/security-scan@v1.0.0 - component: $CI_SERVER_FQDN/my-group/ci-components/k8s-deploy@v1.0.0 inputs: environment: production

Maintenance benefits:

  • Update 1 component affects all projects using that version
  • Gradual rollout: test v2.0.0 in a few projects before updating all 70
  • Single source of truth

See: Components Documentation

Versioned Rollouts

Stage updates across projects:

Week 1: Update 5 projects to v2.0.0 (canary)
Week 2: If stable, update 20 more projects
Week 3: Update remaining 45 projects

Example:

# Week 1: 5 projects test v2.0.0 include: - component: .../ci-components/node-build@v2.0.0 # Week 2-3: Remaining 65 projects still on v1.5.0 include: - component: .../ci-components/node-build@v1.5.0 # After verification: All projects migrate to v2.0.0

Component Library Structure

Organize by functionality:

ci-components/
 templates/
    builds/
       node/
       go/
       python/
       docker/
    tests/
       unit/
       integration/
       e2e/
    scans/
       sast/
       dependency/
       container/
    deploys/
        k8s/
        ecs/
        lambda/

Usage patterns:

# Node.js microservice include: - component: .../ci-components/builds/node@v1 - component: .../ci-components/tests/unit@v1 - component: .../ci-components/scans/sast@v1 - component: .../ci-components/deploys/k8s@v1 # Go API include: - component: .../ci-components/builds/go@v1 - component: .../ci-components/tests/integration@v1 - component: .../ci-components/deploys/ecs@v1

Centralized Configuration

Group-Level CI/CD Variables

Set once, available to all projects:

Location: Group Settings CI/CD Variables

Variables:

  • DOCKER_REGISTRY: Shared registry URL
  • K8S_CLUSTER: Kubernetes cluster endpoint
  • DEPLOY_TOKEN: Deployment credentials
  • MONITORING_API_KEY: Observability platform key

Benefits:

  • No duplication across 70 projects
  • Rotate secrets in one place
  • Consistent configuration

Group-Level Runner Tags

Standardize runner tags:

  • docker-small: 1 CPU, 2GB RAM
  • docker-medium: 2 CPU, 4GB RAM
  • docker-large: 4 CPU, 8GB RAM

All projects use same tags:

build: tags: - docker-medium script: npm run build

Benefit: Easier runner capacity planning

Compliance Pipelines

Enforce policies across all projects:

Location: Group Settings CI/CD Compliance Frameworks

Example policy:

# Enforced on ALL projects in group include: - component: .../ci-components/scans/sast@v1 # Required - component: .../ci-components/scans/dependency@v1 # Required # Block MR if vulnerabilities found security-gate: stage: test script: - ./check-vulnerabilities.sh allow_failure: false # Enforced

Source: GitLab Compliance Pipelines

Cross-Project Dependencies

Pattern 1: Library Consumer

Scenario: Shared library updated trigger all consumers

Library project:

# my-group/shared-library/.gitlab-ci.yml stages: - build - publish - trigger-consumers build-library: stage: build script: - npm run build publish-library: stage: publish script: - npm publish trigger-service-a: stage: trigger-consumers trigger: project: my-group/service-a branch: main trigger-service-b: stage: trigger-consumers trigger: project: my-group/service-b branch: main # ... trigger all 70 consumers

Optimization: Use matrix for multiple consumers:

trigger-consumers: stage: trigger-consumers parallel: matrix: - PROJECT: [service-a, service-b, service-c, ...] trigger: project: my-group/$PROJECT branch: main

Pattern 2: Infrastructure Applications

Scenario: Infrastructure change redeploy all apps

Infrastructure project:

deploy-infrastructure: stage: deploy script: - terraform apply trigger-redeployments: stage: trigger needs: [deploy-infrastructure] trigger: project: my-group/deployment-orchestrator strategy: depend

Orchestrator project (manages 70 app deployments):

stages: - deploy-batch-1 - verify-batch-1 - deploy-batch-2 - verify-batch-2 # Gradual rollout in batches deploy-apps-1-10: stage: deploy-batch-1 parallel: matrix: - APP: [app-1, app-2, ..., app-10] trigger: project: my-group/$APP branch: main verify-batch-1: stage: verify-batch-1 script: - ./verify-deployments.sh apps-1-10 # Continue to next batch only if verification passes deploy-apps-11-20: stage: deploy-batch-2 needs: [verify-batch-1] parallel: matrix: - APP: [app-11, app-12, ..., app-20] trigger: project: my-group/$APP branch: main

Pattern 3: API Contract Changes

Scenario: API spec updated validate all consumers

API project:

validate-spec: stage: validate script: - openapi-validator spec.yaml trigger-contract-tests: stage: trigger needs: [validate-spec] parallel: matrix: - CONSUMER: [frontend, mobile-app, partner-api, ...] trigger: project: my-group/$CONSUMER branch: main variables: RUN_CONTRACT_TESTS: "true" API_SPEC_VERSION: $CI_COMMIT_SHA

Consumer projects:

contract-tests: stage: test script: - ./run-contract-tests.sh $API_SPEC_VERSION rules: - if: $RUN_CONTRACT_TESTS == "true"

Monitoring and Governance

CI/CD Analytics Dashboard

Track metrics across all 70 projects:

Location: Group Analytics CI/CD Analytics

Metrics:

  • Pipeline success rate (per project)
  • Average pipeline duration
  • CI minute consumption (per project)
  • Most failing pipelines
  • Slowest jobs

Action items:

  • Projects with <80% success rate: investigate failures
  • Projects using >10% of group's CI minutes: optimize
  • Projects with >20 min pipelines: parallelize

Automated Compliance Checks

Weekly script to audit all projects:

#!/bin/bash # List all projects in group PROJECTS=$(glab api /groups/my-group/projects | jq -r '.[].path_with_namespace') for project in $PROJECTS; do echo "Auditing $project..." # Check if using approved components CI_CONFIG=$(glab api /projects/${project}/repository/files/.gitlab-ci.yml/raw?ref=main) if ! echo "$CI_CONFIG" | grep -q "ci-components"; then echo " $project not using approved components" fi # Check if security scans enabled if ! echo "$CI_CONFIG" | grep -q "security-scan"; then echo " $project missing security scans" fi # Check pipeline efficiency AVG_DURATION=$(glab api "/projects/${project}/pipelines" | jq '[.[] | .duration] | add / length') if [ "$AVG_DURATION" -gt 1200 ]; then # > 20 minutes echo " $project has slow pipelines (${AVG_DURATION}s avg)" fi done

Cost Monitoring

Track CI minute consumption per project:

#!/bin/bash # Get CI minute usage for all projects for project in $(glab api /groups/my-group/projects | jq -r '.[].id'); do MINUTES=$(glab api /projects/$project/statistics | jq '.statistics.ci_runner_seconds / 60') NAME=$(glab api /projects/$project | jq -r '.path_with_namespace') echo "$NAME: $MINUTES minutes" done | sort -t: -k2 -n -r | head -20 # Top 20 consumers

Set budget alerts:

# Scheduled pipeline in monitoring project check-budget: only: - schedules script: - | TOTAL_MINUTES=$(./get-group-ci-minutes.sh) BUDGET=50000 # 50k minutes/month if [ "$TOTAL_MINUTES" -gt "$BUDGET" ]; then ./send-alert.sh "CI minute budget exceeded: $TOTAL_MINUTES / $BUDGET" fi

Template Migration Tracker

Track which projects migrated to components:

#!/bin/bash TOTAL=0 MIGRATED=0 for project in $(glab api /groups/my-group/projects | jq -r '.[].path_with_namespace'); do TOTAL=$((TOTAL + 1)) CI_CONFIG=$(glab api /projects/${project}/repository/files/.gitlab-ci.yml/raw?ref=main 2>/dev/null) if echo "$CI_CONFIG" | grep -q "component:"; then MIGRATED=$((MIGRATED + 1)) echo " $project" else echo " $project" fi done echo "" echo "Migration progress: $MIGRATED / $TOTAL ($((100 * MIGRATED / TOTAL))%)"

Best Practices for 70+ Projects

1. Standardize Early

Before scaling:

  • Define standard component library
  • Establish naming conventions
  • Document best practices
  • Set up compliance framework

Cost: 1-2 weeks upfront Benefit: Saves months of cleanup later

2. Gradual Migration

Don't force all 70 projects to migrate at once:

Phase 1: Create component library
Phase 2: Migrate 5 pilot projects
Phase 3: Gather feedback, iterate
Phase 4: Migrate 20 more projects
Phase 5: Migrate remaining 45 projects

Timeline: 2-3 months for full migration

3. Automated Governance

Use scheduled pipelines for:

  • Compliance audits (weekly)
  • Cost monitoring (daily)
  • Performance tracking (weekly)
  • Security scanning (daily)

4. Clear Ownership

Assign teams:

  • Platform team: Maintains component library
  • Security team: Enforces security scans
  • Project teams: Maintain project-specific pipelines

5. Documentation as Code

Maintain GitLab Wiki with:

  • Component usage guide
  • Migration playbook
  • Cost optimization tips
  • Troubleshooting guide

Location: Group Wiki

6. Versioned Releases

Component library versioning:

  • v1.x: Initial version (all 70 projects)
  • v2.0: Major update (gradual migration)
  • v1.x: Still maintained (parallel support)
  • v2.x: Standard after 6 months

Never break all 70 projects with one update

7. Cost Attribution

Track CI costs per team/project:

# Add labels to jobs build: tags: - docker-medium variables: COST_CENTER: team-alpha PROJECT_CODE: product-x script: npm run build

Generate monthly reports: CI minutes per team

8. Performance SLOs

Set service level objectives:

  • Pipeline success rate: >90%
  • Average pipeline duration: <10 minutes
  • CI minute budget: <50k minutes/month
  • Security scan coverage: 100%

Monitor and alert when SLOs breached

Summary Checklist

For Managing 70+ Projects

  • Create centralized component library
  • Establish group-level CI/CD variables
  • Define compliance framework
  • Set up monitoring dashboards
  • Implement cost tracking
  • Document migration playbook
  • Assign clear ownership
  • Schedule regular audits
  • Define performance SLOs
  • Plan gradual rollouts

For Monorepo Projects

  • Use parent-child pipelines
  • Implement path-based execution
  • Set up per-service caching
  • Enable dynamic pipeline generation
  • Monitor per-service CI costs

For Microservices

  • Use multi-project pipelines for dependencies
  • Implement contract testing
  • Set up deployment orchestration
  • Enable cross-service testing
  • Track inter-service dependencies

Additional Resources


Last Updated: 2026-01-08 Priority: HIGH - Essential for platform with 70+ projects