Skip to main content

Helm Lab

Purpose

By the end of this lab, you will have deployed an Ignition gateway to a local Kubernetes cluster using the official Inductive Automation Helm chart, accessed it via port-forward, verified that the StatefulSet's PersistentVolumeClaim preserves data across pod deletion, and customized the deployment with your own values.yaml.

Before Getting Started

Prerequisites:

Lab Overview

Here is the path you will follow:

  1. Verify the cluster is running and the Ignition Helm repo is available.
  2. Install the chart with one command. Helm will create a StatefulSet, Service, PVC, ConfigMap, and Secret all at once.
  3. Watch the pod start. Ignition takes 60-180 seconds to come up, so you will see Kubernetes wait it out before marking the pod Ready.
  4. Access the gateway in your browser via kubectl port-forward, a development-friendly way to reach the gateway without setting up an Ingress.
  5. Test persistence. Drop a marker file inside the pod, delete the pod, and watch the StatefulSet rebuild it with the same data still attached. This is the moment that proves StatefulSets are the right choice for Ignition.
  6. Customize the deployment with a values.yaml and helm upgrade.
  7. Clean up the namespace and PVCs.

If you skim the steps and see something that looks unfamiliar, the Kubernetes Concepts guide explains the underlying pieces.


Step 1: Verify the Cluster

Confirm a local cluster is reachable and the Ignition Helm repo is available.

kubectl get nodes
helm search repo ignition
bash — ~
$ kubectl get nodes
NAME                     STATUS   ROLES           AGE   VERSION
helm-lab-control-plane   Ready    control-plane   21s   v1.35.0

$ helm search repo ignition
NAME                        	CHART VERSION	APP VERSION	DESCRIPTION
ignition/ignition           	0.2.3        	8.3.6      	Ignition by Inductive Automation

If kubectl get nodes does not return a Ready node, revisit the Local Kubernetes Setup guide before continuing. The chart name shown above (ignition/ignition) is what you will install in the next step.

Multiple repo rows are fine

If you have several Ignition chart repos registered (for example, ignition/, ia/, ia-charts/, and inductiveautomation/), helm search repo ignition will show one row per alias. The chart is the same in every case; pick any alias and stay consistent.

Any local cluster works

Docker Desktop's built-in Kubernetes, kind, minikube, and k3d all behave the same for this lab. Use whichever one you configured during workstation setup.


Step 2: Install the Chart with Defaults

A Helm chart is a packaged set of Kubernetes resources with sensible defaults you can override. Running helm install against the Ignition chart will create everything the gateway needs (StatefulSet, Service, PVC, ConfigMap, and Secret) from one command, all scoped to a Kubernetes namespace.

Create a dedicated namespace and install the chart. The chart requires you to accept the Ignition EULA via a values flag.

kubectl create namespace ignition
helm install my-gw ignition/ignition \
--namespace ignition \
--set commissioning.acceptIgnitionEULA=true
bash — ~
$ kubectl create namespace ignition
namespace/ignition created

$ helm install my-gw ignition/ignition --namespace ignition --set commissioning.acceptIgnitionEULA=true
NAME: my-gw
LAST DEPLOYED: Sat May 16 15:21:27 2026
NAMESPACE: ignition
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
NOTES:
🎉 Thank you for installing Ignition 8.3.6 by Inductive Automation!

🚀 Ignition Gateway is reachable at:
  - http://my-gw-ignition.localtest.me

🔐 The initial Ignition Gateway `admin` password can be retrieved with:

  kubectl get secret -n ignition my-gw-ignition-gateway-admin-password --template='{{ printf "%s\n" (index .data "gateway-admin-password" | base64decode) }}'

To learn more about Ignition, please visit our website at https://inductiveautomation.com

One command produced a StatefulSet, Service, headless DNS, PVC, ConfigMap, and a Secret with the generated admin password. Inspect what landed in the namespace:

