Back to Jenkins
Q1Question 1 of 2

Separating CI and CD Pipelines in Jenkins and Integrating Jenkins with Kubernetes

Key Concepts
  • The CI pipeline is triggered on every push or pull request and only builds, tests, scans, and publishes an immutable artifact.
  • The CD pipeline is triggered by CI success or a tag and only deploys the already-built artifact to environments.
  • Separating CI from CD means the thing tested is exactly the thing deployed, with no rebuild.
  • The Kubernetes plugin runs Jenkins build agents as ephemeral pods that are created on demand and destroyed after the job.
  • Pod-based agents give clean isolation, elastic scale, and pinned tool versions for every build.
  • Use Kaniko or Buildah for image building inside pods instead of mounting the Docker socket.

Definition

CI builds and certifies an artifact on every push, CD deploys that same artifact when CI passes, and Jenkins runs each build in a throwaway Kubernetes pod.
  • CI pipeline – triggered automatically on every push or pull request; builds, tests, scans, and publishes an immutable artifact to a registry.
  • CD pipeline – triggered by CI success, a tag, or a manual promotion; takes the already-built artifact and deploys it to an environment.
  • Separation – CI never deploys and CD never rebuilds, so the thing tested is exactly the thing deployed.
  • Kubernetes plugin – runs Jenkins build agents as ephemeral pods inside the cluster; each job gets a fresh pod that is destroyed after the build.
  • Pod template – defines the containers (JNLP, Maven, Kaniko) and resource limits for each agent pod.

Think of the National Bank of India having two assembly lines. The first line (CI) takes raw cheque forms, checks them, and seals them into certified envelopes. The second line (CD) takes only certified envelopes and delivers them to the right branch. For staffing, instead of keeping a hall of permanent clerks, the bank hires a temporary clerk for each batch who appears, does the work, and leaves. In Jenkins-on-Kubernetes terms, each build gets its own pod that spins up, runs the job, and disappears.

Understanding the Concepts

What does it mean to separate CI and CD in Jenkins?

Separating CI from CD means two distinct Jenkins jobs with different triggers and different responsibilities. The CI pipeline is triggered by source events and only produces a trusted artifact. The CD pipeline is triggered by CI completion and only deploys that artifact. Neither crosses into the other's responsibility.

GROOVY
// CI Jenkinsfile: triggered on push, produces an artifact.
pipeline {
    agent { kubernetes { yaml podTemplate } }
    triggers { githubPush() }
    stages {
        stage('Build')   { steps { sh 'mvn clean package -DskipTests' } }
        stage('Test')    { steps { sh 'mvn verify' } }
        stage('Scan')    { steps { sh 'mvn sonar:sonar' } }
        stage('Publish') {
            steps {
                sh "docker build -t registry.bank.local/transfer-service:${env.GIT_COMMIT} ."
                sh "docker push registry.bank.local/transfer-service:${env.GIT_COMMIT}"
            }
        }
    }
    post {
        success {
            build job: 'transfer-service-cd',
                  parameters: [string(name: 'IMAGE_TAG', value: env.GIT_COMMIT)]
        }
    }
}

How is the CD pipeline triggered?

The CD pipeline receives the artifact tag from CI and deploys it. It never rebuilds. Common trigger methods are the build step at the end of CI, a Git tag push for releases, or a manual start by an operator.

