CI/CD Pipeline Explained with Examples

Learn how CI/CD pipelines work, their benefits, and see real-world examples to help you implement them in your own projects.

CI/CD Pipeline Explained with Examples

CI/CD Pipeline Explained with Examples

Continuous Integration and Continuous Deployment (CI/CD) pipelines have revolutionized how software teams deliver code to production. In this article, we'll break down what CI/CD pipelines are, why they're essential, and provide practical examples to help you understand how they work in real-world scenarios.

What is a CI/CD Pipeline?

At its core, a CI/CD pipeline is an automated process that takes new code from developers and delivers it to users (or a production environment) quickly and reliably. It's a series of steps that must be passed for new code to be released.

Let's break down the two main components:

CI: Continuous Integration

Concept: Developers frequently merge their code changes into a central repository (like Git).

Goal: To detect integration issues early and often. Instead of everyone working in isolation for weeks and then facing a massive, painful merge, small changes are integrated frequently.

Process:

  1. Build: The code is compiled (if necessary).
  2. Test: Automated tests (unit tests, integration tests) are run to ensure the new code works and doesn't break existing functionality.
  3. Feedback: If the build or tests fail, the team is immediately notified, and they can fix the issue quickly.

CD: Continuous Delivery / Continuous Deployment

Concept: This extends CI by automating the release of the successfully integrated code to various environments.

Goal: To ensure that you can release new changes to your customers quickly, safely, and sustainably.

There are two related terms here:

  • Continuous Delivery: The code is automatically built, tested, and prepared for release to production. The final step to deploy to production is usually a manual button push (e.g., after business approval or a specific release window). The software is always releasable.
  • Continuous Deployment: This goes one step further. Every change that passes all automated tests is automatically deployed to production. There's no manual intervention.

The "Pipeline" Analogy

Think of it like an assembly line. Code enters one end, and if it passes all quality checks at each stage, it comes out the other end as a deployed application. If it fails at any stage, it's pulled off the line, fixed, and re-enters.

Real-World Analogy: A Pizza Restaurant with an Automated Kitchen

Imagine you're running a very popular pizza restaurant that wants to get pizzas to customers as fast and consistently as possible.

  1. New Order (Developer Commits Code):
    • A customer (developer) places an order for a new type of pizza (writes new code/features).
  2. Order Verification (Automated Build - CI):
    • The order (code) is sent to the automated kitchen system.
    • Dough Making (Compile): The system automatically checks if the basic ingredients (code syntax) are correct and can be formed into dough (compiled). If not, the order is rejected (build fails).
    • Ingredient Check (Unit Tests - CI): The system checks if each individual topping (code module/function) is fresh and correct (passes unit tests). If a topping is bad, the order is flagged (test fails).
  3. Pizza Assembly (Integration Tests - CI):
    • The system assembles the pizza with all the toppings (integrates different code modules).
    • Taste Test (Integration Tests): A robot taster checks if the combination of toppings works well together (do modules interact correctly?). If it tastes bad, the order is flagged (integration test fails).
  4. Baking (Packaging/Creating an Artifact - CI/CD):
    • The assembled pizza is automatically baked in a high-tech oven (code is packaged into a deployable artifact – like a Docker image, .jar file, etc.).
  5. Quality Control (Deploy to Staging & Acceptance Tests - CD):
    • The baked pizza (artifact) is put on a "display counter" that's exactly like the customer's table (staging environment).
    • Manager's Check (Automated Acceptance Tests): A robot manager inspects the pizza for appearance, correct toppings, and even does a final taste test (runs automated end-to-end tests simulating user behavior). If it's not perfect, it's rejected.
  6. Ready for Delivery (Continuous Delivery - CD):
    • If the pizza passes the manager's check, it's boxed and ready. A human waiter (operations team/product owner) gives the final "OK" to send it out (manual deployment trigger).
  7. Automatic Delivery (Continuous Deployment - CD):
    • Alternatively, if the restaurant is super confident, once the manager's check is passed, a drone (automated deployment script) immediately takes the pizza to the customer's table (production environment) without waiting for a human waiter.

Benefits of this "Pizza Pipeline":

  • Speed: Pizzas (features) get to customers faster.
  • Consistency: Every pizza (release) meets a high quality standard because of the automated checks.
  • Early Problem Detection: Bad ingredients (bugs) are found quickly, not when the customer complains.
  • Efficiency: Chefs (developers) can focus on creating new recipes (features) rather than manually checking every step.

Example: A Simple Web Application Pipeline

Let's say you have a web application and you use Git for version control and Jenkins (a popular CI/CD tool) to automate the pipeline.

Developer Action:

  1. A developer writes new code for a feature (e.g., a new "Contact Us" form).
  2. They run local tests on their machine.
  3. They git commit and git push their changes to the main branch (or a feature branch that then gets merged into main).

