Learn GitLab CI/CD - A Complete Production-Grade Pipeline Case Study
Episode 20 of 21

Learn GitLab CI/CD - A Complete Production-Grade Pipeline Case Study

All the concepts from 19 episodes are woven into one real production pipeline. You'll follow the flow from feature commit, multi-arch build, staging deploy, manual approval gates, to production canary with automatic alerts, closing with a production readiness checklist.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

Over 19 episodes we built capabilities one by one: rules, Docker, artifacts, security, environments, the Kubernetes agent, and progressive delivery. Now it's time for the final exam: wiring it all into a single pipeline that runs in the real world. This episode walks through an end-to-end production flow for the my-app application — from the first feature commit to the release alert in Slack.

The complete flow: a feature commit passes lint, unit tests, SAST, secret detection, and a review app; merging to main triggers a parent-child pipeline for a multi-arch build with Kaniko and a Trivy scan; the staging deploy is tested with integration and Cypress E2E tests; manual approval in a protected environment opens the road to production; then a 10 percent canary, health check, full promotion, review app cleanup, and an alert close the flow.

The Main Pipeline

The main .gitlab-ci.yml file at the repository root:

.gitlab-ci.yml - main production pipeline
include:
  - template: Jobs/SAST.gitlab-ci.yml
  - template: Jobs/Secret-Detection.gitlab-ci.yml
 
stages:
  - test
  - review
  - build
  - staging
  - approval
  - production
  - cleanup
 
workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"
 
variables:
  IMAGE_TAG: $CI_COMMIT_SHA

The include block pulls in GitLab's official SAST and Secret Detection templates. workflow prevents duplicate pipelines for pushes and merge requests.

1. Feature Branch Commit

Every push to a feature branch runs the quality layer:

Linting and unit tests
lint:
  stage: test
  image: node:22-alpine
  script:
    - npm ci
    - npm run lint
 
unit_test:
  stage: test
  image: node:22-alpine
  script:
    - npm ci
    - npm test

The SAST and Secret Detection templates generate the sast and secret_detection jobs automatically. Both are set with allow_failure: false via additional rules, so code vulnerabilities immediately block the merge request.

Dynamic Review App

Every merge request gets a temporary environment:

Review app per merge request
review_app:
  stage: review
  image: alpine/helm:latest
  variables:
    KUBE_CONTEXT: my-group/my-app:prod-agent
    KUBE_NAMESPACE: review-$CI_MERGE_REQUEST_IID
  script:
    - helm upgrade --install review-$CI_MERGE_REQUEST_IID ./chart
      --set image.tag=$IMAGE_TAG
  environment:
    name: review/$CI_MERGE_REQUEST_IID
    url: https://review-$CI_MERGE_REQUEST_IID.example.com
    on_stop: stop_review_app

on_stop links the temporary environment to the cleanup job. Once the MR is merged, GitLab triggers stop_review_app to delete the release.

2. Merge to Main — Parent-Child Pipeline

After the MR is approved and merged, the job below triggers a child pipeline in child/build-ci.yml:

Parent-child pipeline trigger job
build_images:
  stage: build
  trigger:
    include: child/build-ci.yml
    strategy: depend
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

The child pipeline contains the multi-arch build and container scan:

child/build-ci.yml - Kaniko multi-arch and Trivy
stages:
  - build
  - scan
 
kaniko_build:
  stage: build
  image: gcr.io/kaniko-project/executor:v1.23.2-debug
  script:
    - /kaniko/executor
      --context $CI_PROJECT_DIR
      --dockerfile $CI_PROJECT_DIR/Dockerfile
      --destination $CI_REGISTRY_IMAGE:${CI_COMMIT_SHA}
      --destination $CI_REGISTRY_IMAGE:latest
 
trivy_scan:
  stage: scan
  image: aquasec/trivy:0.58.2
  script:
    - trivy image --exit-code 1 --severity CRITICAL $CI_REGISTRY_IMAGE:${CI_COMMIT_SHA}

strategy: depend makes the parent wait for the child to finish — deploy only runs if the build and scan succeed.

3. Staging Deploy and E2E

Deploy to staging then Cypress E2E
deploy_staging:
  stage: staging
  image: alpine/helm:latest
  variables:
    KUBE_CONTEXT: my-group/my-app:prod-agent
    KUBE_NAMESPACE: staging
  script:
    - helm upgrade --install my-app ./chart
      --set image.tag=$IMAGE_TAG
  environment:
    name: staging
    url: https://staging.example.com
 
