Skip to content

CI/CD Tooling — GitHub Actions, Jenkins, GitLab CI

DodaTech Updated 2026-06-22 6 min read

In this tutorial, you'll learn about CI/CD Tooling. We cover key concepts, practical examples, and best practices.

CI/CD pipelines automate the build, test, and deployment process — catching bugs early, enforcing quality gates, and delivering software to production reliably and repeatedly.

What You'll Learn

In this tutorial, you'll learn GitHub Actions for workflow automation, Jenkins Declarative and Scripted Pipelines with shared libraries, GitLab CI configuration, environment promotion (dev, staging, canary, production), artifact management, and deployment strategies including blue-green and rolling updates.

Why It Matters

Manual deployments are error-prone, slow, and don't scale. A well-designed CI/CD pipeline catches integration issues within minutes, enforces code quality checks, and deploys with zero downtime. Teams using CI/CD ship 2x more frequently with 3x fewer failures.

Real-World Use

Doda Browser's CI/CD pipeline uses GitHub Actions for PR checks (lint, test, build), then a Jenkins pipeline for staging deployment with integration tests, and finally a GitLab CI pipeline for production rollout with canary analysis and automatic rollback.

flowchart LR
  A[Developer Push] --> B[GitHub Actions: CI]
  B --> C{Lint + Test + Build}
  C -->|Pass| D[Staging Deploy]
  C -->|Fail| E[Fix and Re-push]
  D --> F[Integration Tests]
  F -->|Pass| G[Production Deploy]
  G --> H[Canary: 5% traffic]
  H --> I{Metrics OK?}
  I -->|Yes| J[Full Rollout]
  I -->|No| K[Auto-Rollback]

GitHub Actions

GitHub Actions uses YAML workflows triggered by events like push, PR, schedule, or manual dispatch.

Basic CI Workflow

# .github/workflows/ci.yml
name: CI
on:
  pull_request:
    branches: [main]
  push:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm test
        env:
          DATABASE_URL: postgres://postgres:test@localhost:5432/test
      - run: npm run build

Expected behavior: On every PR to main, this workflow checks out code, installs dependencies, starts a PostgreSQL service container, runs tests, and builds. A green checkmark on the PR means all checks passed.

Deployment Workflow with Environments

name: Deploy
on:
  workflow_run:
    workflows: ["CI"]
    branches: [main]
    types: [completed]
jobs:
  deploy-staging:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - run: echo "Deploying to staging..."
  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://dodabrowser.com
    steps:
      - run: echo "Deploying to production..."

Expected behavior: After CI passes on main, the deployment workflow starts. Staging deploys first, and only if it succeeds does production deploy. Each environment has its own approval rules and secrets.

Matrix Builds

jobs:
  test-matrix:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [18, 20, 22]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ matrix.node }} }
      - run: npm ci && npm test

Expected behavior: 9 parallel jobs run across platforms and Node versions. If any combination fails, the overall workflow is marked as failed.

Jenkins — Declarative Pipeline

Jenkins pipelines are defined in a Jenkinsfile checked into the repository.

// Jenkinsfile
pipeline {
    agent any
    environment {
        DOCKER_REGISTRY = 'registry.example.com'
    }
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        stage('Build') {
            steps {
                sh 'docker build -t ${DOCKER_REGISTRY}/app:${BUILD_NUMBER} .'
            }
        }
        stage('Test') {
            steps {
                sh 'docker run --rm ${DOCKER_REGISTRY}/app:${BUILD_NUMBER} npm test'
            }
        }
        stage('Deploy Staging') {
            when {
                branch 'main'
            }
            steps {
                sh 'deploy-to-staging.sh ${BUILD_NUMBER}'
            }
        }
    }
    post {
        failure {
            slackSend(color: '#FF0000',
                message: "Pipeline failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}")
        }
    }
}

Expected behavior: Jenkins runs the pipeline on every push. If any stage fails, subsequent stages are skipped. On failure, a Slack notification is sent. The BUILD_NUMBER uniquely identifies each run.