CI Phase (Triggered by Git Push):

Jenkins detects the push and starts the pipeline:

Stage 1: Build

# If it's a Node.js app
npm install

# If it's a Java app
mvn clean install

If this fails, the pipeline stops, and the developer is notified.

Stage 2: Unit & Integration Tests

# For Node.js
npm test

# For Java
mvn test

If any test fails, the pipeline stops, and the developer is notified.

Stage 3: Static Code Analysis

Tools like SonarQube check for code quality, security vulnerabilities, and code smells.

Stage 4: Package

# Build Docker image
docker build -t myapp:${BUILD_NUMBER} .

# Push to registry
docker push myapp:${BUILD_NUMBER}

CD Phase (Continues if CI is successful):

Stage 5: Deploy to Staging

# Deploy to staging environment
kubectl apply -f kubernetes/staging/deployment.yaml

Stage 6: Automated Acceptance Tests

# Run end-to-end tests
cypress run --config baseUrl=https://staging.myapp.com

If these tests fail, the pipeline stops, and the team is notified.

Stage 7: Manual Approval (for Continuous Delivery)

Jenkins pauses the pipeline and waits for a human to approve the release to production.

Stage 8: Deploy to Production

# Deploy to production
kubectl apply -f kubernetes/production/deployment.yaml

Stage 9: Smoke Tests & Monitoring

# Run basic smoke tests
curl -f https://myapp.com/health

# Set up monitoring alerts
datadog-agent monitor set

Real-World Example: GitLab CI/CD Pipeline

Here's a simplified .gitlab-ci.yml file that defines a CI/CD pipeline for a web application:

stages:
  - build
  - test
  - analyze
  - deploy_staging
  - test_staging
  - deploy_production

build_job:
  stage: build
  script:
    - npm install
    - npm run build
  artifacts:
    paths:
      - dist/

unit_test_job:
  stage: test
  script:
    - npm run test:unit

integration_test_job:
  stage: test
  script:
    - npm run test:integration

code_quality:
  stage: analyze
  script:
    - npm run lint
    - npm audit

deploy_staging:
  stage: deploy_staging
  script:
    - aws s3 sync dist/ s3://staging-bucket/
  environment:
    name: staging
    url: https://staging.example.com

e2e_tests:
  stage: test_staging
  script:
    - npm run test:e2e -- --baseUrl=https://staging.example.com

deploy_production:
  stage: deploy_production
  script:
    - aws s3 sync dist/ s3://production-bucket/
  environment:
    name: production
    url: https://example.com
  when: manual
  only:
    - main

Real-World Example: GitHub Actions CI/CD Pipeline

Here's a GitHub Actions workflow file (.github/workflows/ci-cd.yml) that implements a CI/CD pipeline:

name: CI/CD Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-files
          path: ./dist

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    steps:
      - uses: actions/checkout@v3
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-files
          path: ./dist
      - name: Deploy to staging
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
      - run: aws s3 sync ./dist s3://staging-bucket/

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    environment:
      name: production
      url: https://www.example.com
    steps:
      - uses: actions/checkout@v3
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: build-files
          path: ./dist
      - name: Deploy to production
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
      - run: aws s3 sync ./dist s3://production-bucket/

Our Own CI/CD Pipeline at PCJ

At PCJ, we've implemented a robust CI/CD pipeline using GitLab CI/CD to deploy our Laravel and Nuxt.js application. Our pipeline:

  1. Builds both our Laravel backend and Nuxt.js frontend
  2. Tests the application with PHPUnit and Jest
  3. Deploys to our staging environment automatically
  4. Runs end-to-end tests with Cypress
  5. Deploys to production after manual approval

This approach has significantly reduced our deployment time from hours to minutes and has virtually eliminated deployment-related issues.

Benefits of CI/CD Pipelines

  1. Faster Time to Market: Automate manual processes to deliver features quickly.
  2. Higher Quality Code: Catch bugs early through automated testing.
  3. Reduced Risk: Small, incremental changes are easier to troubleshoot.
  4. Improved Developer Productivity: Developers focus on writing code, not deployment tasks.
  5. Consistent Releases: Standardized process ensures reliable deployments.
  6. Better Collaboration: Provides visibility into the development and deployment process.

Conclusion

CI/CD pipelines have become an essential part of modern software development. By automating the build, test, and deployment processes, teams can deliver high-quality software faster and more reliably. Whether you're using Jenkins, GitLab CI, GitHub Actions, or another tool, the principles remain the same: automate repetitive tasks, catch issues early, and streamline the path to production.

By implementing a CI/CD pipeline, you're not just improving your technical processes—you're transforming how your entire team collaborates and delivers value to customers.