Skip to main content

Deploy Nginx with Helm

Updated Aug 19, 2022 ·

Overview

This lab shows how to deploy the Nginx web server on a Kubernetes cluster using Helm.

Pre-requisites

Project Directory

info

Make sure to go through the pre-requisites before proceeding to the next steps

If you followed the steps in setting up Flux, your project directory should have the following files:

$ tree
.
├── README.md
└── clusters
└── dev
└── flux-system
├── gotk-components.yaml
├── gotk-sync.yaml
├── kustomization.yaml

3 directories, 6 files

To organize the manifests, we will create folders for each lab, along with their respective kustomization.yaml file.

For this lab, create the helm-nginx directory:

mkdir clusters/dev/helm-nginx 

Depoying Nginx

Prepare the Local Helm Chart

Create a charts directory at the root of the project directory:

mkdir charts
cd charts

Next, scaffold a new chart, for example, nginx:

helm create nginx

This creates a simple web server chart with settings we can customize before deploying.

Open charts/nginx/values.yaml and update:

ingress:
enabled: true
className: nginx
hosts:
- host: ""

Create the Helm Release

Create a HelmRelease YAML file in directory containing the Kubernetes manifests:

## clusters/dev/helm-nginx/nginx-helm-release.yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: nginx
namespace: default
spec:
interval: 1m
chart:
spec:
interval: 1m
chart: ./charts/nginx
sourceRef:
kind: GitRepository
name: flux-system
namespace: flux-system

Note:

  • Set the release name, chart path, and intervals
  • Reference the local Git repository as the chart source

Add the Files in kustomization.yaml

This ensures that Flux also watches for the podinfo manifests.

## clusters/dev/helm-nginx/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- nginx-helm-release.yaml

Commit and Push Changes

To follow GitOps, commit the changes and create a merge request.

## Make sure main branch is updated 
git checkout main
git pull

## Create a new branch for the change
git checkout -b nginx-helm

## Stage, commit, and push to the Git repository
git add -A
git commit -m "Add nginx Helm chart and release config"
git push --set-upstream origin nginx-helm

After pushing, merge the changes to the main branch. Since we are using Gitlab in this setup, login to the Gitlab UI and go to the repository. We should see a Create merge request at the top. Click it.

Provide a title and description to the merge request and click the Create merge request button at the bottom.

In a typical team setting, developers create merge requests which are then reviewed and approved by other team members. For this lab, you can go ahead and click Approve and Merge directly.

Trigger Flux to Reconcile

You can wait for Flux to detect changes, or you can also trigger it manually by running:

flux reconcile kustomization flux-system --with-source

Next, check the Helm release:

kubectl get helmrelease

You might initially get this if you immediately run the kubectl command:

$ kubectl get helmrelease
NAME AGE READY STATUS
nginx 13s Unknown Running 'install' action with timeout of 5m0s

After a few seconds, it should change to Helm install succeeded:

$ kubectl get helmrelease
NAME AGE READY STATUS
nginx 72s True Helm install succeeded for release default/nginx.v1 with chart nginx@0.1.0

Finally, check if pods, services, and ingress objects are created

flux reconcile kustomization flux-system --with-source

Different Status

You may see two different status:

  • Helm install succeeded for release default/nginx.v1 with chart nginx@0.1.0

    • This message comes from Helm via Flux
    • It means release is successfully installed.
    • Kubernetes accepted the resulting resources.
    • This happens right after Helm completes the install step.
  • Release reconciliation succeeded

    • This is a higher-level Flux message.
    • Flux reconciled the state (HelmRelease → Kubernetes resources).
    • It verified the resources are in the desired state
    • It may appear a few seconds after install, once everything settles and Flux re-evaluates the state.

You should see an NGINX pod running and the service created:

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-7fc44c46f6-7fr7q 1/1 Running 0 8m4s
podinfo-5d9db9b467-9h26p 1/1 Running 0 5h46m
podinfo-5d9db9b467-mldhr 1/1 Running 0 5h46m

$ k get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 21h
nginx ClusterIP 10.96.69.189 <none> 80/TCP 9m47s
podinfo ClusterIP 10.96.103.28 <none> 9898/TCP,9999/TCP 5h48m

You can also access the welcome page when visiting the cluster IP.

Port Forwarding

Since the nginx service is a ClusterIP, it’s only accessible within the Kubernetes cluster. To access the Nginx welcome page in your browser on WSL, you need to expose it outside the cluster.

