How to Deploy Jenkins on Kubernetes
Jenkins remains one of the most widely used CI/CD automation tools in the world, even after more than a decade in the industry. However, running Jenkins the traditional way — on a single physical server or virtual machine — comes with real limitations: it’s hard to scale, prone to downtime during maintenance, and wasteful of resources since the server has to stay on even when no jobs are running.
This is where Kubernetes comes in. By running Jenkins on Kubernetes, you gain elasticity, resilience, and far better resource efficiency. Build agents can be spun up dynamically as pods and automatically removed once a job finishes. This article walks through, in depth, how to deploy Jenkins on Kubernetes — from initial preparation to a secure, production-ready, scalable setup.
Table of Contents
- Why Run Jenkins on Kubernetes?
- Prerequisites Before Deployment
- Method 1: Deploying Jenkins with YAML Manifests
- Method 2: Deploying Jenkins with a Helm Chart
- Configuring Persistent Storage
- Exposing Jenkins via Ingress
- Configuring the Kubernetes Plugin for Dynamic Agents
- Securing Jenkins in a Kubernetes Environment
- Monitoring and Logging
- Backup and Disaster Recovery Strategy
- Common Troubleshooting Issues
- Conclusion
Why Run Jenkins on Kubernetes?
Before diving into the technical steps, it’s worth understanding the business and technical reasons behind moving Jenkins to Kubernetes.
1. Elastic Build Agents
In a traditional Jenkins setup, DevOps teams typically maintain several static agents that are always running. When build load is low, this capacity sits idle and wastes infrastructure budget. On Kubernetes, Jenkins can use the Kubernetes Plugin to spin up agent pods on demand — a new pod is created when a job runs and automatically deleted once it finishes.
2. High Availability
Kubernetes provides self-healing out of the box. If the Jenkins master pod crashes or the node it runs on fails, Kubernetes automatically reschedules it, significantly reducing downtime compared to a conventional server.
3. Portability and Environment Consistency
Because Jenkins runs inside a container, the build environment stays far more consistent across staging, testing, and production — reducing the classic “works on my machine” problem.
4. Better Resource Management
Kubernetes lets you apply resource requests and limits at a granular pod level, giving infrastructure teams precise control over CPU and memory usage for both the Jenkins master and its agents.
5. Integration with the Cloud-Native Ecosystem
Jenkins on Kubernetes integrates more naturally with other cloud-native tools — Prometheus for monitoring, Vault for secrets management, and Istio for service mesh, among others.
Prerequisites Before Deployment
Before starting the deployment process, make sure the following prerequisites are in place:
- An active Kubernetes cluster — managed (GKE, EKS, AKS) or self-managed (kubeadm, k3s). Kubernetes 1.23 or later is recommended.
- kubectl installed and configured to communicate with the target cluster.
- Helm 3+, if you choose the Helm chart deployment method.
- A dedicated namespace for Jenkins, e.g.
jenkins, to keep resources organized and easier to monitor. - A storage class that supports
ReadWriteOnceorReadWriteMany, depending on your architecture. - A domain or subdomain, if Jenkins will be exposed via Ingress with HTTPS.
The first step is creating a dedicated namespace:
kubectl create namespace jenkins
Verify the namespace was created:
kubectl get namespaces
With a separate namespace, you can apply resource quotas, network policies, and RBAC specifically to Jenkins workloads without affecting other workloads in the same cluster.
Method 1: Deploying Jenkins with YAML Manifests
This method is ideal if you want to understand Jenkins’ configuration details explicitly, without Helm’s abstraction layer.
Creating a Persistent Volume Claim
Jenkins needs persistent storage so that configuration, jobs, and plugins aren’t lost when the pod restarts.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pvc
namespace: jenkins
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
Creating a ServiceAccount and RBAC
Jenkins needs access to the Kubernetes API so it can dynamically spawn agent pods.
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-sa
namespace: jenkins
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jenkins-role
namespace: jenkins
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "pods/exec", "secrets", "persistentvolumeclaims"]
verbs: ["create", "delete", "get", "list", "watch", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jenkins-rolebinding
namespace: jenkins
subjects:
- kind: ServiceAccount
name: jenkins-sa
namespace: jenkins
roleRef:
kind: Role
name: jenkins-role
apiGroup: rbac.authorization.k8s.io
Creating the Jenkins Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: jenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
serviceAccountName: jenkins-sa
containers:
- name: jenkins
image: jenkins/jenkins:lts-jdk17
ports:
- containerPort: 8080
- containerPort: 50000
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1"
memory: "2Gi"
volumeMounts:
- name: jenkins-home
mountPath: /var/jenkins_home
volumes:
- name: jenkins-home
persistentVolumeClaim:
claimName: jenkins-pvc
Notice that the image used is jenkins/jenkins:lts-jdk17, the LTS release recommended for production because it’s more stable than the weekly release channel.
Creating a Service
apiVersion: v1
kind: Service
metadata:
name: jenkins-service
namespace: jenkins
spec:
selector:
app: jenkins
ports:
- name: http
port: 8080
targetPort: 8080
- name: agent
port: 50000
targetPort: 50000
type: ClusterIP
Apply all the manifests with:
kubectl apply -f jenkins-pvc.yaml
kubectl apply -f jenkins-rbac.yaml
kubectl apply -f jenkins-deployment.yaml
kubectl apply -f jenkins-service.yaml
Check the pod status:
kubectl get pods -n jenkins
Once the pod status shows Running, Jenkins is ready to be accessed through port-forwarding for an initial test:
kubectl port-forward svc/jenkins-service 8080:8080 -n jenkins
Open your browser at http://localhost:8080 and retrieve the initial admin password with:
kubectl exec -it <jenkins-pod-name> -n jenkins -- cat /var/jenkins_home/secrets/initialAdminPassword
Method 2: Deploying Jenkins with a Helm Chart
For most teams, using the official Helm chart is far more practical and recommended for long-term use, since it supports upgrades, rollbacks, and customization through a values.yaml file.
Adding the Helm Repository
helm repo add jenkins https://charts.jenkins.io
helm repo update
Creating a Custom values.yaml
controller:
image: "jenkins/jenkins"
tag: "lts-jdk17"
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1"
memory: "2Gi"
serviceType: ClusterIP
installPlugins:
- kubernetes:latest
- workflow-aggregator:latest
- git:latest
- configuration-as-code:latest
persistence:
enabled: true
storageClass: "standard"
size: 10Gi
agent:
enabled: true
image: "jenkins/inbound-agent"
tag: "latest"
Installing via Helm
helm install jenkins jenkins/jenkins -n jenkins -f values.yaml
Monitor the installation:
kubectl get pods -n jenkins -w
Once complete, Helm typically prints instructions for retrieving the admin password and setting up port-forwarding, e.g.:
kubectl exec --namespace jenkins -it svc/jenkins -c jenkins -- /bin/cat /run/secrets/additional/chart-admin-password
The advantage of the Helm method is that upgrading the configuration is as simple as editing values.yaml and running:
helm upgrade jenkins jenkins/jenkins -n jenkins -f values.yaml
Configuring Persistent Storage
One of the most common mistakes when deploying Jenkins on Kubernetes is neglecting a proper storage strategy. Here are the key considerations:
Use a Storage Class That Matches Your Provider
On GKE, use standard-rwo or premium-rwo; on EKS, use gp3; on AKS, use managed-premium. The storage class determines I/O performance, which directly affects build speed — especially for jobs with heavy file operations.
Back Up Volumes Regularly
Jenkins data stored under /var/jenkins_home includes job configurations, encrypted credentials, and build history. This data is critical and should be backed up routinely, for example with a tool like Velero.
Consider Volume Size Carefully
For small installations, 10Gi is usually enough. For organizations with hundreds of jobs and long build histories, consider 50Gi or more, and apply build history retention policies so the volume doesn’t fill up quickly.
Exposing Jenkins via Ingress
To make Jenkins accessible through a domain with HTTPS, you need to configure an Ingress Controller such as NGINX Ingress or Traefik.
Here’s an example Ingress manifest using the NGINX Ingress Controller with TLS provided by cert-manager:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jenkins-ingress
namespace: jenkins
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
ingressClassName: nginx
tls:
- hosts:
- jenkins.example.com
secretName: jenkins-tls
rules:
- host: jenkins.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jenkins-service
port:
number: 8080
The proxy-body-size annotation is important because Jenkins often receives large payloads, for example when uploading artifacts or plugins. Without it, large requests can fail with a 413 error.
Once the Ingress is applied, make sure the domain’s DNS points to the Ingress Controller’s external IP, then verify the TLS certificate is active with:
kubectl describe certificate jenkins-tls -n jenkins
Configuring the Kubernetes Plugin for Dynamic Agents
The biggest advantage of running Jenkins on Kubernetes is the ability to run build agents dynamically through the Kubernetes Plugin.
After installing the plugin, go to Manage Jenkins > Clouds > New Cloud, select Kubernetes, and fill in the following configuration:
- Kubernetes URL: usually left blank if Jenkins runs inside the same cluster, since the plugin auto-detects the in-cluster configuration.
- Kubernetes Namespace: set to
jenkinsor a dedicated agent namespace. - Jenkins URL: the internal Jenkins service address, e.g.
http://jenkins-service.jenkins.svc.cluster.local:8080. - Jenkins Tunnel: the address for JNLP communication, e.g.
jenkins-service.jenkins.svc.cluster.local:50000.
Next, add a Pod Template defining the container image to use as an agent, for example:
apiVersion: v1
kind: Pod
metadata:
labels:
jenkins: agent
spec:
containers:
- name: maven
image: maven:3.9-eclipse-temurin-17
command:
- cat
tty: true
- name: docker
image: docker:24-dind
securityContext:
privileged: true
This configuration allows each pipeline to define its own agent directly inside a Jenkinsfile, for example:
pipeline {
agent {
kubernetes {
yamlFile 'pod-template.yaml'
}
}
stages {
stage('Build') {
steps {
container('maven') {
sh 'mvn clean package'
}
}
}
}
}
With this approach, every job runs in an isolated pod that is automatically deleted once finished, keeping the cluster clean and resource usage efficient.
Securing Jenkins in a Kubernetes Environment
Security is a critical aspect, since Jenkins has access to sensitive credentials such as registry tokens, SSH keys, and cloud provider API keys. Here are some best practices worth implementing:
1. Keep RBAC as Minimal as Possible
Avoid granting cluster-admin to the Jenkins ServiceAccount. Only grant the permissions actually needed, scoped to a specific namespace — such as creating and deleting pods.
2. Use External Secret Management
Instead of storing credentials directly in the Jenkins Credentials Store, integrate with HashiCorp Vault or Kubernetes Secrets managed through the External Secrets Operator.
3. Enable Network Policies
Apply a Network Policy so the Jenkins pod can only communicate with the agent pods and services it actually needs, preventing lateral movement in case of a compromise.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: jenkins-network-policy
namespace: jenkins
spec:
podSelector:
matchLabels:
app: jenkins
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: jenkins
4. Keep Images Updated
Always use the latest LTS image and monitor CVEs related to Jenkins core and plugins through the Jenkins Security Advisory.
5. Enable Matrix-Based Security and CSRF Protection
Make sure built-in security options like Matrix Authorization Strategy and “Prevent Cross Site Request Forgery Exploits” are always enabled.
6. Don’t Expose Port 50000 Publicly
This port is used for JNLP agent communication and should only be accessible from within the cluster, never from the public internet.
Monitoring and Logging
To keep Jenkins running smoothly, monitoring is a critical part of day-to-day operations.
Prometheus and Grafana
Install the Prometheus Metrics plugin on Jenkins so that metrics like active job count, build duration, and executor usage can be scraped by Prometheus and visualized in Grafana.
Centralized Logging
Use a stack like EFK (Elasticsearch, Fluentd, Kibana) or Loki to collect logs from both the Jenkins master and agent pods, so logs remain available even after a pod is deleted.
Health Check Probes
Add livenessProbe and readinessProbe to the deployment so Kubernetes can detect an unhealthy Jenkins instance and restart it automatically:
livenessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /login
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
Backup and Disaster Recovery Strategy
Even though Kubernetes provides self-healing, backups are still essential to guard against data corruption or human error.
Back Up Volumes with Velero
Velero allows scheduled snapshots of Persistent Volumes, so Jenkins data can be restored to a specific point in time if something goes wrong.
Export Configuration with Configuration as Code (JCasC)
The Configuration as Code plugin lets you define the entire Jenkins configuration in a YAML file, so a new installation can be replicated quickly without manual reconfiguration.
Test the Restore Process Regularly
A backup that has never been tested for restoration has little real value. Schedule disaster recovery drills at least once a quarter.
Common Troubleshooting Issues
Here are some common issues encountered when deploying Jenkins on Kubernetes, along with their solutions:
Jenkins Pod Stuck in Pending Status
This is usually caused by a PersistentVolumeClaim that can’t be bound. Check it with kubectl describe pvc jenkins-pvc -n jenkins to see the storage class-related error message.
Agent Pods Fail to Connect to the Master
This is typically caused by an incorrect Jenkins Tunnel configuration or an overly restrictive Network Policy. Make sure port 50000 is accessible from the agent namespace.
502 Bad Gateway on Ingress
Check whether the Service selector matches the Jenkins pod’s labels, and confirm that the port defined on the Ingress matches the container port.
Plugins Fail to Install Automatically
Check the pod’s internet connectivity to the Jenkins Update Center, or consider using an internal mirror if the cluster runs in a restricted (air-gapped) network.
Conclusion
Deploying Jenkins on Kubernetes offers substantial advantages over a traditional installation — from elastic build agents and resilience against failure to far more efficient resource usage. Whether you choose manual YAML manifests or a Helm chart, successful implementation hinges on proper storage planning, tightly scoped RBAC, and a well-tested backup strategy.
By leveraging the Kubernetes Plugin for dynamic agents, DevOps teams can run more efficient CI/CD pipelines without maintaining expensive static build infrastructure. Combined with security practices like network policies, external secret management, and Prometheus-based monitoring, Jenkins on Kubernetes is well-equipped to support even enterprise-scale CI/CD needs.
The recommended next step is to start with a staging environment, thoroughly test all critical pipelines, and only then migrate production workloads to this Kubernetes-based Jenkins setup.







