Components like ingress-nginx, Prometheus, and cert-manager arrive as Helm charts, and so does most of the off-the-shelf software you will want on a cluster. Helm is how you install that class of thing. It takes a packaged application, a chart, and installs it as one named release you can upgrade and roll back as a unit. The chart ships with defaults; a values file is where you bend those defaults to a Syself Autopilot cluster, which is the part a generic Helm tutorial cannot do for you. ## Install with values ```console $ helm repo add example https://charts.example.com $ helm install my-app example/some-app \ --namespace team-web --create-namespace \ --version 3.2.1 \ --values values.yaml ``` The values file overrides the chart's defaults. Two of those overrides matter on almost every chart you install here: ```yaml title="values.yaml" # Bind persistent volumes to a storage class that actually exists on the cluster persistence: storageClass: local-nvme # local NVMe; or a Hetzner Cloud volume class # If the chart creates its own LoadBalancer Service, tell Hetzner where to place it service: annotations: load-balancer.hetzner.cloud/location: fsn1 ``` Both come back below, because getting them wrong is the usual reason a chart that works elsewhere stalls on Syself Autopilot. ## Pin versions Pin the **chart version** with `--version`, and pin the **application image tag** through values. Those are two separate things: a chart version selects the templates and defaults, the image tag selects the code that actually runs. Leave either unpinned and the next `helm install` or `helm upgrade` can pull something newer than you tested, shifting behavior without warning. Pinning both makes a release reproducible, which is what you want when the same values file deploys to more than one cluster. ## Upgrade and roll back ```console $ helm upgrade my-app example/some-app --namespace team-web --version 3.3.0 --values values.yaml $ helm history my-app --namespace team-web $ helm rollback my-app 1 --namespace team-web # back to revision 1 if the upgrade misbehaves ``` Helm records every release as a numbered revision, so a bad upgrade is one `rollback` away. That safety net is the real reason to install even a one-manifest app as a release instead of a raw `kubectl apply`: the history and the reversible upgrade come for free. ## Helm on this platform Most charts are written to land on a cluster with a single default StorageClass, where a PersistentVolumeClaim that names no class just works. Syself Autopilot hands you one, `standard`, backed by Hetzner Cloud volumes. It is still worth setting the class per application, because the right storage depends on the workload: local NVMe through `local-nvme` is fast but pinned to one node, while a Hetzner Cloud volume class is network-attached and follows the pod when a node is replaced. That choice is yours to make, per application: see [running a StatefulSet with storage](/docs/hetzner/apalla/workloads/stateful/run-a-statefulset-with-storage) for which fits what. The practical consequence: a chart whose PVC has no `storageClassName` set, and no `persistence.storageClass` value passed, lands on `standard`. Its volume stays `Pending` until a pod schedules, because `standard` binds on first consumer, then binds where that pod runs. Setting `persistence.storageClass` (or whatever key the chart exposes) is what puts it somewhere else. When pods hang on a fresh install, a `kubectl describe pvc` in the namespace usually names the reason directly. The load balancer annotation is the same idea for networking. A chart that provisions its own `Service` of type `LoadBalancer` gets a Hetzner load balancer, and `load-balancer.hetzner.cloud/location` decides which location it lives in. Many charts already expose a `service.annotations` map for exactly this; pass it there rather than editing the rendered Service after the fact. For anything you deploy more than once, stop running `helm install` from a laptop and let [Argo CD](/docs/hetzner/apalla/workloads/delivery/deploy-apps-from-git-argo-cd) render the chart from Git: an upgrade then becomes a values change in a pull request.