Here are three common ways to do that:

  1. Port Forward to Your Localhost (Simplest)

    Run:

    kubectl port-forward svc/nginx 8080:80

    Then open your browser and go to:

    http://localhost:8080

    You should see the Nginx welcome page.

    info

    For simplificity, I just used port forwarding.

  2. Change Service Type to NodePort

If you want it accessible on your VM's IP:

  • Edit the service:

    kubectl edit svc nginx
  • Change this:

    type: ClusterIP

    To:

    type: NodePort
  • Kubernetes will assign a port between 30000–32767, e.g.:

    kubectl get svc nginx

    Output:

    NAME    TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
    nginx NodePort 10.96.69.189 <none> 80:32000/TCP 10m
  • Then open your browser to:

    http://<your-host-ip>:32000

    If using WSL2, find your host IP via:

    ip route | grep default

    Or use localhost if you have port forwarding set up via Windows.

  1. Expose with Ingress (Advanced)

If you're using Ingress + IngressController (like NGINX Ingress), configure an Ingress resource pointing to the nginx service. But this requires an Ingress controller running in your cluster.

Customize Helm Charts

Flux CD allows us to override the chart's values through the Helm release custom resource. This allows us to customize what gets deployed without changing the chart directly.

For this section, we'll create a custom HTML file which will be used as the Nginx's welcome page. The contents of this file will be read from a defined Helm value, which is then supplied through the Helm release.

Create a ConfigMap Template

First, switch to the main branch and pull updates from the remote repository.

git checkout main
git pull

Create a new branch for the new changes:

git checkout -b "custom-welcome-page"  

Go to the ./charts/nginx/templates/ and create a configmap.yaml:

## ./charts/nginx/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-index-html
data:
index.html: |
{{ .Values.indexHtml | nindent 4 }}

Note:

  • Use Helm values to fill in the ConfigMap contents
  • Make sure the ConfigMap name uses the Helm release name

Update the Deployment Template

We now need to mount the ConfigMap into the Nginx container.

  • Add a volumeMount pointing to the default HTML path
  • Add a volume that links to the configmap

The deployment.yaml is in the same directory as the configmap. MOdify the file and update the volumeMount and volume.

    containers:
- name: {{ .Chart.Name }}
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: {{ .Release.Name }}-index-html

Now the container knows where to find the custom HTML page.

Bump the Chart Version

FluxCD watches for new chart versions, not file changes. This means that updates will only be applied when Helm release detects a change in the chart. To trigger a change, we can upgrade the chart version.

MOdify the ./charts/nginx/Chart.yaml and increase the chart version (e.g., from 0.1.0 to 0.2.0):

version: 0.1.1 

This version bump triggers Flux to notice and apply the new files.

Add Values to HelmRelease

Finally, define the actual HTML content in the Flux HelmRelease file.

  • Add a values: section
  • Use a multi-line string for HTML
  • Use | to start the block, then indent the HTML

This HTML will be loaded into the configmap and shown by Nginx.

## clusters/dev/helm-nginx/nginx-helm-release.yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: nginx
namespace: default
spec:
interval: 1m
.... ## Some of the contents truncated/omitted
values:
indexHtml: |-
<!doctype html>
<html>
<head>
<title>My Custom Page</title>
</head>
<body>
<h1>Welcome to my custom Nginx page!</h1>
</body>
</html>

Now we save everything and push to Git.

git add -A
git commit -m "Changes the default index.html page of Nginx"
git push --set-upstream origin custom-welcome-page

Just like in the previous sections, go to Gitlab and create a merge request. Another developer typically reviews and approves the merge request, but you can simply approve and merge it.

Once merged to main, Flux will see the changes and deploy.

Optional: Trigger Reconcile

You can force Flux to pull and apply the latest changes.

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

Verify the Change

Once everything is applied, go back to the webpage where the Nginx welcome page was displayed and refresh the page.

If you're using port forwarding

If the page did not displayed the updated Nginx welcome page, you can kill the process and run port forwarding again.

ps -ef | grep 8080 
sudo kill -9 <enter-pid>

Troubleshooting

If the flux reconcile command get stuck, or if the page did not load, you can try checking the logs of the controller.

kubectl logs -n flux-system deployment/kustomize-controller

For other errors, please see General Troubleshooting.