e2e_test:
  stage: staging
  image: cypress/included:13.6.0
  script:
    - npm ci
    - npm run test:integration
    - npm run test:e2e

The E2E job uses the official Cypress image that already includes a browser — nothing to install. Integration and E2E tests exercise the application against real data after the staging deploy finishes.

4. Manual Approval

Compliance demands human approval before production:

Manual production approval gate
approve_production:
  stage: approval
  script:
    - echo "Menunggu persetujuan lead security dan release manager"
  when: manual
  environment:
    name: production
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

The production environment is set up as a protected environment in Settings, so only users with allowed roles can trigger or approve. This job is the gate: without a manual click, jobs in the production stage will never run.

Warning

when: manual without a protected environment is just a formality — anyone with project access can click. Make sure the production environment is registered as protected and only maintainers or release managers are on the access list.

5. Staged Production Deploy

Once approved, the release flows in two steps:

10 percent canary then full promotion
deploy_canary:
  stage: production
  image: alpine/helm:latest
  variables:
    KUBE_CONTEXT: my-group/my-app:prod-agent
    KUBE_NAMESPACE: production
  script:
    - helm upgrade --install my-app ./chart
      --set canary.weight=10
      --set image.tag=$IMAGE_TAG
  environment:
    name: production/canary
    url: https://app.example.com
 
promote_production:
  stage: production
  image: alpine/helm:latest
  variables:
    KUBE_CONTEXT: my-group/my-app:prod-agent
    KUBE_NAMESPACE: production
  script:
    - ./scripts/healthcheck.sh
    - helm upgrade --install my-app ./chart
      --set canary.weight=100
      --set image.tag=$IMAGE_TAG
  environment:
    name: production
    url: https://app.example.com

The healthcheck.sh script checks Prometheus metrics (exactly the episode 17 pattern) and performs an automatic rollback if the error rate spikes. Only after the 10 percent canary is proven healthy is full traffic released.

Cleanup and Alert

The pipeline closes with two final jobs:

Review app cleanup and release alert
stop_review_app:
  stage: cleanup
  image: alpine/helm:latest
  variables:
    KUBE_CONTEXT: my-group/my-app:prod-agent
  script:
    - helm uninstall review-$CI_MERGE_REQUEST_IID
  environment:
    name: review/$CI_MERGE_REQUEST_IID
    action: stop
 
notify_release:
  stage: cleanup
  image: alpine:latest
  script:
    - curl -s -X POST -H 'Content-type: application/json'
      --data "{\"text\":\"Release ${CI_COMMIT_SHA} sukses ke production\"}"
      https://hooks.slack.com/services/T000/B000/XXXXXXXX
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: always

The Slack webhook URL can be swapped for a Telegram bot endpoint for the same notification. With when: always, the alert still goes out even if a previous job failed — the team always knows the release status.

Production Readiness Checklist

Before turning on a pipeline like this, make sure of the following:

  • workflow: rules prevents duplicate pipelines on push and merge requests.
  • SAST and Secret Detection block merge requests that have vulnerabilities.
  • Images are built with Kaniko without a privileged daemon and always scanned with Trivy.
  • The production environment is registered as protected with a clear approver list.
  • Staged deployment with canary and metric-based automatic rollback.
  • Notifications to Slack or Telegram are sent for every release, and production variables are only injected on protected branches.

Closing

A complete production pipeline isn't a collection of jobs, but a layered system of trust: quality is tested, security is checked, humans supervise, and machines are ready to roll back mistakes.

  • Feature commits pass lint, unit tests, SAST, secret detection, and a review app.
  • Merging to main triggers a multi-arch build with Kaniko and a Trivy scan.
  • Staging is tested with integration and Cypress E2E before production.
  • A protected environment holds the release until human approval is given.
  • A 10 percent canary, health check, full promotion, cleanup, and alert close the flow.

With this, the Learn GitLab CI/CD series is complete — you've built a full foundation from basic concepts to a production-grade pipeline. Keep practicing on real projects, and make this pipeline the starting point of your own architecture. See you in the next series!

Learn GitLab CI/CD - A Complete Production-Grade Pipeline Case Study | Learn GitLab CI/CD