DevOps · CI/CD

CI/CD Pipeline: Complete Training Guide
Jenkins · Azure · Docker

2021 · Training Guide · Prasanna Malode · 8 Parts · Step-by-step
Table of Contents
  1. Foundations & Concepts
  2. Environment Setup
  3. Jenkins Installation & Configuration
  4. Docker Integration
  5. Azure Integration
  6. Writing Your First Jenkinsfile
  7. Full CI/CD Pipeline Walkthrough
  8. Best Practices & Troubleshooting
Part 1 — Foundations & Concepts

1. What is CI/CD?

CI/CD stands for Continuous Integration and Continuous Delivery (or Deployment). It is a set of practices and tools that automate the process of building, testing, and deploying software — reducing human error and speeding up releases.

TermWhat it Means
Continuous Integration (CI)Automatically build and test code every time a developer pushes a change
Continuous Delivery (CD)Automatically prepare tested code for release to any environment
Continuous DeploymentAutomatically deploy every passing build straight to production
PipelineA series of automated steps: build → test → package → deploy
ArtifactThe output of a build (e.g., a Docker image, JAR file, or ZIP)
StageA logical group of pipeline steps (e.g., 'Build' or 'Test')
Agent/NodeThe machine that actually runs the pipeline stages

1.1 Why CI/CD?

1.2 Key Players in This Guide

ToolRole
JenkinsOpen-source automation server — the orchestrator of your pipeline
DockerContainers — packages your app + its dependencies into a portable image
AzureCloud platform — hosts your container registry (ACR) and Kubernetes (AKS) or App Service
Git / GitHubSource code management — every push can trigger a pipeline run
JenkinsfileA text file (Groovy DSL) checked into Git that defines your pipeline

1.3 The Big Picture Flow

Developer pushes code Jenkins webhook fires Stage 1: Build Stage 2: Test Stage 3: Docker Build → ACR Stage 4: Deploy to AKS Slack/Email Notification
Part 2 — Environment Setup

2. Prerequisites Checklist

Teacher Note: Walk through this checklist live in class. Let students verify each item on their own machine before moving on. Estimated setup time: 45–60 minutes for a fresh laptop.

2.1 Local Machine Requirements

RequirementDetails
OSWindows 10/11, macOS 12+, or Ubuntu 20.04+
RAMMinimum 8 GB (16 GB recommended when running Jenkins + Docker locally)
DiskAt least 20 GB free
InternetRequired for downloading images and Azure CLI

2.2 Software to Install

Step A — Install Docker Desktop

  1. Download Docker Desktop from https://www.docker.com/products/docker-desktop
  2. Run the installer and follow the wizard
  3. After install, open a terminal and verify:
docker --version
# Expected output: Docker version 24.x.x

Enable WSL 2 integration on Windows: Settings → Resources → WSL Integration

Step B — Install Java (required by Jenkins)

# Ubuntu/Debian
sudo apt update && sudo apt install -y openjdk-17-jdk

# macOS (Homebrew)
brew install openjdk@17

# Verify
java -version
# Expected: openjdk version "17.x.x"

Step C — Install Git

# Ubuntu
sudo apt install -y git

# macOS
brew install git

# Windows — Download from https://git-scm.com

# Verify
git --version

Step D — Install Azure CLI

# Ubuntu
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# macOS
brew install azure-cli

# Windows — Download MSI from https://aka.ms/installazurecliwindows

# Verify
az --version

Step E — Install kubectl

# Ubuntu
sudo apt-get install -y kubectl

# macOS
brew install kubectl

# Verify
kubectl version --client

2.3 Azure Account Setup

# Login
az login

# List subscriptions
az account list --output table
az account set --subscription ""

# Create a Resource Group
az group create --name cicd-demo-rg --location eastus

2.4 Create Azure Container Registry (ACR)

ACR is Azure's private Docker registry — like Docker Hub, but inside Azure.

# Create the registry
az acr create \
  --resource-group cicd-demo-rg \
  --name mycicdregistry \
  --sku Basic

# Enable admin access (so Jenkins can push images)
az acr update --name mycicdregistry --admin-enabled true

# Get credentials (save these — you'll need them in Jenkins)
az acr credential show --name mycicdregistry
Part 3 — Jenkins Installation & Configuration

3. Running Jenkins in Docker

The easiest way to start Jenkins in any environment is via Docker. This avoids polluting your host OS and makes Jenkins completely portable.

3.1 Run Jenkins Container

# Create a network and volume
docker network create jenkins
docker volume create jenkins-data

# Run Jenkins (official LTS image with Docker-in-Docker support)
docker run -d \
  --name jenkins \
  --network jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins-data:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  jenkins/jenkins:lts-jdk17

# Wait ~30 seconds, then get the initial admin password
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
What is -v /var/run/docker.sock? This mounts the host Docker socket into Jenkins, allowing Jenkins to run Docker commands (docker build, docker push) inside pipeline stages. Without this, Docker commands inside Jenkinsfile would fail.