kubectl get statefulset,svc,pvc -n ignition
bash — ~
$ kubectl get statefulset,svc,pvc -n ignition
NAME                                      READY   AGE
statefulset.apps/my-gw-ignition-gateway   0/1     3s

NAME                     TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                      AGE
service/my-gw-ignition   ClusterIP   None         <none>        8060/TCP,8088/TCP,8043/TCP   3s

NAME                                                  STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/data-my-gw-ignition-gateway-0   Bound    pvc-635b8a56-19bb-472c-b293-6c0b0bde90a2   3Gi        RWO            standard       <unset>                 4s

The service is my-gw-ignition (headless, ClusterIP None), and the StatefulSet creates pod my-gw-ignition-gateway-0 with PVC data-my-gw-ignition-gateway-0. These names follow the <release>-<chart> convention from Helm.

Retrieve the admin password

Save the password somewhere safe right now. The chart generates a random one on first install and stores it in the Secret named in the post-install notes. If you uninstall the release without deleting that Secret, the password is retained on reinstall.


Step 3: Watch the Pod Start

Stream pod status. Ignition takes 60-180 seconds to start: JVM launch, module loading, and the internal database init all happen before the readiness probe passes.

kubectl get pods -n ignition -w
bash — ~
$ kubectl get pods -n ignition -w
NAME                       READY   STATUS     RESTARTS   AGE
my-gw-ignition-gateway-0   0/1     Init:0/1   0          9s
my-gw-ignition-gateway-0   0/1     Init:0/1   0          29s
my-gw-ignition-gateway-0   0/1     Init:0/1   0          60s
my-gw-ignition-gateway-0   0/1     Init:0/1   0          91s
my-gw-ignition-gateway-0   0/1     Running    0          101s
my-gw-ignition-gateway-0   1/1     Running    0          115s

Press Ctrl+C once you see 1/1 Running. The Init:0/1 phase is the preconfigure init container the chart uses to prepare the data volume before the main gateway container starts.

Startup takes a while

The chart's startupProbe gives the gateway up to 5 minutes to come up. A standard JVM start plus module loading plus database init takes 60-180 seconds. Be patient: readiness flips to 1/1 only when the gateway answers HTTP.


Step 4: Access the Gateway via Port-Forward

The gateway pod has its own internal IP, but it is not reachable from your browser by default. kubectl port-forward opens a temporary tunnel from a port on your workstation into the cluster, so you can hit the gateway from your browser as if it were running locally. It is the simplest way to access a workload during development; production deployments use an Ingress or a LoadBalancer Service instead.

Check the service name, then port-forward 8088 to your workstation.

kubectl get svc -n ignition
kubectl port-forward -n ignition svc/my-gw-ignition 8088:8088
bash — ~
$ kubectl get svc -n ignition
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                      AGE
my-gw-ignition   ClusterIP   None         <none>        8060/TCP,8088/TCP,8043/TCP   2m

$ kubectl port-forward -n ignition svc/my-gw-ignition 8088:8088
Forwarding from 127.0.0.1:8088 -> 8088
Forwarding from [::1]:8088 -> 8088

Open http://localhost:8088 in your browser. The Ignition gateway home page loads. Log in with username admin and the password you retrieved in Step 2. This is the same gateway you'd see in the Docker Lab, just running inside Kubernetes.

Port-forward terminates with the terminal

Port-forward is a development-time convenience. Press Ctrl+C in the terminal running port-forward to stop it; reopen the connection any time. In production deployments you would use an Ingress or a LoadBalancer service type instead.


Step 5: Test Persistence

This is the critical step. You will modify state inside the pod, delete the pod, and confirm the StatefulSet brings it back with the same data attached. This is what makes a StatefulSet correct for Ignition and a Deployment wrong.

1. Confirm the data volume mount

In a separate terminal (leave the port-forward running):

