Jenkins Course
Pipeline with Kubernetes
Kubernetes changes how Jenkins scales. Instead of maintaining a fleet of static agents, Jenkins spins up a fresh pod for every build and destroys it when done. No idle machines, no configuration drift, no "it only breaks on agent 3" mysteries.
This lesson covers
Kubernetes plugin setup → Pod templates → Running pipeline steps in pods → Deploying to Kubernetes from Jenkins → kubectl and Helm in pipelines → Namespace-based environment promotion
In Lessons 3 and 4 you learned that Jenkins agents can be physical servers, VMs, or Docker containers. Kubernetes adds a fourth option — pods. A pod is a lightweight, ephemeral compute unit managed by Kubernetes. With the Kubernetes plugin, Jenkins creates a pod for each build, runs the pipeline inside it, and deletes the pod when the build finishes. The agent fleet scales automatically with demand.
This lesson also covers the second Kubernetes use case — deploying applications to a cluster from a Jenkins pipeline using kubectl and Helm.
The Analogy
Static Jenkins agents are like office desks — they sit there all day whether anyone is using them or not, they accumulate clutter over time, and if one breaks you have to fix it manually. Kubernetes pod agents are like hot desks — a build starts, a desk appears, the build finishes, the desk vanishes. The room is always clean and perfectly sized for however many people are working right now.
How the Kubernetes Plugin Works
The Jenkins Kubernetes plugin connects Jenkins to a Kubernetes cluster. When a build is triggered, the plugin creates a pod in the cluster. That pod runs the pipeline steps. When the build finishes, the pod is automatically deleted. Here's the lifecycle:
Build triggered
A push or schedule fires the Jenkins pipeline. The Kubernetes plugin reads the pod template from the Jenkinsfile.
Pod created in Kubernetes
Jenkins asks Kubernetes to create a pod matching the template. Kubernetes schedules it on an available node. The pod contains one or more containers — the JNLP agent container (mandatory) plus any tool containers your build needs.
JNLP agent connects back to Jenkins master
The JNLP container inside the pod starts and reaches out to the Jenkins master. The pod is now registered as an agent and ready to receive work.
Pipeline steps run in the pod's containers
Each container('name') block in the Jenkinsfile switches which container the steps run in. Test steps run in the Maven container. Docker steps run in the Docker container. All containers share the same workspace volume.
Build finishes — pod deleted
Build passes or fails, post-build actions run, and Kubernetes deletes the pod entirely. No residue. The next build gets a fresh pod.
Running a Pipeline in Kubernetes Pods
The scenario:
You're a platform engineer at a cloud-native SaaS company. Your team runs 30+ microservices and maintaining a fleet of static Jenkins agents for each team has become a nightmare. You're migrating to Kubernetes-based build agents. This Jenkinsfile defines a pod with three containers — Maven for building, Docker for image building, and kubectl for deployment — and runs the pipeline across all three.
New terms in this code:
- kubernetes { } — the agent block for Kubernetes-based builds. Defines the pod template that Jenkins will create for this pipeline.
- yaml — the full Kubernetes pod specification in YAML format, embedded directly in the Jenkinsfile. Gives you complete control over every pod attribute — resource limits, environment variables, volumes, security contexts.
- container('name') — a step block that switches the execution context to the named container inside the pod. Steps inside this block run in that container. Steps outside run in the default JNLP container.
- jnlp container — the mandatory Jenkins agent container. Every pod must have one. It connects back to the Jenkins master and handles communication. You don't run your build steps in it — only tool containers.
- resources requests/limits — Kubernetes resource constraints on the container.
requestsis what Kubernetes reserves.limitsis the maximum. Setting these prevents a runaway build from consuming all cluster resources. - serviceAccountName — the Kubernetes service account the pod runs as. For the kubectl deployment container to talk to the Kubernetes API, it needs a service account with the right permissions.
pipeline {
agent {
kubernetes {
// The full pod specification in YAML
// Jenkins creates exactly this pod for every build
yaml '''
apiVersion: v1
kind: Pod
metadata:
labels:
app: jenkins-build
spec:
# Service account with permissions to deploy to the cluster
serviceAccountName: jenkins-deployer
containers:
# Mandatory: the JNLP agent container
# Jenkins uses this to communicate with the pod
# Do NOT remove this or run your steps in it
- name: jnlp
image: jenkins/inbound-agent:latest
resources:
requests:
cpu: 100m
memory: 256Mi
# Maven container for building and testing the Java service
- name: maven
image: maven:3.9-eclipse-temurin-21
command: [cat]
tty: true
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: "1"
memory: 2Gi
volumeMounts:
- name: maven-cache
mountPath: /root/.m2
# Docker container for building and pushing the image
- name: docker
image: docker:24-dind
command: [cat]
tty: true
securityContext:
privileged: true # required for Docker-in-Docker
resources:
requests:
cpu: 500m
memory: 512Mi
# kubectl container for Kubernetes deployments
- name: kubectl
image: bitnami/kubectl:latest
command: [cat]
tty: true
resources:
requests:
cpu: 100m
memory: 128Mi
volumes:
# Shared Maven cache across builds — speeds up dependency downloads
- name: maven-cache
persistentVolumeClaim:
claimName: maven-cache-pvc
'''
}
}
environment {
APP_NAME = 'order-service'
REGISTRY = 'registry.acmecorp.com'
IMAGE_TAG = "${BUILD_NUMBER}-${env.GIT_COMMIT?.take(7) ?: 'local'}"
DOCKER_CREDS = credentials('docker-registry-credentials')
}
stages {
stage('Checkout') {
steps {
checkout scm
echo "Building ${APP_NAME}:${IMAGE_TAG} in Kubernetes pod"
}
}
stage('Test') {
steps {
// 'container' switches to the maven container for these steps
container('maven') {
sh 'mvn --version'
sh 'mvn clean test'
}
}
post {
always {
junit 'target/surefire-reports/**/*.xml'
}
}
}
stage('Build and Push Image') {
when { branch 'main' }
steps {
// Switch to the docker container to build and push the image
container('docker') {
sh """
docker login -u ${DOCKER_CREDS_USR} -p ${DOCKER_CREDS_PSW} ${REGISTRY}
docker build -t ${REGISTRY}/${APP_NAME}:${IMAGE_TAG} .
docker push ${REGISTRY}/${APP_NAME}:${IMAGE_TAG}
docker push ${REGISTRY}/${APP_NAME}:latest
"""
}
}
}
stage('Deploy to Staging') {
when { branch 'main' }
steps {
// Switch to the kubectl container to interact with the cluster
container('kubectl') {
sh """
# Update the image tag in the staging deployment
kubectl set image deployment/${APP_NAME} \
${APP_NAME}=${REGISTRY}/${APP_NAME}:${IMAGE_TAG} \
--namespace=staging
# Wait for the rollout to complete before declaring success
kubectl rollout status deployment/${APP_NAME} \
--namespace=staging \
--timeout=120s
"""
}
}
}
}
post {
success {
slackSend(
channel: '#deployments',
color: 'good',
message: "✅ *${APP_NAME}:${IMAGE_TAG}* deployed to staging — <${env.BUILD_URL}|Build #${BUILD_NUMBER}>"
)
}
failure {
slackSend(
channel: '#deployments',
color: 'danger',
message: "❌ *${APP_NAME}* build failed — <${env.BUILD_URL}|Build #${BUILD_NUMBER}>"
)
}
always { cleanWs() }
}
}
Where to practice: Install the Kubernetes plugin from Manage Jenkins → Plugin Manager. Then go to Manage Jenkins → Manage Nodes and Clouds → Configure Clouds → Add a new cloud → Kubernetes. Point it at your cluster's API server URL and add credentials. For a free local Kubernetes cluster to practice with, use minikube or kind. Full plugin docs at plugins.jenkins.io/kubernetes.
Started by GitHub push by dev-chen (branch: main)
[Pipeline] Start of Pipeline
[Pipeline] podTemplate
[Pipeline] node (order-service-build-7xk9p)
Creating a new pod: order-service-build-7xk9p
Pod is running: order-service-build-7xk9p
[Pipeline] { (Checkout) }
[Pipeline] checkout — git checkout main — HEAD: b5c6d7e
[Pipeline] echo
Building order-service:61-b5c6d7e in Kubernetes pod
[Pipeline] { (Test) }
[Pipeline] container (maven)
[Pipeline] sh
+ mvn clean test
[INFO] Tests run: 71, Failures: 0, Errors: 0, Skipped: 0
[Pipeline] junit — recording test results
[Pipeline] { (Build and Push Image) }
[Pipeline] container (docker)
[Pipeline] sh
+ docker build -t registry.acmecorp.com/order-service:61-b5c6d7e .
Successfully built f8e3a1c2
+ docker push registry.acmecorp.com/order-service:61-b5c6d7e
Pushed: registry.acmecorp.com/order-service:61-b5c6d7e
[Pipeline] { (Deploy to Staging) }
[Pipeline] container (kubectl)
[Pipeline] sh
+ kubectl set image deployment/order-service order-service=registry.acmecorp.com/order-service:61-b5c6d7e --namespace=staging
deployment.apps/order-service image updated
+ kubectl rollout status deployment/order-service --namespace=staging --timeout=120s
Waiting for deployment "order-service" rollout to finish: 0 of 3 updated replicas are available...
Waiting for deployment "order-service" rollout to finish: 1 of 3 updated replicas are available...
Waiting for deployment "order-service" rollout to finish: 2 of 3 updated replicas are available...
deployment "order-service" successfully rolled out
[Pipeline] post (success)
Slack: ✅ order-service:61-b5c6d7e deployed to staging
[Pipeline] cleanWs
Deleting pod: order-service-build-7xk9p
[Pipeline] End of Pipeline
Finished: SUCCESS
What just happened?
Creating a new pod: order-service-build-7xk9p— Jenkins asked Kubernetes to create a pod with the nameorder-service-build-7xk9p. The random suffix ensures uniqueness — 10 simultaneous builds create 10 separate pods, none interfering with each other.[Pipeline] container (maven)— this line marks where Jenkins switched execution into the maven container. Themvn clean testcommand ran inside the maven container, not the jnlp container. Maven was available because it's baked into themaven:3.9image.[Pipeline] container (docker)— switched to the docker container for image building and pushing. Each container in the pod shares the same workspace volume, so the compiled JAR from the maven stage was already there.Waiting for deployment "order-service" rollout to finish: 1 of 3—kubectl rollout statuspolls the Kubernetes API and streams progress back to Jenkins. The pipeline waits until all three replicas are running the new image before marking the deploy stage as passed. If a pod fails to start, rollout status returns a non-zero exit code and Jenkins marks the build as failed.Deleting pod: order-service-build-7xk9p— when the build finished, Kubernetes deleted the pod entirely. No residue on any node. The next build starts with a completely fresh environment.
Environment Promotion With Namespaces
Kubernetes namespaces map naturally to deployment environments. A common pattern uses separate namespaces for staging, qa, and production. Jenkins promotes the same Docker image through these environments by updating the deployment in each namespace — the image tag never changes, only the namespace it's deployed into.
Image promotion through namespaces — same image, different environments
order-service:61
auto deploy
manual trigger
approval gate
The same image tag (:61-b5c6d7e) is deployed to staging automatically, promoted to QA manually, and promoted to production through an approval gate. The image never changes — only the namespace does.
// Simplified namespace promotion pipeline
// Assumes the Docker image is already built and pushed
// This pipeline handles the environment promotion only
pipeline {
agent {
kubernetes {
yaml '''
spec:
containers:
- name: jnlp
image: jenkins/inbound-agent:latest
- name: kubectl
image: bitnami/kubectl:latest
command: [cat]
tty: true
'''
}
}
parameters {
// Release manager picks which image to promote
string(name: 'IMAGE_TAG', defaultValue: '', description: 'Image tag to promote — e.g. 61-b5c6d7e')
choice(name: 'TARGET_ENV', choices: ['staging', 'qa', 'production'], description: 'Target namespace')
}
stages {
stage('Validate') {
steps {
script {
if (!params.IMAGE_TAG) {
error('IMAGE_TAG is required')
}
echo "Promoting order-service:${params.IMAGE_TAG} to ${params.TARGET_ENV}"
}
}
}
stage('Approve Production') {
// Only require approval for production deploys
when {
expression { return params.TARGET_ENV == 'production' }
}
steps {
input(
message: "Deploy order-service:${params.IMAGE_TAG} to PRODUCTION?",
ok: 'Promote to Production',
submitter: 'release-managers'
)
}
}
stage('Deploy') {
steps {
container('kubectl') {
sh """
kubectl set image deployment/order-service \
order-service=registry.acmecorp.com/order-service:${params.IMAGE_TAG} \
--namespace=${params.TARGET_ENV}
kubectl rollout status deployment/order-service \
--namespace=${params.TARGET_ENV} \
--timeout=180s
"""
}
}
}
}
post {
success {
echo "✅ order-service:${params.IMAGE_TAG} successfully deployed to ${params.TARGET_ENV}"
}
failure {
echo "❌ Promotion of order-service:${params.IMAGE_TAG} to ${params.TARGET_ENV} failed"
}
}
}
Security — RBAC for the Jenkins service account
The Kubernetes service account Jenkins uses must have RBAC permissions scoped to exactly what it needs — and nothing more. A service account with cluster-admin permissions is a serious security risk. Create a Role that allows only get, patch, and watch on Deployments in specific namespaces, and bind it to the Jenkins service account. Never use cluster-admin for Jenkins.
Teacher's Note
The single biggest win from Kubernetes-based agents isn't scalability — it's consistency. Every build starting from a clean pod eliminates an entire class of "flaky build" debugging that teams waste days on.
Practice Questions
1. Which Jenkinsfile step switches execution to a named container inside a Kubernetes pod?
2. Every Kubernetes pod used as a Jenkins agent must include which mandatory container that handles communication with the Jenkins master?
3. Which kubectl command does Jenkins use to wait for a Kubernetes deployment to finish rolling out before marking the stage as passed?
Quiz
1. What is the main operational advantage of Kubernetes pod agents over static VM agents?
2. How does the compiled JAR built in the maven container become available in the docker container in the same pod?
3. What is the correct RBAC approach for the Kubernetes service account used by Jenkins?
Up Next · Lesson 24
Handling Pipeline Failures
Retry logic, catchError, rollbacks, and the patterns that keep a broken build from becoming a production incident.