3.2 Initial Jenkins Setup

  1. Open http://localhost:8080 in your browser
  2. Paste the initial admin password retrieved above
  3. Click 'Install suggested plugins' and wait ~5 minutes
  4. Create your first admin user
  5. Accept the default Jenkins URL

3.3 Essential Plugins to Install

Go to: Manage Jenkins → Plugins → Available plugins

PluginPurpose
Docker PipelineEnables docker.build(), docker.withRegistry() in Jenkinsfile
Azure CredentialsStores Azure SP credentials securely
Kubernetes CLIRuns kubectl commands inside pipeline stages
Pipeline: Stage ViewVisualises pipeline stages in the Jenkins UI
GitHub IntegrationEnables webhooks from GitHub to trigger builds
Blue OceanModern UI for visualising pipelines (optional)
Credentials BindingInjects secrets as environment variables into pipeline steps

3.4 Configuring Jenkins Credentials

Credentials are stored securely in Jenkins and referenced by ID in the Jenkinsfile — never hardcode passwords!

  1. Go to: Manage Jenkins → Credentials → System → Global Credentials → Add Credentials
  2. Kind: Username with password
  3. Username: (ACR admin username)
  4. Password: (ACR password)
  5. ID: acr-credentials ← this exact ID is used in the Jenkinsfile
# Create a service principal for Azure deployment
az ad sp create-for-rbac \
  --name jenkins-cicd-sp \
  --role Contributor \
  --scopes /subscriptions/

# This outputs: appId, password, tenant — save all three!
Part 4 — Docker Integration

4. Docker Fundamentals for CI/CD

Docker packages your application into a self-contained unit called a container. In a CI/CD pipeline, Docker solves the classic 'it works on my machine' problem — if it runs in a container on your laptop, it runs exactly the same in Azure.

4.1 Writing a Dockerfile

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

# Stage 2: Production image (smaller — only what's needed)
FROM node:20-alpine AS production
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/index.js"]
InstructionMeaning
FROMBase image to start from
WORKDIRSet the working directory inside the container
COPYCopy files from your local machine into the image
RUNExecute a command during image build
EXPOSEDocuments which port the app listens on
CMDThe command that runs when a container starts

4.2 Essential Docker Commands

# Build an image
docker build -t myapp:1.0 .

# Run a container
docker run -d -p 3000:3000 --name myapp-container myapp:1.0

# View running containers
docker ps

# View logs
docker logs myapp-container

# Tag for ACR
docker tag myapp:1.0 mycicdregistry.azurecr.io/myapp:1.0

# Push to ACR
az acr login --name mycicdregistry
docker push mycicdregistry.azurecr.io/myapp:1.0
Part 5 — Azure Integration

5. Azure Services Used in This Pipeline

Azure ServiceRole in Pipeline
Azure Container Registry (ACR)Private registry for Docker images built by Jenkins
Azure Kubernetes Service (AKS)Managed Kubernetes cluster to run your containers (Option A)
Azure App ServiceSimpler PaaS option — deploy containers without Kubernetes (Option B)

5.1 Option A — Deploy to Azure Kubernetes Service (AKS)

# Create cluster (takes 5–10 minutes)
az aks create \
  --resource-group cicd-demo-rg \
  --name cicd-demo-aks \
  --node-count 2 \
  --node-vm-size Standard_B2s \
  --generate-ssh-keys \
  --attach-acr mycicdregistry

# Get credentials so kubectl can talk to your cluster
az aks get-credentials \
  --resource-group cicd-demo-rg \
  --name cicd-demo-aks

# Verify cluster is running
kubectl get nodes
Part 6 — Writing Your First Jenkinsfile

6. The Jenkinsfile

A Jenkinsfile is a text file written in Groovy DSL, checked into your Git repo. It defines every stage of your pipeline — Jenkins reads it automatically when a build triggers.

pipeline {
  agent any

  environment {
    ACR_NAME        = 'mycicdregistry'
    ACR_LOGIN_SERVER = "${ACR_NAME}.azurecr.io"
    IMAGE_NAME      = 'myapp'
    IMAGE_TAG       = "${BUILD_NUMBER}"
    AKS_RG          = 'cicd-demo-rg'
    AKS_CLUSTER     = 'cicd-demo-aks'
  }

  stages {

    stage('Checkout') {
      steps {
        checkout scm
        echo "Building commit: ${env.GIT_COMMIT}"
      }
    }

    stage('Build') {
      steps {
        sh 'npm ci'
        sh 'npm run build'
      }
    }

    stage('Test') {
      steps {
        sh 'npm run test -- --coverage'
      }
      post {
        always {
          junit 'test-results/**/*.xml'
        }
      }
    }

    stage('Docker Build & Push') {
      steps {
        withCredentials([usernamePassword(
          credentialsId: 'acr-credentials',
          usernameVariable: 'ACR_USER',
          passwordVariable: 'ACR_PASS'
        )]) {
          sh """
            docker login ${ACR_LOGIN_SERVER} -u ${ACR_USER} -p ${ACR_PASS}
            docker build -t ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG} .
            docker push  ${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG}
          """
        }
      }
    }

    stage('Deploy to AKS') {
      steps {
        withCredentials([azureServicePrincipal('azure-service-principal')]) {
          sh """
            az login --service-principal \
              -u ${AZURE_CLIENT_ID} \
              -p ${AZURE_CLIENT_SECRET} \
              --tenant ${AZURE_TENANT_ID}

            az aks get-credentials \
              --resource-group ${AKS_RG} \
              --name ${AKS_CLUSTER} \
              --overwrite-existing

            kubectl set image deployment/myapp \
              myapp=${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${IMAGE_TAG} \
              --record

            kubectl rollout status deployment/myapp
          """
        }
      }
    }
  }

  post {
    success {
      echo "Pipeline succeeded — Build ${BUILD_NUMBER} deployed to AKS"
    }
    failure {
      echo "Pipeline FAILED — check Stage View for details"
    }
  }
}
Part 7 — Full CI/CD Pipeline Walkthrough