kubectl exec -n ignition my-gw-ignition-gateway-0 -- df -h /usr/local/bin/ignition/data
bash — ~
$ kubectl exec -n ignition my-gw-ignition-gateway-0 -- df -h /usr/local/bin/ignition/data
Defaulted container "gateway" out of: gateway, preconfigure (init)
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       453G   35G  395G   9% /usr/local/bin/ignition/data

The chart mounts the PVC at /usr/local/bin/ignition/data. Anything Ignition writes there - projects, the internal database, tag history, gateway configuration - lives on the PVC, not in the pod's writable layer.

2. Create state inside the pod

For the lab, you can either:

  • Open the gateway in your browser, launch the Designer, and create a new project named helm_lab with a Perspective view named persistence-test, or
  • Drop a marker file directly into the data volume to prove the mechanic without the Designer round-trip.

The marker-file approach is faster and proves the same thing:

kubectl exec -n ignition my-gw-ignition-gateway-0 -- \
sh -c 'echo "persistence-test-$(date +%s)" > /usr/local/bin/ignition/data/persistence-test.txt && cat /usr/local/bin/ignition/data/persistence-test.txt'
bash — ~
$ kubectl exec -n ignition my-gw-ignition-gateway-0 -- sh -c 'echo "persistence-test-$(date +%s)" > /usr/local/bin/ignition/data/persistence-test.txt && cat /usr/local/bin/ignition/data/persistence-test.txt'
Defaulted container "gateway" out of: gateway, preconfigure (init)
persistence-test-1778981029

Note the timestamp suffix. You will be looking for that same value after the pod is recreated.

3. Delete the pod

kubectl delete pod -n ignition my-gw-ignition-gateway-0
kubectl get pods -n ignition -w
bash — ~
$ kubectl delete pod -n ignition my-gw-ignition-gateway-0
pod "my-gw-ignition-gateway-0" deleted from ignition namespace

$ kubectl get pods -n ignition -w
NAME                       READY   STATUS    RESTARTS   AGE
my-gw-ignition-gateway-0   0/1     Running   0          5s
my-gw-ignition-gateway-0   1/1     Running   0          115s

The StatefulSet controller immediately starts a replacement pod with the same name (my-gw-ignition-gateway-0) and reattaches the same PVC (data-my-gw-ignition-gateway-0). Wait for it to be 1/1 Running, then press Ctrl+C.

4. Restart port-forward

The original port-forward session died when the pod went away. Start a new one:

kubectl port-forward -n ignition svc/my-gw-ignition 8088:8088

5. Verify the data survived

kubectl exec -n ignition my-gw-ignition-gateway-0 -- cat /usr/local/bin/ignition/data/persistence-test.txt
bash — ~
$ kubectl exec -n ignition my-gw-ignition-gateway-0 -- cat /usr/local/bin/ignition/data/persistence-test.txt
Defaulted container "gateway" out of: gateway, preconfigure (init)
persistence-test-1778981029

Same timestamp value. The replacement pod inherited the PVC, and Ignition booted from the existing data directory. If you used the Designer path instead, re-open the gateway and the helm_lab project's persistence-test view is still there.

Why StatefulSet, not Deployment

A Deployment treats its pods as interchangeable and will give a replacement pod a new name and a new ephemeral volume. Ignition's data directory contains the internal database, tag history, and project files, so losing it loses the gateway. A StatefulSet pins identity (-0, -1, ...) and pairs each pod with its own PVC across reschedules. This is the only correct primitive for a stateful gateway.


Step 6: Customize with values.yaml

Production deployments belong in a versioned values.yaml file, not on the --set command line. Create one alongside your project:

values.yaml
commissioning:
acceptIgnitionEULA: true

gateway:
resources:
requests:
cpu: "2"
memory: 4Gi
limits:
cpu: "4"
memory: 8Gi

Then upgrade the release:

helm upgrade my-gw ignition/ignition --namespace ignition --values values.yaml
bash — ~
$ helm upgrade my-gw ignition/ignition --namespace ignition --values values.yaml
Release "my-gw" has been upgraded. Happy Helming!
NAME: my-gw
LAST DEPLOYED: Sat May 16 17:43:33 2026
NAMESPACE: ignition
STATUS: deployed
REVISION: 2
DESCRIPTION: Upgrade complete
NOTES:
🎉 Thank you for installing Ignition 8.3.6 by Inductive Automation!

🚀 Ignition Gateway is reachable at:
  - http://my-gw-ignition.localtest.me

To learn more about Ignition, please visit our website at https://inductiveautomation.com

The StatefulSet rolls the pod asynchronously, so checking the pod spec immediately after helm upgrade returns the OLD values until the replacement pod takes over. Wait for the rollout to finish before inspecting the new resource limits:

kubectl rollout status statefulset/my-gw-ignition-gateway -n ignition
bash — ~
$ kubectl rollout status statefulset/my-gw-ignition-gateway -n ignition
Waiting for 1 pods to be ready...
partitioned roll out complete: 1 new pods have been updated...

This blocks until the new pod is ready (typically 30-60 seconds on a warm cluster). Now confirm the new resource limits applied to the pod:

kubectl get pod -n ignition my-gw-ignition-gateway-0 \
-o jsonpath='{.spec.containers[0].resources}'
bash — ~
$ kubectl get pod -n ignition my-gw-ignition-gateway-0 -o jsonpath='{.spec.containers[0].resources}'
{"limits":{"cpu":"4","memory":"8Gi"},"requests":{"cpu":"2","memory":"4Gi"}}

The StatefulSet rolled the pod with the new pod spec, and the PVC reattached. Persistence and customization work together.

You cannot resize the data volume in place

The gateway.dataVolumeStorageSize value is fixed at install time. Trying to change it via helm upgrade produces a StatefulSet ... is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', ... error. Pick a sane size on first install. To grow storage later you either use a CSI driver that supports volume expansion (and patch the PVC directly) or reinstall.

Value paths come from the chart, not from guesswork

Always check helm show values ignition/ignition for the real value keys. The exact paths (gateway.resources, commissioning.acceptIgnitionEULA, gateway.dataVolumeStorageSize, and so on) are documented in the chart and at charts.ia.io. Helm silently accepts unknown keys, so a typo in values.yaml will not error - it will just have no effect.


Step 7: Clean Up

Uninstall the release, then reclaim the PVC and the namespace.

helm uninstall my-gw --namespace ignition
kubectl get pvc -n ignition
kubectl delete pvc --all --namespace ignition
kubectl delete namespace ignition
bash — ~
$ helm uninstall my-gw --namespace ignition
release "my-gw" uninstalled

$ kubectl get pvc -n ignition
NAME                            STATUS   CAPACITY   STORAGECLASS   AGE
data-my-gw-ignition-gateway-0   Bound    3Gi        standard       3m41s

$ kubectl delete pvc --all --namespace ignition
persistentvolumeclaim "data-my-gw-ignition-gateway-0" deleted from ignition namespace

$ kubectl delete namespace ignition
namespace "ignition" deleted
helm uninstall does not delete the PVC

By design. The PVC outlives the release so a reinstall reattaches the same data. If you do not delete the PVC explicitly, your storage stays allocated and the next helm install my-gw ignition/ignition will reuse the existing data directory (admin password and all). This is great when you want it and a footgun when you don't.


What You Built

You deployed Ignition to Kubernetes with one Helm command, watched the StatefulSet's PVC persist data across a pod deletion, and overrode the chart defaults with your own values.yaml. The same workflow scales from a laptop kind cluster to a production EKS or GKE cluster - only the values change.

Where to go next:

  • charts.ia.io for the full chart reference and every documented value
  • Phase 3 content (Local Platform Repo, GitOps with Argo CD, multi-environment promotion) for how this same release moves from one-off helm install into a managed delivery pipeline