How to Deploy Jenkins on Kubernetes

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

  1. Why Run Jenkins on Kubernetes?
  2. Prerequisites Before Deployment
  3. Method 1: Deploying Jenkins with YAML Manifests
  4. Method 2: Deploying Jenkins with a Helm Chart
  5. Configuring Persistent Storage
  6. Exposing Jenkins via Ingress
  7. Configuring the Kubernetes Plugin for Dynamic Agents
  8. Securing Jenkins in a Kubernetes Environment
  9. Monitoring and Logging
  10. Backup and Disaster Recovery Strategy
  11. Common Troubleshooting Issues
  12. 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 ReadWriteOnce or ReadWriteMany, 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 jenkins or 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.

(Visited 1 times, 2 visits today)

You may also like