7. End-to-End Walkthrough

7.1 Set Up the Sample Application

# Create project structure
mkdir cicd-demo && cd cicd-demo
git init

# Minimal Node.js app (index.js)
# const http = require('http');
# const server = http.createServer((req, res) => {
#   res.end(`Hello from CI/CD Pipeline! Build: ${process.env.BUILD_NUMBER || 'local'}\n`);
# });
# server.listen(3000);

git add .
git commit -m "Initial commit with Jenkinsfile and Dockerfile"
git push origin main

7.2 Create a Jenkins Pipeline Job

  1. Open Jenkins at http://localhost:8080
  2. Click 'New Item' → enter name: cicd-demo-pipeline → select 'Pipeline'
  3. Under 'Pipeline Definition' → select 'Pipeline script from SCM'
  4. SCM: Git → Repository URL: your GitHub repo URL
  5. Branch: */main → Script Path: Jenkinsfile
  6. Click Save

7.3 Set Up GitHub Webhook

A webhook tells GitHub to notify Jenkins whenever code is pushed — no manual clicking needed.

  1. In GitHub: repo → Settings → Webhooks → Add webhook
  2. Payload URL: http://<JENKINS_PUBLIC_IP>:8080/github-webhook/
  3. Content type: application/json
  4. Events: Just the push event → Add webhook
Local Dev Tip: If Jenkins is on localhost, GitHub can't reach it. Use ngrok http 8080 to create a public tunnel and use the https://xxx.ngrok.io URL as your webhook payload URL.

7.4 Verify the Deployment

# Check pods are running
kubectl get pods -n default

# Get the external IP of your service
kubectl get service myapp-service -n default

# Test the app
curl http://
# Expected: Hello from CI/CD Pipeline! Build: 1
Part 8 — Best Practices & Troubleshooting

8. Best Practices

8.1 Security

8.2 Performance

// Parallel stage example
stage('Tests') {
  parallel {
    stage('Unit Tests')        { steps { sh 'npm run test:unit' } }
    stage('Integration Tests') { steps { sh 'npm run test:integration' } }
    stage('Lint')              { steps { sh 'npm run lint' } }
  }
}

8.3 Branching Strategy

BranchPipeline Action
feature/*Build + test only — no Docker push, no deploy
developBuild + test + Docker push to ACR with :dev tag — deploy to dev
mainFull pipeline — push :latest and :<build> tags — deploy to production
release/*Full pipeline — deploy to staging for UAT

8.4 Troubleshooting Reference

Error / SymptomFix
docker: command not found in pipelineBind mount the host socket and install Docker CLI inside the container, or use a Jenkins agent with Docker pre-installed
unauthorized: authentication required on docker pushThe ACR credential ID in Jenkinsfile doesn't match what you saved in Jenkins Credentials — check spelling exactly
Webhook not triggering buildVerify the webhook URL is accessible from the internet (use ngrok locally). Check Jenkins → Manage Jenkins → System Log
AKS pod stuck in ImagePullBackOffAKS can't pull from ACR. Ensure you ran az aks update --attach-acr to link them
kubectl: command not found in JenkinsInstall the Kubernetes CLI plugin in Jenkins, or add kubectl to the Jenkins agent PATH
Permission denied on /var/run/docker.sockAdd jenkins user to docker group: usermod -aG docker jenkins then restart Jenkins

8.5 Useful Diagnostic Commands

# View Jenkins container logs
docker logs jenkins -f

# Shell into Jenkins container for debugging
docker exec -it jenkins bash

# Check ACR images
az acr repository list --name mycicdregistry --output table
az acr repository show-tags --name mycicdregistry --repository myapp

# AKS diagnostics
kubectl describe pod  -n default
kubectl logs  -n default
kubectl get events -n default --sort-by='.lastTimestamp'

# Rollback a failed deployment
kubectl rollout undo deployment/myapp -n default

9. Learning Checkpoints

After Parts 1–2

After Parts 3–4

After Parts 5–6

After Parts 7–8

Prasanna Malode
DevSecOps · Cybersecurity · IT Operations · Bengaluru, India