GitLab CI

GitLab CI uses .gitlab-ci.yml with stages, jobs, and runners.

# .gitlab-ci.yml
stages:
  - lint
  - test
  - build
  - deploy

variables:
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

lint:
  stage: lint
  image: node:20-alpine
  script:
    - npm ci
    - npm run lint

test:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm test

build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  script:
    - docker build -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE

deploy:
  stage: deploy
  script:
    - kubectl set image deployment/api api=$DOCKER_IMAGE
  only:
    - main

Expected behavior: Each stage runs sequentially. If lint fails, test and build are skipped. The build stage creates a Docker image tagged with the commit SHA. The deploy stage updates the Kubernetes deployment.

CI/CD Tool Comparison

Tool Configuration Hosting Pipeline Model Best For
GitHub Actions YAML Cloud or self-hosted Event-triggered, matrix Open-source and GitHub projects
Jenkins Groovy/DSL (Jenkinsfile) Self-hosted Stage-based with shared libs Enterprise, complex pipelines
GitLab CI YAML Cloud or self-hosted Stage-based with DAG GitLab ecosystem users
CircleCI YAML Cloud or self-hosted Workflow-based, parallelism Teams needing fast feedback
ArgoCD Kubernetes manifests Kubernetes-native GitOps pull model Kubernetes-native deployments

Common Errors

  1. Secrets hardcoded in pipeline config — Never put passwords in YAML/DSL files. Use secret variables (GitHub: Settings > Secrets, Jenkins: Credentials Binding, GitLab: CI/CD Variables).
  2. Long-running pipelines with no caching — Without dependency caching, every run does npm install from scratch. Use actions/cache@v4 (GitHub Actions) or cache keyword (GitLab CI) to speed up builds.
  3. Deploying without testing infrastructure — A green pipeline means unit tests passed, not that the deployment will succeed. Add smoke tests after deployment to verify the service is healthy.
  4. Not pinning runner versionsubuntu-latest can change its base image at any time. Pin to ubuntu-22.04 or a specific image tag for reproducible builds.
  5. Skipping staging environment — Deploying directly to production without staging means integration issues are found by users. Always promote through at least one intermediate environment.

Practice Questions

  1. What is the difference between GitHub Actions and Jenkins for CI/CD? GitHub Actions is cloud-native with simpler YAML config and tight GitHub integration. Jenkins is self-hosted, more flexible, and supports complex pipeline patterns with shared libraries.

  2. How do you prevent deployment to production if tests fail in CI? In GitHub Actions, use needs: and if: conditions to gate deployment jobs. In Jenkins, failed stages prevent subsequent stages from running automatically.

  3. What is a matrix build and when would you use it? A matrix build runs the same job across multiple OS/version combinations. Use it to ensure compatibility across platforms and runtime versions.

  4. How does GitLab CI handle environment promotion? GitLab CI uses environments with URLs and deployment jobs. You can restrict jobs to specific branches (only: main) and add manual approval gates.

Challenge

Create a multi-stage CI/CD pipeline for a microservice: ran lint and unit tests on every PR, build and push a Docker image on merge to main, deploy to a staging environment with integration tests, and after manual approval, deploy to production with a canary rollout (5% traffic for 10 minutes, then full rollout or rollback).

Mini Project: Build a CI/CD Pipeline for a Security Scanner

Create a complete CI/CD pipeline for a file scanning microservice:

  1. GitHub Actions CI: lint (ESLint), unit tests (Jest), build (Docker image), publish to GitHub Container Registry
  2. Jenkins pipeline: deploy to staging, run integration tests (scan 100 test files and verify results), run security scan (Trivy on the Docker image)
  3. GitLab CI: deploy to production using Kubernetes with a blue-green strategy
  4. Add quality gates: test coverage must be above 80%, no critical vulnerabilities in the Docker image, performance benchmarks must not regress by more than 5%
  5. Add automatic rollback if health checks fail within 5 minutes of deployment

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro