DevCloudLab More courses at DevCloudLab.com →
FLUX CD · SECTION 3 · LECTURE 3

DevCloudLab

Modifying the Helm chart and overriding the default values

Section 3, Lecture 3, from the Flux CD course by DevCloudLab.


What you’ll learn

Overview

This lecture demonstrates how to override Helm chart default values using Flux CD’s HelmRelease custom resource. You’ll create a ConfigMap template in your Helm chart to supply a custom HTML welcome page for Nginx, and then inject that custom value through the HelmRelease manifest. This workflow shows the GitOps approach to managing Helm deployments: chart templates and value overrides are version-controlled, and Flux CD continuously reconciles the desired state in Git with the actual state in the cluster.

Key Concepts

Helm Values: Every Helm chart defines default values for its templates. Values are injected into template placeholders (using the `` syntax) to generate Kubernetes manifests. You can override these defaults at deployment time.

ConfigMap for Configuration: A ConfigMap is a Kubernetes object that stores non-sensitive configuration data as key-value pairs. In this example, we create a ConfigMap containing custom HTML content that Nginx will serve.

HelmRelease Custom Resource: Flux CD’s HelmRelease resource defines how and when to deploy a Helm chart. It allows you to specify value overrides inline (using spec.values) or from separate files (using spec.chart.spec.valuesFiles).

Inline vs File-Based Values: The spec.values field (at the top level of spec) lets you define values directly in the manifest. If you prefer to manage values in separate YAML files, use spec.chart.spec.valuesFiles, which accepts a list of file paths relative to the root of the Git repository the chart comes from, not to the chart directory. Later files in the list override earlier ones. Both approaches achieve the same result: providing custom values to the chart.

Commands Used

Create and switch branches:

git checkout main          # Switch to the main branch
git pull                   # Pull the latest changes from remote
git checkout -b "custom-welcome-page"  # Create and switch to a new branch

Stage, commit, and push changes:

git add -A                 # Stage all modified and new files
git commit -m "message"    # Create a commit with a descriptive message
git push --set-upstream origin custom-welcome-page  # Push the branch and set upstream tracking

Reconcile Flux resources to immediately apply changes:

flux reconcile kustomization flux-system --with-source  # Sync the Kustomization and fetch new chart versions
flux reconcile helmrelease nginx -n default             # Apply the latest HelmRelease configuration to the cluster

Verify resources in the cluster:

kubectl get configmap -n default              # List all ConfigMaps in the default namespace
kubectl describe pod -n default                # Show detailed info about pods
kubectl logs -n default <pod-name>             # View logs from a specific pod

Complete Manifest Files

