Running static Jenkins agents used to mean paying for idle EC2 instances around the clock, or manually scaling a fixed pool up and down. Moving Jenkins agents onto Kubernetes solved that completely for me — agents now spin up as pods exactly when a build starts and disappear the moment it finishes. This guide covers both sides of that integration: running Jenkins agents on Kubernetes, and using Jenkins to deploy applications onto Kubernetes clusters.
Why Kubernetes and Jenkins Fit Together
Kubernetes is fundamentally about running many containerized workloads reliably; Jenkins is fundamentally about running many build workloads reliably. Combining them means every build gets a fresh, isolated pod (no more “works on this agent but not that one” drift), agent capacity scales automatically with demand, and you can define agent pod specs — CPU, memory, specific tool images — per pipeline rather than maintaining a fixed set of static agents.
Jenkins Architecture with Kubernetes
- Controller: Can run inside the cluster (as a Deployment with persistent storage for
$JENKINS_HOME) or outside it, connecting to the cluster’s API server. - Kubernetes Plugin: The core integration piece — it talks to the Kubernetes API to dynamically create agent pods per build and tear them down afterward.
- Pod Templates: Define what an agent pod looks like — which container images, how much CPU/memory, what volumes are mounted.
- Cloud config: Registered under Manage Jenkins > Clouds, pointing Jenkins at the Kubernetes API endpoint and namespace to provision agents in.
Prerequisites
- A running Kubernetes cluster (EKS, GKE, AKS, or self-managed) with
kubectlaccess - Helm (recommended for installing Jenkins) or raw manifests
- Cluster admin or namespace-scoped permissions to create Deployments, Services, and RBAC roles
Step 1: Install Jenkins on Kubernetes via Helm
helm repo add jenkins https://charts.jenkins.io
helm repo update
helm install jenkins jenkins/jenkins \
--namespace jenkins --create-namespace \
--set controller.serviceType=LoadBalancer \
--set persistence.enabled=true \
--set persistence.size=20Gi
Retrieve the initial admin password:
kubectl exec -n jenkins -it svc/jenkins -c jenkins -- \
/bin/cat /run/secrets/additional/chart-admin-password
Step 2: Set Up RBAC for Jenkins to Provision Agent Pods
The Helm chart creates a service account and role by default, but here’s what it looks like explicitly, useful to understand or customize:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jenkins-agent-role
namespace: jenkins
rules:
- apiGroups: [""]
resources: ["pods", "pods/exec", "pods/log", "persistentvolumeclaims"]
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jenkins-agent-rolebinding
namespace: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
namespace: jenkins
roleRef:
kind: Role
name: jenkins-agent-role
apiGroup: rbac.authorization.k8s.io
Step 3: Install and Configure the Kubernetes Plugin
If you’re not using the Helm chart’s built-in configuration, install the Kubernetes Plugin manually and configure it under Manage Jenkins > Clouds > Add a new cloud > Kubernetes:
- Kubernetes URL: usually auto-detected if Jenkins runs in-cluster
- Kubernetes Namespace:
jenkins(or a dedicatedjenkins-agentsnamespace) - Jenkins URL: the internal service URL agents use to connect back
Step 4: Define a Pod Template for Agents
Pod templates can be configured in the UI or, more maintainably, directly in the Jenkinsfile using the kubernetes agent type:
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9-eclipse-temurin-17
command:
- sleep
args:
- 99d
- name: docker
image: docker:24-dind
securityContext:
privileged: true
- name: kubectl
image: bitnami/kubectl:latest
command:
- sleep
args:
- 99d
'''
}
}
stages {
stage('Build') {
steps {
container('maven') {
sh 'mvn clean package'
}
}
}
stage('Build and Push Image') {
steps {
container('docker') {
sh '''
docker build -t myrepo/myapp:$BUILD_NUMBER .
docker push myrepo/myapp:$BUILD_NUMBER
'''
}
}
}
stage('Deploy') {
steps {
container('kubectl') {
sh '''
kubectl set image deployment/myapp myapp=myrepo/myapp:$BUILD_NUMBER -n production
kubectl rollout status deployment/myapp -n production
'''
}
}
}
}
}
Each container() block runs its steps inside a specific container within the same pod — this multi-container pattern is the standard way to give a single build access to different toolchains (Maven, Docker, kubectl) without installing everything onto one bloated image.
Step 5: Handling Docker-in-Docker Securely
Running Docker builds inside Kubernetes pods traditionally required privileged mode (as shown above), which is a real security concern in shared clusters. Two better alternatives:
Kaniko (build images without a Docker daemon, no privileged mode needed):
container('kaniko') {
sh '''
/kaniko/executor \
--context=dir://$(pwd) \
--dockerfile=Dockerfile \
--destination=myrepo/myapp:$BUILD_NUMBER
'''
}
Pod template addition:
- name: kaniko
image: gcr.io/kaniko-project/executor:debug
command:
- sleep
args:
- 99d
Buildah is another rootless alternative with similar benefits, worth considering if your organization standardizes on Red Hat tooling.
Step 6: Resource Requests and Autoscaling
Set resource requests/limits on agent pod templates so the cluster’s autoscaler can react correctly:
- name: maven
image: maven:3.9-eclipse-temurin-17
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "2"
memory: "4Gi"
Pair this with the Cluster Autoscaler (or Karpenter on EKS) so a burst of concurrent builds automatically provisions additional nodes, then scales back down once the agent pods terminate.
Using Jenkins to Deploy Applications to Kubernetes
Beyond hosting agents, the more common use case is Jenkins deploying application workloads to a Kubernetes cluster — either the same cluster running Jenkins, or a separate target cluster:
stage('Deploy to Target Cluster') {
steps {
withKubeConfig([credentialsId: 'target-cluster-kubeconfig']) {
sh '''
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl rollout status deployment/myapp -n production --timeout=180s
'''
}
}
}
Using withKubeConfig (from the Kubernetes CLI Plugin) with a stored kubeconfig credential lets Jenkins deploy to a completely separate cluster from the one hosting its own agents — common in setups where CI infrastructure and production infrastructure are deliberately isolated.
Using Helm from Jenkins
For applications packaged as Helm charts:
stage('Helm Upgrade') {
steps {
container('helm') {
sh '''
helm upgrade --install myapp ./charts/myapp \
--namespace production \
--set image.tag=$BUILD_NUMBER \
--wait --timeout 5m
'''
}
}
}
The --wait flag blocks until all resources reach a ready state, giving the same fail-fast safety as kubectl rollout status.
Real-World Workflow
- A commit triggers a multibranch pipeline; Jenkins provisions a fresh agent pod with Maven, Kaniko, and kubectl containers.
- Build and test run in the Maven container; on success, Kaniko builds and pushes the image without needing privileged access.
- The kubectl container applies updated manifests or runs a Helm upgrade against the target cluster.
--wait/rollout statusblocks until the deployment is confirmed healthy.- The agent pod terminates immediately after the pipeline finishes, freeing cluster resources for the next build.
Security Best Practices
- Avoid privileged Docker-in-Docker; use Kaniko or Buildah for rootless image builds.
- Scope the Jenkins service account’s RBAC role to only the namespace(s) it needs, never cluster-admin.
- Use separate namespaces (or separate clusters entirely) for Jenkins infrastructure versus production workloads.
- Store target-cluster kubeconfigs as Jenkins Credentials, never inline in pipeline scripts.
- Enable Kubernetes NetworkPolicies to restrict what agent pods can reach on the network.
Troubleshooting
- Agent pods stuck in “Pending”: Usually insufficient cluster resources — check
kubectl describe podfor scheduling failures and confirm the cluster autoscaler is configured. - “Error: Jenkins agent failed to connect”: Confirm the JNLP port and Jenkins controller’s internal service URL are correctly set in the Kubernetes cloud configuration.
- Build hangs indefinitely: Check pod template
idleMinutesandpodRetentionsettings; a hung container can block cleanup if retention policy isn’t configured properly.
FAQs
Do I need a separate Kubernetes cluster for Jenkins agents versus my production workloads? Not strictly required, but recommended for isolation — a runaway build shouldn’t be able to affect production resource availability or, worse, gain network access to production services.
Is the Kubernetes Plugin the only way to run Jenkins agents in containers? It’s the most mature option; the Kubernetes Operator for Jenkins and self-managed Jenkins Configuration as Code (JCasC) setups offer alternative approaches with similar underlying mechanics.
Can Jenkins itself run as a Kubernetes-native CRD-based pipeline instead of the traditional controller model? Not natively — Jenkins remains a traditional controller/agent model even when hosted on Kubernetes; if you want a fully Kubernetes-native pipeline model, that’s a different tool category (like Tekton), though the two can coexist.
How do I persist Jenkins configuration and job history across pod restarts? Use a PersistentVolumeClaim mounted at $JENKINS_HOME, as configured by the Helm chart’s persistence.enabled setting — without it, restarting the controller pod wipes all job history and configuration.
Summary
Integrating Jenkins with Kubernetes pays off on two fronts: dynamically provisioned, isolated build agents that scale with demand and cost nothing when idle, and a natural deployment target for the applications those pipelines build. Multi-container pod templates give each build exactly the toolchain it needs, rootless builders like Kaniko keep things secure, and kubectl/Helm deployment stages with proper wait conditions keep rollouts safe and observable.