CI/CD Pipeline Maturity Model

Evolve your CI/CD pipeline. Covers maturity levels from manual builds to fully automated deployment, pipeline optimization, testing strategies, release management, and developer experience.

A CI/CD Pipeline Maturity Model is a framework for evaluating and evolving a team’s continuous integration and continuous delivery practices. It guides teams from ad-hoc builds and manual deployments to fully automated, self-healing, observability-driven pipelines that scale across multiple services and environments. Use this model when assessing pipeline health, planning incremental improvements, or aligning engineering practices across teams.

Foundational Pipeline: Build and Test

The first stage of maturity is a pipeline that reliably builds code and runs tests on every change. This is where teams move from local development to shared, repeatable processes.

Every commit to main triggers a job that:

A job failure is signaled by a non-zero exit code from the test runner. The pipeline logs include:

npm test -- --coverage --watch=false
> [email protected] test /app
> jest --coverage --watch=false

 FAIL  src/auth/login.test.ts
  ● Login with valid credentials

    Error: Timeout of 5000ms exceeded. Ensure the callback is invoked in the test.

  ✕ Login with valid credentials (5003ms)

  ● Login with invalid credentials

    Error: Expected 401, got 400

  ✕ Login with invalid credentials (123ms)

  ✓ Login with empty credentials (57ms)

Test Suites: 2 failed, 3 passed, 5 total
Tests:       5 failed, 12 passed, 17 total
Snapshots:   0 total
Time:        5.8s
Ran all test suites.

The pipeline stores artifacts using tar cfz build.tar.gz dist/ coverage/. These are archived and linked to the build, with the artifacts directory containing test-results.xml, coverage/lcov.info, and dist/app.js.

Automated Deployment to Staging

With a reliable build, the next step is automatic deployment to staging. This requires a configuration layer and a deployment mechanism.

The pipeline deploys using kubectl apply -f manifests/staging/ and waits for all pods to be ready:

kubectl rollout status deployment/myapp --timeout=60s

If the rollout fails, it logs:

error: rollout status timed out after 60s
waiting for deployment myapp to rollout

Deployment is accompanied by a deploy.yaml file in the repository that defines:

pipeline:
  stages:
    - name: deploy-staging
      steps:
        - run: kubectl apply -f manifests/staging/
        - run: kubectl rollout status deployment/myapp --timeout=60s
        - run: curl -s -f http://staging.myapp.com/health

The health check is critical: it must respond with HTTP 200 and {"status": "ok"} within 5 seconds. If not, the pipeline fails and rolls back:

kubectl rollout undo deployment/myapp

Rollback is triggered by a rollback.sh script that:

-d '{"message": "Rollback triggered", "version": "v1.2.3"}' \ https://webhook.example.com/deployments


## Feature-Branch Driven Development

At this stage, teams use feature branches with pull requests. The pipeline now integrates with the pull request lifecycle.

When a PR is opened:
- The pipeline runs `npm test`, `npm run lint`, and `docker build --tag myapp:pr-$PR_NUM .`
- The build artifact is uploaded to a temporary registry (`docker push myapp:pr-$PR_NUM`)
- A deployment to a preview environment is triggered:
  ```bash
  kubectl apply -f manifests/preview.yaml --namespace=preview-$PR_NUM

The preview URL is generated from the branch name:

echo "https://preview-$PR_NUM.myapp.com"

A PR_STATUS file is created:

{
  "branch": "feature/user-profile",
  "commit": "a1b2c3d",
  "status": "success",
  "artifacts": {
    "docker": "myapp:pr-42",
    "preview_url": "https://preview-42.myapp.com",
    "test_results": "https://artifacts.example.com/pr-42/test-results.xml"
  }
}

The pipeline fails if:

The pipeline does not fail silently: each step logs its command, stdout, and stderr, with output like:

Running: docker build --tag myapp:pr-42 .
Sending build context to Docker daemon  28.7MB
Step 1/4 : FROM node:18-alpine
...
Step 4/4 : COPY . .
 ---> Using cache
 ---> 3f4e5d6c7b8a
Successfully built 3f4e5d6c7b8a
Successfully tagged myapp:pr-42

Canary Deployments with Traffic Shifting

The pipeline now supports canary deployments, where new versions are rolled out gradually.

The deployment job includes:

- name: deploy-canary
  steps:
    - run: kubectl apply -f manifests/canary.yaml
    - run: kubectl rollout status deployment/myapp-canary --timeout=30s
    - run: |
        echo "Shifting 10% traffic to canary"
        kubectl patch service/myapp \
          -p '{"spec":{"selector":{"version":"canary"}}}' \
          --type=merge
    - run: |
        echo "Wait 5 minutes for canary to stabilize"
        sleep 300
    - run: |
        echo "Check metrics"
        curl -f http://metrics.example.com/api/v1/query?query=deployments%7Bjob%3D%22myapp%22%2Cphase%3D%22canary%22%7D

Traffic shifting is done by patching the myapp service to route 10% of traffic to pods with version: canary. The pipeline waits for a health check and metrics.

The canary job fails if:

The pipeline updates the canary-status.json file:

{
  "canary_version": "v1.3.0",
  "traffic_percentage": 10,
  "deployment_started": "2023-08-17T10:00:00Z",
  "status": "progressing",
  "metrics": {
    "error_rate": 0.02,
    "latency_p95": 450
  }
}

Production-Grade Pipeline: Self-Healing and Observability

At the highest maturity, the pipeline is self-healing and observability-driven.

The pipeline monitors the production deployment:

- name: monitor-production
  steps:
    - run: |
        while true; do
          curl -s http://prod.myapp.com/health | jq -e '.status == "ok"'
          if [ $? -ne 0 ]; then
            echo "Health check failed. Triggering rollback."
            kubectl rollout undo deployment/myapp --to-revision=$(kubectl rollout history deployment/myapp --output=json | jq -r '.[0].revision')
            break
          fi
          sleep 30
        done

The pipeline checks for:

If error rate exceeds 0.01 or latency p95 exceeds 500ms, the pipeline triggers a canary rollout with increased traffic:

kubectl patch service/myapp \
  -p '{"spec":{"selector":{"version":"canary"}}}' \
  --type=merge

The pipeline records deployment events in a deployments.log file:

2023-08-17T10:05:00Z: Deployed v1.3.0 to production
2023-08-17T10:05:00Z: Traffic shifted to canary (10%)
2023-08-17T10:10:00Z: Canaries stable. Increasing traffic to 30%
2023-08-17T10:15:00Z: Canaries stable. Increasing traffic to 60%
2023-08-17T10:25:00Z: Canaries stable. Promoting to production
2023-08-17T10:25:00Z: Finalizing deployment

The pipeline handles failure modes:

Key Patterns and Failure Modes

The pipeline’s success hinges on subtle but critical details:

Core Principles Across Maturity Levels

A mature CI/CD pipeline is not a sequence of jobs—it is a living system that evolves with the code, infrastructure, and team.

This page was rewritten on 15 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.