Learn Jenkins - Continuous Deployment (CD) to Kubernetes & Helm
Episode 16 of 21

Learn Jenkins - Continuous Deployment (CD) to Kubernetes & Helm

In this episode we discuss continuous deployment to Kubernetes and Helm, starting from configuring kubeconfig securely with the withKubeConfig step to automatic Helm chart releases. We also learn GitOps integration with ArgoCD and Flux so that deployments always stay in sync with the Git repository.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

In episode 15 we delivered the application to a Linux server via SSH and Ansible — a classic approach that remains valid for virtual machines. Now it is time to level up: we deploy to a Kubernetes cluster with Helm, the combination used by almost every company running on containers. Kubernetes provides rolling updates, self-healing, and a uniform environment across nodes. Helm lets dozens of YAML manifest lines be packaged, versioned, and released like a regular application — think of Helm as apt-get for Kubernetes.

In this episode we cover three main things:

  1. Deploying manifests to the cluster securely with the withKubeConfig step and kubectl apply.
  2. Automating application releases with helm upgrade --install.
  3. GitOps integration with ArgoCD and Flux so the cluster always stays in sync with Git.

Why Kubernetes & Helm for CD?

After a successful build and a pushed Docker image, the application still has to be deployed. Manually deploying to hundreds of pods with kubectl run is clearly not scalable. Kubernetes gives us:

  • Rolling updates — pods are replaced gradually without downtime.
  • Self-healing — crashed pods are automatically replaced by the ReplicaSet.
  • Declarative state — we just say "want 3 replicas", and Kubernetes takes care of the rest.

Meanwhile Helm solves the problem of repetitive YAML manifests across environments. One chart can serve both staging and production, differing only in the values file. This is analogous to templating, but specifically for infrastructure.

Note

Prerequisites for this episode: a Kubernetes cluster accessible from the Jenkins agent, and kubectl and helm installed on the agent. If not yet present, add both tools to the Docker agent image as we discussed in episode 8.

Setting Up Secure Cluster Access

Access to the cluster is controlled by the kubeconfig file, which contains the server address, token, and context. This file is the key to our house — if it leaks, anyone can destroy the production cluster. That is why a kubeconfig must never be copied into the repository or workspace.

The safe way in Jenkins is to store it as a Secret file credential. In Manage Jenkins then Credentials, add the kubeconfig from the production cluster's ~/.kube/config file as a Secret file with the ID kubeconfig-prod.

Warning

A kubeconfig contains a token equivalent to a cluster password. Make sure this credential is only visible to the jobs that need it, and restrict the context inside it to the required namespace — do not use the cluster admin context for ordinary pipelines.

Deploying with the withKubeConfig Step

The Kubernetes CLI plugin provides the withKubeConfig step, which writes the kubeconfig from the credential to a temporary file, then removes it when the block finishes. We do not need to bother exporting variables or storing files in the workspace.

JenkinsJenkinsfile - deploying manifests with withKubeConfig
stage('Deploy ke Kubernetes') {
    steps {
        withKubeConfig(credentialsId: 'kubeconfig-prod', serverUrl: 'https://k8s-prod.example.com') {
            sh 'kubectl apply -f k8s/ --namespace production'
        }
    }
}

Let's unpack what happens:

  • credentialsId points to the Secret file we created earlier.
  • serverUrl tells the step which cluster server is being used.
  • Inside the block, the kubectl apply command automatically reads the kubeconfig from that temporary file.

The command kubectl apply -f k8s/ applies all manifests in the k8s/ folder — Deployment, Service, and Ingress. This command is idempotent, so it is safe to run repeatedly.

Validating Manifests Before Apply

Do not apply straight to production without validation. Add a dry-run as a smoke test:

Validate manifests before apply
kubectl apply -f k8s/ --namespace production --dry-run=client -o yaml

This step ensures the YAML is valid and its schema is recognized by Kubernetes. Only then perform the real apply.

Automating Helm Chart Releases

After the basic manifests are running, we move to Helm. The core principle: helm upgrade --install is idempotent — it installs if not present, and upgrades if already present. One command for both needs.