GROOVY
// CD Jenkinsfile: triggered by CI success, deploys the artifact.
pipeline {
    agent { kubernetes { yaml podTemplate } }
    parameters {
        string(name: 'IMAGE_TAG', description: 'Exact image tag from CI')
    }
    stages {
        stage('Deploy Dev') {
            steps {
                sh "kubectl set image deployment/transfer-service " +
                   "transfer-service=registry.bank.local/transfer-service:${params.IMAGE_TAG} " +
                   "-n dev"
            }
        }
        stage('Deploy QA') {
            steps {
                sh "kubectl set image deployment/transfer-service " +
                   "transfer-service=registry.bank.local/transfer-service:${params.IMAGE_TAG} " +
                   "-n qa"
            }
        }
        stage('Approve Prod') {
            steps {
                input message: 'Deploy to production?',
                      submitter: 'release-managers'
            }
        }
        stage('Deploy Prod') {
            steps {
                sh "kubectl set image deployment/transfer-service " +
                   "transfer-service=registry.bank.local/transfer-service:${params.IMAGE_TAG} " +
                   "-n prod"
            }
        }
    }
}

How does environment promotion work with approval gates?

Environment promotion is modeled as ordered stages with gates. Dev and QA deploy automatically, and production sits behind an input step restricted by submitter to named release managers.

GROOVY
stage('Prod') {
    steps {
        timeout(time: 4, unit: 'HOURS') {
            input message: 'Deploy to production?',
                  submitter: 'release-managers'
        }
        sh "helm upgrade transfer-service ./chart " +
           "--set image.tag=${params.IMAGE_TAG} -n prod"
    }
}

The submitter restriction means only a release manager, not any developer, can approve. The timeout ensures an unapproved release eventually aborts rather than hanging.

How does the Kubernetes plugin work?

The Kubernetes plugin lets Jenkins run build agents as ephemeral pods. You configure the cluster API endpoint and a namespace, then define a pod template. When a pipeline requests an agent with that label, Jenkins creates a pod, the build runs inside it, and the pod is destroyed.

