Introduction — Why run Jenkins on Kubernetes?
If you’ve been running Jenkins on a VM or a single server, you know the pain when that server goes down: builds stop, history disappears, and everyone looks at you. Running Jenkins on Kubernetes gives you the flexibility to manage lifecycle, scale agents, and treat Jenkins as another cloud-native application — but you still need reliable storage and correct RBAC, probes, and networking.
In this guide I’ll walk you through a practical, easy-to-follow deployment of Jenkins on Kubernetes using YAML manifests. You’ll get commands you can copy-paste, explanations for why each step matters, and small production-minded notes so you don’t end up surprised when a node is drained or a pod restarts.

What this tutorial covers (high level)
- Create a
devops-toolsnamespace - Create a service account with cluster-admin-like permissions for Jenkins
- Provision local persistent storage (demo) or show how to swap to cloud storage
- Deploy Jenkins with health probes and security context
- Expose Jenkins via a NodePort (or suggest alternatives for production)
- Retrieve the initial admin password and finish setup
All YAML examples in this post are taken from the scriptcamp/kubernetes-jenkins repo — clone it if you want the full files:
git clone https://github.com/scriptcamp/kubernetes-jenkins
Step 1 — Create a namespace for DevOps tools
Keeping CI/CD tooling in its own namespace helps with organization, resource quotas, and RBAC scoping.
kubectl create namespace devops-tools
This keeps Jenkins and other DevOps components separate from application namespaces.
Step 2 — Create a service account (RBAC)
Jenkins often needs to talk to the Kubernetes API (for dynamic agents, cluster updates, etc.). The simplest approach for demos is to create a service account and give it broad cluster permissions. Warning: giving cluster-wide * permissions is permissive — for production, narrow these rules to only the required verbs and resources.
serviceAccount.yaml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: jenkins-admin
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["*"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-admin
namespace: devops-tools
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: jenkins-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jenkins-admin
subjects:
- kind: ServiceAccount
name: jenkins-admin
namespace: devops-tools
Apply it:
kubectl apply -f serviceAccount.yaml
Tip: When you harden RBAC later, follow the principle of least privilege: allow only namespaces, resources and verbs Jenkins actually needs (Pods, Secrets, ConfigMaps, ServiceAccounts, Deployments, Jobs, etc.).
Step 3 — Persistent storage for Jenkins data
Jenkins stores its configuration, plugins, and build history under /var/jenkins_home. If the pod is ephemeral without persistent storage, you lose everything on restart. For demos you can use a local PersistentVolume; for production, use your cloud or SAN-backed storage class (AWS EBS, GCE PD, Azure Disk, NFS, or an operator-managed CSI solution).
volume.yaml (demo using local storage)
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv-volume
labels:
type: local
spec:
storageClassName: local-storage
claimRef:
name: jenkins-pv-claim
namespace: devops-tools
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
local:
path: /mnt
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- worker-node01
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jenkins-pv-claim
namespace: devops-tools
spec:
storageClassName: local-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
Important: Replace worker-node01 with the actual worker node hostname from kubectl get nodes. Local PVs bind to a single node — if that node is removed, you’ll lose the data. Prefer cloud/storage-class-backed PVs for HA.
Apply:
kubectl apply -f volume.yaml
Step 4 — Deployment: run Jenkins with probes and security context
This deployment uses the official Jenkins LTS image, sets resource requests/limits, readiness/liveness probes, mounts the PVC, and runs Jenkins with a user that can write to the volume.
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: devops-tools
spec:
replicas: 1
selector:
matchLabels:
app: jenkins-server
template:
metadata:
labels:
app: jenkins-server
spec:
securityContext:
fsGroup: 1000
runAsUser: 1000
serviceAccountName: jenkins-admin
containers:
- name: jenkins
image: jenkins/jenkins:lts
resources:
limits:
memory: "2Gi"
cpu: "1000m"
requests:
memory: "500Mi"
cpu: "500m"
ports:
- name: httpport
containerPort: 8080
- name: jnlpport
containerPort: 50000
livenessProbe:
httpGet:
path: "/login"
port: 8080
initialDelaySeconds: 90
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
path: "/login"
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
volumeMounts:
- name: jenkins-data
mountPath: /var/jenkins_home
volumes:
- name: jenkins-data
persistentVolumeClaim:
claimName: jenkins-pv-claim
Apply and verify:
kubectl apply -f deployment.yaml
kubectl get deployments -n devops-tools
kubectl describe deployment jenkins -n devops-tools
kubectl get pods -n devops-tools
Alternative (not persistent): If you just want a throwaway Jenkins for experimenting, replace the volume with emptyDir: {} — but don’t do this if you want to keep plugins, jobs or build history.
Step 5 — Expose Jenkins (Service YAML)
You can expose Jenkins multiple ways:
- NodePort (quick demo) — exposes on all node IPs and a high port (example uses
32000). - LoadBalancer — for cloud environments (AWS/Google/Azure). Creates a cloud LB with a stable public IP.
- Ingress — best practice in production to provide host-based routing, TLS, and central auth.
service.yaml (NodePort example)
apiVersion: v1
kind: Service
metadata:
name: jenkins-service
namespace: devops-tools
annotations:
prometheus.io/scrape: 'true'
prometheus.io/path: /
prometheus.io/port: '8080'
spec:
selector:
app: jenkins-server
type: NodePort
ports:
- port: 8080
targetPort: 8080
nodePort: 32000
Apply it:
kubectl apply -f service.yaml
Open Jenkins: http://<node-ip>:32000.
Note: If you have Ingress, prefer creating an Ingress rule and TLS certificate (Let’s Encrypt or your corporate CA) and keep NodePort for internal clusters or debugging.
Step 6 — Get the initial admin password
Jenkins shows an initial admin password on first start. You can get it from logs or by reading the secret file inside the pod.
Find the pod name:
kubectl get pods -n devops-tools
Get logs and search for initialAdminPassword or read the file directly:
kubectl logs <jenkins-pod-name> -n devops-tools
# or
kubectl exec -it <jenkins-pod-name> -n devops-tools -- cat /var/jenkins_home/secrets/initialAdminPassword
Use that password to unlock Jenkins, install plugins and create your admin user.
Production considerations & best practices
- Use a stable, replicated storage solution — cloud disks, NFS, or a CSI-backed distributed storage (Longhorn, OpenEBS, Portworx) so data survives node failures.
- Harden RBAC — do not keep
ClusterRolewith*permissions in production. Create scoped roles for Kubernetes operations Jenkins actually needs. - Run Jenkins masters with Anti-affinity — for HA, run multiple masters or use backup/restore workflows (Jenkins master HA is non-trivial; many teams prefer backing up
JENKINS_HOME). - Use agents (ephemeral pods) — run builds on ephemeral Kubernetes agents (Kubernetes plugin or a cloud-native agents approach) so master stays lightweight.
- Backups — schedule regular backups of
/var/jenkins_home(or the underlying PV snapshot) and test restores. - Monitoring and Alerts — scrape Jenkins metrics and logs using Prometheus/Grafana and alert on job failures, queue length, and disk pressure.
Troubleshooting quick tips
- Pod stuck
ContainerCreating— checkkubectl describe podfor volume or node affinity issues. jenkinscannot write to volume — ensurefsGroupandrunAsUserare set and the underlying PV permissions allow write.Readinessprobe failing — increaseinitialDelaySecondsif Jenkins takes longer to boot while installing plugins.
Conclusion
Hosting Jenkins on Kubernetes modernizes your CI/CD platform but requires attention to storage, RBAC, and availability. For experiments and learning, the manifests above get you running quickly; for production, swap local PVs with a durable CSI-backed storage class, narrow RBAC permissions, and use Ingress/LoadBalancer with TLS.
Repository used as reference: https://github.com/scriptcamp/kubernetes-jenkins