Release a chart to production
helm upgrade --install my-app ./chart \
  --namespace production \
  --values values.production.yaml \
  --set image.repository=registry.example.com/my-app \
  --set image.tag=1.0.42

The --values flag uses a per-environment configuration file, while --set can override specific values, such as the image tag from the build. In Jenkins, the tag can be taken from the BUILD_NUMBER environment variable so every build produces a new release.

JenkinsJenkinsfile - Helm release with a dynamic tag
stage('Release Helm Chart') {
    steps {
        sh 'helm upgrade --install my-app ./chart \
            --namespace production \
            --values values.production.yaml \
            --set image.tag=1.0.${BUILD_NUMBER}'
    }
}

Jenkins interpolates the BUILD_NUMBER value in that command before passing it to the shell. For charts from public repositories, first add them with helm repo add, then install directly by chart name.

Tip

Always store per-environment values (staging, production) in the repository as separate files, rather than overriding them manually from the console. This preserves the audit trail and makes releases reproducible.

GitOps Integration: ArgoCD & Flux

Finally, we discuss the modern pattern: GitOps. The idea is that the Git repository becomes the single source of truth for the cluster state. Instead of Jenkins executing commands directly against the cluster, Jenkins simply updates the Git repository containing the manifests, then ArgoCD or Flux pulls that change and syncs the cluster. This architecture makes cluster operations fully auditable through commit history.

The workflow looks like this:

  1. The Jenkins pipeline builds an image and pushes it to the registry.
  2. Jenkins commits the image tag change to the gitops repository.
  3. ArgoCD or Flux detects the Git change and reconciles the cluster.
  4. The cluster is updated without Jenkins touching the cluster directly.

An example of Jenkins bumping the tag in the gitops repository:

Jenkins - committing the new tag to the gitops repo
git clone git@github.com:my-org/gitops-repo.git
cd gitops-repo
sed -i 's|imageTag: .*|imageTag: 1.0.42|' apps/my-app/values.yaml
git add apps/my-app/values.yaml
git commit -m "chore: bump my-app to 1.0.42"
git push origin main

Both ArgoCD and Flux automatically read that change. If you want to trigger synchronization directly (without waiting for the polling interval), Jenkins can call their CLIs:

JenkinsJenkins - triggering an ArgoCD sync
withCredentials([string(credentialsId: 'argocd-token', variable: 'ARGOCD_TOKEN')]) {
    sh 'argocd login argocd.example.com --auth-token=$ARGOCD_TOKEN --grpc-web'
    sh 'argocd app sync my-app --grpc-web'
}

For Flux, the equivalent command is flux reconcile kustomization my-app.

ArgoCD vs Flux — Which to Choose?

AspectArgoCDFlux
Sync triggerargocd CLI or webhookflux CLI or interval
InterfaceMature web UICLI plus optional dashboard
BackendApplication CRDKustomization and HelmRelease
Best fitTeams that like visual UITeams that prefer full declarative

Both are equally robust. Choose based on team culture: ArgoCD excels at visibility, Flux is more minimalist and purely declarative.

Important

In the GitOps pattern, Jenkins no longer needs cluster admin credentials for production deployments — only Git access to the gitops repository. This significantly narrows the attack surface.

Conclusion

In this episode we covered continuous deployment to Kubernetes and Helm:

  • The withKubeConfig step uses the kubeconfig from a Secret file credential without storing it in the workspace.
  • kubectl apply -f k8s/ applies manifests idempotently, and can be validated first with --dry-run.
  • helm upgrade --install installs and upgrades a chart at once, with per-environment values.
  • GitOps with ArgoCD or Flux moves cluster synchronization responsibility to Git, making deployments easily auditable and rollbackable.

Automated deployment is great, but without notifications the team will not know whether a build succeeded or failed. In episode 17 we will integrate production notifications to Slack, Microsoft Teams, Telegram, and custom HTML email — so every build event reaches the right people immediately. See you there!

Learn Jenkins - Continuous Deployment (CD) to Kubernetes & Helm | Learn Jenkins