GROOVY
// Pod template for a banking service build
pipeline {
    agent {
        kubernetes {
            yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3.9-eclipse-temurin-21
    command: ['sleep', 'infinity']
    resources:
      requests: { memory: "1Gi", cpu: "500m" }
  - name: kaniko
    image: gcr.io/kaniko-project/executor:debug
    command: ['sleep', 'infinity']
"""
        }
    }
    stages {
        stage('Build') {
            steps {
                container('maven') {
                    sh 'mvn clean package'
                }
            }
        }
        stage('Image') {
            steps {
                container('kaniko') {
                    sh '/kaniko/executor --context . --destination registry.bank.local/transfer-service:${GIT_COMMIT}'
                }
            }
        }
    }
}

What are the benefits of pod-based agents?

Pod-based agents give three concrete benefits:

  • Isolation : each build runs in a fresh pod with no leftover state from a previous build, preventing one service's build from contaminating another.
  • Elastic scale : ten concurrent pull requests get ten pods in parallel, and pods are destroyed after use, so there is no cost for idle agents.
  • Consistency : the pod template pins exact tool versions, removing the classic "works on my agent" drift.
YAML
# Pod template pinning exact versions for reproducibility
containers:
- name: maven
  image: maven:3.9.6-eclipse-temurin-21  # Exact version pinned
- name: kaniko
  image: gcr.io/kaniko-project/executor:v1.21.0  # Exact version pinned

Why use Kaniko instead of Docker-in-Docker?

Mounting the host Docker socket (/var/run/docker.sock) inside a build pod gives the build root-equivalent access to the node. On a shared banking cluster this is a serious security risk. Kaniko builds images in userspace without a Docker daemon.

GROOVY
// WRONG: mounting the Docker socket (security risk on shared cluster).
agent {
    kubernetes {
        yaml """
spec:
  containers:
  - name: docker
    image: docker:24
    volumeMounts:
    - name: docker-sock
      mountPath: /var/run/docker.sock
  volumes:
  - name: docker-sock
    hostPath: { path: /var/run/docker.sock }
"""
    }
}

// CORRECT: Kaniko builds images without a Docker daemon.
agent {
    kubernetes {
        yaml """
spec:
  containers:
  - name: kaniko
    image: gcr.io/kaniko-project/executor:debug
    command: ['sleep', 'infinity']
"""
    }
}

How do you manage credentials securely?

CD needs cluster deploy credentials and registry credentials. These come from the Jenkins credential store or a secret manager injected at runtime, never hardcoded in the Jenkinsfile.

GROOVY
stage('Deploy') {
    steps {
        withCredentials([string(credentialsId: 'k8s-token', variable: 'KUBE_TOKEN')]) {
            sh "kubectl --token=${KUBE_TOKEN} set image deployment/transfer-service ..."
        }
    }
}

What should a senior engineer know?

  • The CI artifact is tagged with the commit SHA and never rebuilt. What was tested is what gets deployed.
  • The CD pipeline receives the tag as a parameter and has no build or test stages.
  • Pod templates should pin tool versions by digest or exact tag for reproducibility.
  • Use Kaniko or Buildah for image building in pods. Never mount the Docker socket on a shared cluster.
  • Credentials come from the Jenkins credential store or Vault, never from the Jenkinsfile.
  • The approval gate uses submitter to restrict who can approve and timeout to prevent infinite hangs.
GROOVY
// Senior pattern: full CI/CD with Kaniko and approval gate
pipeline {
    agent { kubernetes { yaml podTemplate } }
    stages {
        stage('Build')   { steps { container('maven') { sh 'mvn package' } } }
        stage('Image')   { steps { container('kaniko') { sh '/kaniko/executor ...' } } }
        stage('Approve') { steps { input submitter: 'release-managers' } }
        stage('Deploy')  { steps { container('kubectl') { sh 'kubectl apply ...' } } }
    }
}

Visual Overview

DIAGRAM
  Push / PR
       │
       ▼
  ┌───────────────────────────────┐
  │         CI PIPELINE            │
  │  Build → Test → Scan → Publish │
  │  Output: image:abc1234         │
  └──────────────┬────────────────┘
                 │ trigger (pass tag)
                 ▼
  ┌───────────────────────────────────────┐
  │            CD PIPELINE                 │
  │  Deploy Dev → Deploy QA → [Approval]   │
  │                             → Deploy   │
  │                               Prod     │
  └───────────────────────────────────────┘

  Jenkins + Kubernetes:

  ┌──────────────┐          ┌─────────────────────┐
  │   Jenkins     │  API  ──▶│  Kubernetes Cluster  │
  │  Controller   │  call    │                     │
  └──────────────┘          │  ┌─────────────┐    │
                            │  │  Agent Pod   │    │
  Job starts ──────────────▶│  │ ┌─────────┐ │    │
                            │  │ │ Maven   │ │    │
  Job ends ────────────────▶│  │ │ Kaniko  │ │    │
    Pod destroyed            │  │ └─────────┘ │    │
                            │  └─────────────┘    │
                            └─────────────────────┘

Key Takeaway

In Jenkins I keep continuous integration and continuous delivery as two separate pipelines with different triggers. The CI pipeline is triggered on every push, and its only job is to build, test, scan, and publish a single immutable artifact tagged with the commit SHA. It never deploys. The CD pipeline is triggered by CI success and takes the already-built artifact and promotes it through environments, with a manual approval gate before production.

For build capacity I integrate Jenkins with Kubernetes using the Kubernetes plugin. Instead of permanent build servers, each job gets a fresh ephemeral agent pod that is created on demand and destroyed after the build. This gives clean isolation, elastic parallelism, and pinned tool versions. For image building I use Kaniko instead of mounting the Docker socket, because the socket gives root-equivalent access to the node, which is unacceptable on a shared banking cluster.

The approval gate before production uses the input step restricted by submitter to named release managers and wrapped in a timeout, so an unapproved release aborts rather than hanging forever. Credentials come from the Jenkins credential store or Vault, never from the Jenkinsfile.

Remember one sentence: CI certifies an artifact on every push, CD deploys that same artifact, and Jenkins runs each build in a throwaway Kubernetes pod.

---

Share
Use ← → arrow keys to navigate