ConfigMap Template (charts/nginx/templates/configmap.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name: -index-html
data:
  index.html: |
    

Explanation:

Updated Deployment Template (charts/nginx/templates/deployment.yaml)

The chart that helm create scaffolds wraps every optional block in ``, and it already ships volumeMounts and volumes blocks driven from values. Because the ConfigMap we mount is named after the release, a values file cannot supply it, so we replace those two blocks with literal ones. Only the pod spec is shown; everything else in the deployment stays as scaffolded, including the probes and the resources block:

    spec:
      containers:
        - name: 
          securityContext:
          image: ":"
          imagePullPolicy: 
          ports:
            - name: http
              containerPort: 
              protocol: TCP
          livenessProbe:
          readinessProbe:
          resources:
          # replaces the scaffold's block
          volumeMounts:
            - name: html
              mountPath: /usr/share/nginx/html
      # replaces the scaffold's block
      volumes:
        - name: html
          configMap:
            name: -index-html

Explanation:

Chart Version (charts/nginx/Chart.yaml)

apiVersion: v2
name: nginx
description: A Helm chart for Kubernetes
type: application
version: 0.2.0
appVersion: "1.16.0"

Explanation:

HelmRelease (clusters/my-cluster/nginx-helm-release.yaml)

The HelmRelease must live under the path Flux was bootstrapped against, here clusters/my-cluster/. A HelmRelease placed outside that path is never reconciled. This lecture adds one key, indexHtml, to the values: block of the HelmRelease shown at the start of this lecture. If you come straight from the previous lecture, your HelmRelease has no values: block yet: add one under spec, as below.

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: nginx
  namespace: default
spec:
  interval: 10m
  chart:
    spec:
      chart: ./charts/nginx
      sourceRef:
        kind: GitRepository
        name: flux-system
        namespace: flux-system
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      retries: 3
  values:
    replicaCount: 1
    image:
      repository: nginx
      tag: latest
    service:
      type: ClusterIP
      port: 80
    indexHtml: |-
      <!doctype html>
      <html>
      <head>
        <title>My Custom Page</title>
      </head>
      <body>
        <h1>Welcome to my custom Nginx page!</h1>
      </body>
      </html>

Explanation:

Alternative: Using valuesFiles

If you prefer to store values separately, you can use spec.chart.spec.valuesFiles instead. Put values-custom.yaml at the root of your repository, because that is where Flux will look for it. Since valuesFiles replaces the chart’s own values.yaml rather than adding to it, list that file first, by its path from the repository root:

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: nginx
  namespace: default
spec:
  interval: 1m
  chart:
    spec:
      chart: ./charts/nginx
      sourceRef:
        kind: GitRepository
        name: flux-system
        namespace: flux-system
      interval: 1m
      valuesFiles:
        - charts/nginx/values.yaml
        - values-custom.yaml

Explanation:

Step-by-Step Workflow

  1. Prepare the repository: Check out main, pull latest, and create a feature branch.
  2. Add the ConfigMap template: Create charts/nginx/templates/configmap.yaml to hold custom HTML.
  3. Update the Deployment: Replace the scaffold’s value-driven volumeMounts and volumes blocks in deployment.yaml with literal ones that mount the ConfigMap.
  4. Bump the chart version: Increment the version in Chart.yaml (0.1.0 to 0.2.0) to trigger reconciliation.
  5. Define values in HelmRelease: Add indexHtml under spec.values to supply the HTML content from a single source of truth.
  6. Commit and merge: Push the branch, create a merge request, and merge to main.
  7. Reconcile: Run flux reconcile commands to apply changes immediately.
  8. Verify: Refresh the Nginx welcome page in your browser to see the custom content.

Common Issues and Solutions

Error: “no matches for kind ‘HelmRelease’ in version ‘helm.toolkit.fluxcd.io/v2beta1’“ Your manifest uses an API version that Flux removed in version 2.7. Upgrading Flux or its CRDs cannot bring v2beta1 back; the manifest itself has to change. Set apiVersion: helm.toolkit.fluxcd.io/v2 in the HelmRelease file, commit and push, then reconcile:

flux reconcile kustomization flux-system --with-source

The Section 2 lecture on migrating off the removed Flux beta APIs walks through this migration in full.

ConfigMap not mounted in the pod: Verify the ConfigMap exists and is named correctly:

kubectl get configmap -n default
kubectl describe configmap nginx-index-html -n default

Check that the volumeMounts indentation is correct in the deployment template.

Nginx pod stuck in CrashLoopBackOff: Inspect pod logs:

kubectl logs -n default -l app.kubernetes.io/name=nginx

Ensure the mount path /usr/share/nginx/html is correct and the HTML in the ConfigMap is valid.

Changes not appearing after reconcile: Ensure the branch is merged to main and run reconcile with --with-source to pull the latest from Git:

flux reconcile kustomization flux-system --with-source
flux reconcile helmrelease nginx -n default

The browser shows a 404 page with nginx in the footer: That is the ingress controller’s default backend, which means no Ingress is routing to your release. The previous lecture enabled Ingress in the chart’s own values.yaml; check it is still there:

kubectl get ingress -n default
grep -A 6 "^ingress:" charts/nginx/values.yaml

ingress.enabled must be true and ingress.className must match your controller (nginx for ingress-nginx). The ConfigMap change is unaffected by this. You can confirm the release itself is correct from inside the cluster:

kubectl run curltest --rm -it --restart=Never --image=curlimages/curl -- \
  curl -s http://nginx.default.svc.cluster.local/

Further Reading


DevCloudLab

Built by DevCloudLab
Hands-on cloud-native courses: Kubernetes, GitOps, CI/CD and the cloud.
Visit DevCloudLab.com →