The first time I moved Jenkins from a lonely EC2 instance to Kubernetes, I was skeptical it would be worth the migration effort. A few months in, I was a convert – dynamic build agents that scale to zero when idle, no more manually provisioning beefy static agents, and a controller that survives node failures without me getting paged at 3 a.m. If you’re tired of babysitting Jenkins infrastructure, running it on Kubernetes is one of the highest-leverage changes you can make. Here’s how I set it up, end to end.
Why Run Jenkins on Kubernetes
A traditional Jenkins setup has a controller plus a fixed pool of static agents that sit around consuming resources even when nothing is building. On Kubernetes, Jenkins can dynamically provision agent pods on demand using the Kubernetes plugin, run the build, and tear the pod down the moment the job finishes. This gets you:
- True elastic scaling – dozens of parallel builds without pre-provisioning machines.
- Cost efficiency – agents only exist while a build is running.
- Environment consistency – every build starts from a clean pod image, eliminating “works on my machine” agent drift.
- Easier disaster recovery – the controller itself becomes just another Kubernetes deployment that can be rescheduled.
Jenkins Architecture on Kubernetes
At a high level, you have:
- Jenkins Controller – runs as a Deployment or StatefulSet, stores its state (jobs, config, plugins) on a PersistentVolumeClaim, and is exposed via a Service and Ingress.
- Kubernetes Plugin – installed on the controller, it talks to the Kubernetes API to spin up agent pods as needed, using Pod Templates you define (base image, containers, resource limits).
- Dynamic Agents – ephemeral pods that register as Jenkins agents over JNLP, run exactly one build, then get deleted automatically.
- RBAC – a ServiceAccount with permissions scoped to create/delete pods in the target namespace, so Jenkins can manage its own agents securely.
Step 1: Prerequisites
- A running Kubernetes cluster (EKS, GKE, AKS, or local via Minikube/kind).
kubectlconfigured to talk to the cluster.- Helm installed (recommended, though raw manifests work too).
Step 2: Install Jenkins Using Helm
helm repo add jenkins https://charts.jenkins.io
helm repo update
kubectl create namespace jenkins
helm install jenkins jenkins/jenkins \
--namespace jenkins \
--set controller.serviceType=LoadBalancer \
--set persistence.enabled=true \
--set persistence.size=20Gi
This deploys the controller with persistent storage, a default admin user, and the Kubernetes plugin pre-installed. Fetch the initial admin password:
kubectl exec -n jenkins -it svc/jenkins -c jenkins -- \
/bin/cat /run/secrets/additional/chart-admin-password
Step 3: Set Up RBAC for Dynamic Agents
The Helm chart creates a ServiceAccount and Role automatically, but if you’re doing this manually, here’s the minimum RBAC you need:
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: jenkins
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jenkins-agent
namespace: jenkins
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec", "pods/log", "persistentvolumeclaims"]
verbs: ["create", "delete", "get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["events"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jenkins-agent-binding
namespace: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
namespace: jenkins
roleRef:
kind: Role
name: jenkins-agent
apiGroup: rbac.authorization.k8s.io
Step 4: Configure the Kubernetes Cloud in Jenkins
Go to Manage Jenkins → Clouds → Add a new cloud → Kubernetes. Set:
- Kubernetes URL: usually
https://kubernetes.defaultif Jenkins itself runs in-cluster. - Kubernetes Namespace:
jenkins(or wherever agents should run). - Jenkins URL: the internal service URL, e.g.
http://jenkins.jenkins.svc.cluster.local:8080.
Click Test Connection to confirm.
Step 5: Define a Pod Template
You can define pod templates in the UI, or better, as code inside your Jenkinsfile using the kubernetes agent directive:
pipeline {
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9-eclipse-temurin-17
command: ['sleep']
args: ['infinity']
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
- name: docker
image: docker:24-dind
securityContext:
privileged: true
"""
}
}
stages {
stage('Build') {
steps {
container('maven') {
sh 'mvn -B clean package'
}
}
}
stage('Build Image') {
steps {
container('docker') {
sh 'docker build -t myapp:${BUILD_NUMBER} .'
}
}
}
}
}
Every stage runs inside a specific container within the same pod, and the whole pod disappears once the pipeline finishes.
Step 6: Expose Jenkins Externally
For production, put Jenkins behind an Ingress with TLS:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jenkins
namespace: jenkins
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts: ["jenkins.mycompany.com"]
secretName: jenkins-tls
rules:
- host: jenkins.mycompany.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jenkins
port:
number: 8080
Scaling and Resource Management
Set sensible resources.requests and resources.limits on every agent pod template – without them, a runaway build can starve the node and take down other pods. Combine this with a Cluster Autoscaler so the underlying node pool grows and shrinks with build demand instead of you managing node count manually.
Integrating with the Wider Toolchain
Running on Kubernetes makes several other integrations more natural:
- Docker – use Docker-in-Docker or, better, Kaniko/Buildah for rootless image builds inside pods without privileged mode.
- Helm/Terraform – deploy stages can run
helm upgradeorterraform applydirectly from an agent pod that has the right service account bound to cloud IAM roles (via IRSA on EKS, Workload Identity on GKE). - Git/GitHub – webhook-triggered builds work the same as any Jenkins setup; just make sure your Ingress is reachable from GitHub for webhook delivery.
- Monitoring – expose Jenkins’ Prometheus metrics via the Prometheus plugin and scrape them with a
ServiceMonitorif you run the kube-prometheus-stack.
Monitoring and Troubleshooting
- Agent pods stuck in Pending – usually insufficient cluster resources; check
kubectl describe podfor scheduling events. - “Unable to connect to controller” – check that the JNLP port (default 50000) is exposed via the Jenkins service and reachable from the agent namespace.
- Persistent volume issues after restart – confirm your StorageClass supports
ReadWriteOnceproperly and that only one controller pod is scheduled at a time (avoid running the controller as a Deployment with replicas > 1 unless using a proper HA setup). - Slow pod startup – pre-pull large images with a
DaemonSetor use a private in-cluster registry to cut cold-start time.
Security Best Practices
- Never run agent containers as
privileged: trueunless absolutely required (e.g., legacy Docker-in-Docker); prefer Kaniko or Buildah for rootless builds. - Restrict the Jenkins ServiceAccount’s RBAC to only the namespace it needs.
- Store cloud credentials (AWS/GCP/Azure) using Workload Identity or IRSA instead of long-lived static keys baked into pods.
- Enable Jenkins’ built-in Role-Based Access Control (via the Role-based Authorization Strategy plugin) so not every developer has admin rights to the controller.
Best Practices
- Keep the controller lightweight – it should orchestrate, not execute heavy builds itself; always delegate work to agent pods.
- Version your pod templates as YAML in source control, not as UI-only configuration.
- Use namespace-per-team if multiple teams share one cluster, to isolate resource usage and RBAC.
- Back up the Jenkins home directory (or use JCasC – Jenkins Configuration as Code – so the whole controller config is reproducible from Git).
FAQs
Can I run Jenkins itself as a pod without Helm? Yes, raw Deployment/Service/PVC manifests work fine; Helm just saves you from writing them by hand.
Do agent pods need Docker installed on the node? Not with Kaniko or Buildah, which build images without a Docker daemon, making them better suited to Kubernetes-native environments.
How do I handle Jenkins upgrades on Kubernetes? Update the image tag in your Helm values and run helm upgrade; because state lives on a PVC, upgrades are generally low-risk if you snapshot the volume first.
Can I run multiple Jenkins controllers on one cluster? Yes, each in its own namespace with its own PVC – useful for team isolation or blue/green controller upgrades.
Is Kubernetes overkill for a small team? If you’re already running Kubernetes for your applications, it’s usually less overhead than maintaining separate static Jenkins agent VMs. If you have no Kubernetes footprint at all, a simpler Docker-based setup might be more proportionate to your needs.
Summary
Running Jenkins on Kubernetes turns your CI/CD infrastructure from a set of static, hand-maintained machines into an elastic, self-healing system. Install the controller via Helm, configure the Kubernetes cloud plugin, define pod templates as code in your Jenkinsfiles, and let the cluster handle scaling agents up and down automatically. Combined with proper RBAC, resource limits, and Workload Identity for cloud credentials, this setup scales from a five-person team to hundreds of concurrent builds without you manually provisioning a single VM.
References
- Jenkins Kubernetes Plugin documentation: https://plugins.jenkins.io/kubernetes/
- Official Jenkins Helm chart: https://github.com/jenkinsci/helm-charts
- Jenkins Configuration as Code (JCasC): https://www.jenkins.io/projects/jcasc/
- Kubernetes RBAC documentation: https://kubernetes.io/docs/reference/access-authn-authz/rbac/
- Kaniko (rootless image builds): https://github.com/GoogleContainerTools/kaniko
