Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Helm Ubuntu
GUIDEInstall Guides

How to Install Helm on Ubuntu 24.04 VPS: The Kubernetes Package Manager

29 min read

How to Install Helm on Ubuntu 24.04 VPS: The Kubernetes Package Manager

Helm is the de-facto package manager for Kubernetes. Where kubectl apply forces you to hand-assemble dozens of YAML manifests, Helm bundles them into a single versioned artifact called a chart -- one command to install, one command to upgrade, one command to roll back. If you run a self-hosted Kubernetes cluster on a Ubuntu 24.04 VPS (typically with k3s or kubeadm), Helm is the tool you reach for every single day.

This guide walks you through installing Helm on Ubuntu 24.04, connecting it to your cluster, adding the most useful public chart repositories (Bitnami and ingress-nginx), installing your first production workload, writing your own values.yaml overrides, and finally authoring a chart of your own. By the end, you will understand how to upgrade, roll back, template-debug, and compose multi-chart releases with Helmfile or umbrella charts.

Need a cluster to install Helm against? The CloudCore Starter plan is the sweet spot for a single-node k3s + Helm lab. Pair it with our k3s install guide and you will have a working cluster in under ten minutes.

Table of Contents

  • What Is Helm?
  • Why Self-Host Kubernetes + Helm Instead of a Managed Service?
  • Prerequisites
  • Step 1: Update System Packages
  • Step 2: Install Helm via the APT Repository
  • Step 3: Alternative Install via the Official Script
  • Step 4: Verify Helm and Cluster Connectivity
  • Step 5: Add Chart Repositories (Bitnami + ingress-nginx)
  • Step 6: Install Your First Chart
  • Step 7: Customize with values.yaml
  • Step 8: Upgrade and Roll Back Releases
  • Step 9: Inspect Rendered Templates
  • Step 10: Create Your Own Chart
  • Step 11: Helmfile and Umbrella Charts
  • Troubleshooting
  • FAQ
  • Next Steps
  • What Is Helm?

    Helm is an open-source package manager for Kubernetes maintained by the CNCF. It solves a problem every Kubernetes user hits within their first week: a real-world application is not one YAML file, it is 15 -- Deployments, Services, ConfigMaps, Secrets, Ingresses, ServiceAccounts, RBAC rules, HorizontalPodAutoscalers, NetworkPolicies, PersistentVolumeClaims, and more. Shipping these as loose files is unmanageable at any non-trivial scale.

    Helm introduces three core concepts. A chart is a directory (or tarball) containing templated Kubernetes manifests plus metadata describing the application. A release is a specific installation of a chart into a cluster, tracked by Helm with a name, revision number, and stored history. A repository is an HTTP-accessible index of charts, typically served from GitHub Pages, OCI registries, or a dedicated chart museum.

    With Helm you run helm install my-postgres bitnami/postgresql and Helm applies the full set of manifests atomically. Change one value, run helm upgrade, and Helm diffs the new desired state against the previous release, applies only the delta, and bumps the revision. Something broke? helm rollback restores the prior manifest set in seconds. This is the same ergonomic model apt brings to Debian/Ubuntu, but for distributed systems.

    The public chart ecosystem is enormous. Bitnami (now part of VMware/Broadcom) maintains hardened charts for PostgreSQL, MySQL, MongoDB, Redis, RabbitMQ, Kafka, Elasticsearch, WordPress, Nextcloud, Keycloak, and 100+ more. ingress-nginx provides the canonical ingress controller chart. cert-manager, Prometheus, Grafana, Loki, ArgoCD, Jaeger, Vault, MinIO, Traefik, and nearly every major cloud-native project ships an official chart. You very rarely need to write manifests from scratch.

    Why Self-Host Kubernetes + Helm Instead of a Managed Service?

    Managed Kubernetes (EKS, GKE, AKS, DigitalOcean DOKS, Linode LKE) is convenient, but it comes at a cost that small and mid-size teams frequently underestimate. Here is the honest trade-off:

    • Flat, predictable pricing. A managed control plane alone is typically USD 70-150/month before you pay for a single worker node, load balancer, or gigabyte of egress. A self-hosted k3s cluster on a CloudCore Starter VPS runs the full control plane + worker on one box for a fraction of that, with unmetered bandwidth included.
    • No per-cluster fees, no per-pod metering. Scale to 200 pods or 2 pods, you pay the same EUR amount every month.
    • No vendor lock-in. Everything -- the control plane, the container runtime, the CNI, the CSI, Helm itself -- is open source. You can migrate between providers by re-running your Helmfile against the new cluster.
    • Root access to every layer. Need to tweak kubelet flags, swap out containerd for CRI-O, add a custom CNI plugin, or debug kernel-level networking issues? On managed Kubernetes you file a support ticket. On your own VPS you ssh in and fix it.
    • Data residency and compliance. You choose the datacenter. You control the disks. You decide what logs leave the machine. For GDPR, HIPAA-adjacent, or financial workloads, this is not optional.
    • Learning compounds. The skills you build operating a self-hosted cluster transfer directly to any Kubernetes job, whereas cloud-provider-specific knowledge (EKS add-ons, GKE Autopilot quirks) is partially disposable.
    The honest counterpoint: managed Kubernetes removes the operational burden of etcd backups, control-plane upgrades, and high-availability concerns. For most teams running one or two clusters, the CNCF-certified k3s distribution sidesteps nearly all of that complexity by bundling everything into a single Go binary with embedded SQLite or etcd.

    Cost Comparison: Self-Hosted vs. Managed Kubernetes

    Line itemManaged Kubernetes (typical)Self-Hosted k3s + Helm (CloudCore Starter)
    Control plane fee~USD 75/monthIncluded (runs on the same VPS)
    Worker node (4 vCPU / 8 GB)~USD 40/monthIncluded
    Load balancer~USD 15/month eachIncluded (ServiceLB or MetalLB)
    Bandwidth (1 TB/mo)USD 20-90Unmetered
    Helm-installed chartsUnlimitedUnlimited
    Typical monthly totalUSD 150+EUR 7.99
    For solo developers, staging environments, CI runners, and most SMB production workloads, self-hosting pays back in the first week.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access.
    • SSH access to the server.
    • A working Kubernetes cluster reachable from the VPS where you install Helm. If you do not have one yet, follow How to Install k3s on Ubuntu 24.04 first -- you can install Helm on the same node as k3s.
    • kubectl configured with a valid ~/.kube/config pointing at your cluster. Verify with kubectl get nodes.
    • At least 2 GB of free RAM on the cluster nodes to install non-trivial charts like ingress-nginx or PostgreSQL.
    Recommended Plan: CloudCore Starter
    >
    For a single-node k3s + Helm learning environment or a small production cluster, the CloudCore Starter plan is ideal:
    >
    - 4 vCPU cores
    - 8 GB RAM
    - 75 GB NVMe SSD
    - Unmetered bandwidth
    - From EUR 7.99/month
    >
    This is enough headroom to run k3s, Helm, ingress-nginx, cert-manager, and a handful of application charts simultaneously without swapping. For multi-node HA clusters, upgrade to CloudCore Professional and add worker nodes as you grow.

    Connect to your server:

    bash
    ssh root@your-server-ip

    Step 1: Update System Packages

    Refresh the package index and apply any pending security updates so the APT repository metadata you add later resolves cleanly.

    bash
    sudo apt update && sudo apt upgrade -y

    Expected output (abbreviated):

    text
    Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
    Hit:2 http://archive.ubuntu.com/ubuntu noble-updates InRelease
    Reading package lists... Done
    Building dependency tree... Done
    0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

    Install the packages Helm's APT setup depends on:

    bash
    sudo apt install -y curl gnupg apt-transport-https ca-certificates

    If your kernel was upgraded, reboot before continuing:

    bash
    sudo reboot

    Step 2: Install Helm via the APT Repository

    The Helm project publishes a signed Debian/Ubuntu APT repository. This is the recommended approach for production because it integrates Helm into apt upgrade and ensures you receive signed upgrades.

    Import the Helm signing key:

    bash
    curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null

    Add the repository to APT:

    bash
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" \
      | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list

    Update the package index and install Helm:

    bash
    sudo apt update
    sudo apt install -y helm

    Expected output:

    text
    Reading package lists... Done
    Building dependency tree... Done
    The following NEW packages will be installed:
      helm
    0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
    Need to get 15.8 MB of archives.
    After this operation, 56.2 MB of additional disk space will be used.
    ...
    Setting up helm (3.15.4-1) ...

    From now on, sudo apt upgrade keeps Helm on the latest stable release alongside the rest of your system.

    Step 3: Alternative Install via the Official Script

    If you prefer a single-command install, or you are provisioning via cloud-init where adding APT keys is awkward, the official get-helm-3 script downloads the correct binary for your architecture and drops it at /usr/local/bin/helm.

    bash
    curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
    chmod 700 get_helm.sh
    ./get_helm.sh

    Expected output:

    text
    Downloading https://get.helm.sh/helm-v3.15.4-linux-amd64.tar.gz
    Verifying checksum... Done.
    Preparing to install helm into /usr/local/bin
    helm installed into /usr/local/bin/helm

    To pin a specific version, pass --version:

    bash
    ./get_helm.sh --version v3.14.4

    The script approach is faster and scriptable, but it does not add a package source, so apt upgrade will not update Helm. You are responsible for re-running the script to upgrade. Pick APT for long-lived servers, script for ephemeral CI runners or cloud-init.

    Step 4: Verify Helm and Cluster Connectivity

    Confirm Helm is on your $PATH and can talk to your cluster.

    Check the installed version:

    bash
    helm version

    Expected output:

    text
    version.BuildInfo{Version:"v3.15.4", GitCommit:"fa9efb07d9d8debbb4306d72af76a383895aa8c4", GitTreeState:"clean", GoVersion:"go1.22.6"}

    Helm reads your ~/.kube/config the same way kubectl does. Verify the cluster is reachable:

    bash
    kubectl get nodes

    Expected output (for a single-node k3s install):

    text
    NAME      STATUS   ROLES                  AGE   VERSION
    cc-01     Ready    control-plane,master   3d    v1.30.3+k3s1

    Check that Helm sees no releases yet:

    bash
    helm list --all-namespaces

    Expected output:

    text
    NAME    NAMESPACE    REVISION    UPDATED    STATUS    CHART    APP VERSION

    An empty table is the correct result on a fresh cluster. You are ready to add repositories.

    Step 5: Add Chart Repositories (Bitnami + ingress-nginx)

    Helm 3 requires you to explicitly add any repository you want to install from. Two repos cover 80% of day-one use cases: Bitnami for databases, message queues, and common applications, and ingress-nginx for the ingress controller that exposes your services to the internet.

    Add the Bitnami repo:

    bash
    helm repo add bitnami https://charts.bitnami.com/bitnami

    Expected output:

    text
    "bitnami" has been added to your repositories

    Add the ingress-nginx repo:

    bash
    helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

    Expected output:

    text
    "ingress-nginx" has been added to your repositories

    Refresh the local index cache so Helm knows what charts and versions each repo offers:

    bash
    helm repo update

    Expected output:

    text
    Hang tight while we grab the latest from your chart repositories...
    ...Successfully got an update from the "ingress-nginx" chart repository
    ...Successfully got an update from the "bitnami" chart repository
    Update Complete. Happy Helming!

    List repositories:

    bash
    helm repo list

    Expected output:

    text
    NAME            URL
    bitnami         https://charts.bitnami.com/bitnami
    ingress-nginx   https://kubernetes.github.io/ingress-nginx

    Search for a chart by keyword:

    bash
    helm search repo postgresql

    Expected output (abbreviated):

    text
    NAME                            CHART VERSION   APP VERSION   DESCRIPTION
    bitnami/postgresql              15.5.20         16.3.0        PostgreSQL (Postgres) is an open source object-...
    bitnami/postgresql-ha           14.3.1          16.3.0        This PostgreSQL cluster solution includes the P...

    Other popular repos worth adding as you expand your stack:

    bash
    helm repo add jetstack https://charts.jetstack.io            # cert-manager
    helm repo add argo https://argoproj.github.io/argo-helm      # ArgoCD
    helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    helm repo add grafana https://grafana.github.io/helm-charts
    helm repo update

    Step 6: Install Your First Chart

    Install the ingress-nginx controller. Every cluster needs an ingress controller to route HTTP(S) traffic from the outside world to internal services.

    Create a dedicated namespace and install:

    bash
    kubectl create namespace ingress-nginx

    helm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx \ --set controller.service.type=LoadBalancer \ --set controller.publishService.enabled=true

    Expected output:

    text
    NAME: ingress-nginx
    LAST DEPLOYED: Thu Apr 16 10:00:00 2026
    NAMESPACE: ingress-nginx
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    NOTES:
    The ingress-nginx controller has been installed.
    It may take a few minutes for the LoadBalancer IP to be available.

    Break down the command:

    • ingress-nginx (first argument) -- the release name. You choose this. It must be unique per namespace.
    • ingress-nginx/ingress-nginx -- repo/chart. The first is the repo alias you added in Step 5, the second is the chart name inside that repo.
    • --namespace -- where to install. Helm will not create the namespace unless you pass --create-namespace.
    • --set key=value -- inline override of a default value. Use sparingly; prefer values.yaml for anything beyond a quick experiment.
    Watch the pods come up:

    bash
    kubectl get pods -n ingress-nginx -w

    Expected output:

    text
    NAME                                        READY   STATUS    RESTARTS   AGE
    ingress-nginx-controller-7f9b8c5d4f-k2xjp   1/1     Running   0          45s

    Confirm the release is tracked by Helm:

    bash
    helm list -n ingress-nginx

    Expected output:

    text
    NAME            NAMESPACE       REVISION   UPDATED                 STATUS     CHART                   APP VERSION
    ingress-nginx   ingress-nginx   1          2026-04-16 10:00:00     deployed   ingress-nginx-4.11.2    1.11.2

    You just deployed roughly a dozen Kubernetes objects (Deployment, Service, ConfigMap, ServiceAccount, ClusterRole, ClusterRoleBinding, Role, RoleBinding, ValidatingWebhookConfiguration, Job, IngressClass, and more) with a single command. That is the Helm value proposition in action.

    Step 7: Customize with values.yaml

    --set flags are fine for tweaking one or two values, but production configuration belongs in a values.yaml file you commit to Git. Every chart exposes its tunable parameters through values.

    See the full set of defaults a chart supports:

    bash
    helm show values bitnami/postgresql > postgresql-defaults.yaml
    wc -l postgresql-defaults.yaml

    Expected output:

    text
    1247 postgresql-defaults.yaml

    That is over 1,200 configuration knobs. You do not override all of them -- you create a small file that overrides only what you need. Create postgres-values.yaml:

    yaml
    # postgres-values.yaml
    auth:
      username: dmapp
      database: dmapp
      existingSecret: postgres-credentials  # created separately via kubectl

    primary: persistence: enabled: true size: 20Gi storageClass: local-path # k3s default resources: requests: cpu: 250m memory: 512Mi limits: cpu: 1000m memory: 2Gi

    metrics: enabled: true serviceMonitor: enabled: false # enable once Prometheus Operator is installed

    Create the credentials secret Helm will reference:

    bash
    kubectl create namespace databases
    kubectl -n databases create secret generic postgres-credentials \
      --from-literal=postgres-password="$(openssl rand -base64 24)" \
      --from-literal=password="$(openssl rand -base64 24)"

    Install the chart with your custom values:

    bash
    helm install postgres bitnami/postgresql \
      --namespace databases \
      --values postgres-values.yaml

    Expected output:

    text
    NAME: postgres
    LAST DEPLOYED: Thu Apr 16 10:05:00 2026
    NAMESPACE: databases
    STATUS: deployed
    REVISION: 1

    Verify:

    bash
    kubectl -n databases get pods,pvc,svc

    Expected output:

    text
    NAME                     READY   STATUS    RESTARTS   AGE
    pod/postgres-postgresql-0    1/1     Running   0          90s

    NAME STATUS VOLUME CAPACITY persistentvolumeclaim/data-postgres-postgresql-0 Bound pvc-... 20Gi

    NAME TYPE CLUSTER-IP PORT(S) service/postgres-postgresql ClusterIP 10.43.142.17 5432/TCP service/postgres-postgresql-hl ClusterIP None 5432/TCP

    Values Precedence

    Helm merges values from multiple sources in this order (lowest to highest precedence):

  • The chart's built-in values.yaml
  • Any parent chart values.yaml (for umbrella charts)
  • --values file.yaml (can be passed multiple times, later files win)
  • --set key=value flags on the command line
  • This means you can layer a base values.yaml, a per-environment values-production.yaml, and a last-minute --set image.tag=hotfix-42 all in one helm upgrade call.

    Step 8: Upgrade and Roll Back Releases

    The superpower Helm gives you over raw kubectl apply is versioned, reversible releases. Every helm upgrade produces a new revision; every revision is stored in the cluster; any revision can be restored in seconds.

    Change resources.limits.memory in postgres-values.yaml from 2Gi to 4Gi, then:

    bash
    helm upgrade postgres bitnami/postgresql \
      --namespace databases \
      --values postgres-values.yaml

    Expected output:

    text
    Release "postgres" has been upgraded. Happy Helming!
    NAME: postgres
    LAST DEPLOYED: Thu Apr 16 10:15:00 2026
    NAMESPACE: databases
    STATUS: deployed
    REVISION: 2

    View the revision history:

    bash
    helm history postgres -n databases

    Expected output:

    text
    REVISION    UPDATED                     STATUS      CHART                APP VERSION    DESCRIPTION
    1           2026-04-16 10:05:00 UTC     superseded  postgresql-15.5.20   16.3.0         Install complete
    2           2026-04-16 10:15:00 UTC     deployed    postgresql-15.5.20   16.3.0         Upgrade complete

    Something went wrong with revision 2? Roll back to revision 1:

    bash
    helm rollback postgres 1 -n databases

    Expected output:

    text
    Rollback was a success! Happy Helming!

    Helm creates revision 3 that matches the manifest state of revision 1. The history is append-only, so you always know what ran when.

    Useful Upgrade Flags

    • --install -- install if the release does not exist, upgrade otherwise. Perfect for CI pipelines: helm upgrade --install.
    • --atomic -- roll back automatically if the upgrade fails or times out. Essential for production.
    • --timeout 5m -- how long to wait for resources to become ready. Default is 5 minutes.
    • --wait -- block until all Deployments, StatefulSets, and Services reach ready state before marking the release as deployed.
    • --dry-run -- render the templates and show what would be sent to the API server, without actually applying.
    • --diff -- with the helm-diff plugin (helm plugin install https://github.com/databus23/helm-diff), shows a unified diff between the current release and the proposed upgrade.
    A production-grade upgrade command looks like this:

    bash
    helm upgrade --install postgres bitnami/postgresql \
      --namespace databases \
      --values postgres-values.yaml \
      --atomic \
      --wait \
      --timeout 10m

    Step 9: Inspect Rendered Templates

    When a chart misbehaves, the first diagnostic step is to see exactly what YAML Helm is generating. Use helm template to render templates locally without touching the cluster.

    Render an entire chart with your values:

    bash
    helm template postgres bitnami/postgresql \
      --values postgres-values.yaml \
      --namespace databases

    The output is the complete set of Kubernetes manifests Helm would apply. Pipe it into kubectl diff or into kubeval/kubeconform for schema validation.

    Render only one template file (useful when a chart has 30+ templates):

    bash
    helm template postgres bitnami/postgresql \
      --values postgres-values.yaml \
      --show-only templates/primary/statefulset.yaml

    See what an existing release looks like after rendering:

    bash
    helm get manifest postgres -n databases

    See the values a release was installed with:

    bash
    helm get values postgres -n databases

    See the full values tree (user overrides merged with defaults):

    bash
    helm get values postgres -n databases --all

    Lint a local chart directory for syntax and best-practice issues:

    bash
    helm lint ./my-chart

    These commands turn a Helm release from a black box into something fully inspectable.

    Step 10: Create Your Own Chart

    At some point you stop installing other people's charts and start packaging your own application. helm create scaffolds a working chart you can edit.

    bash
    helm create dm-web

    Expected output:

    text
    Creating dm-web

    Inspect the generated directory:

    bash
    tree dm-web

    Expected output:

    text
    dm-web
    ├── Chart.yaml
    ├── charts
    ├── templates
    │   ├── NOTES.txt
    │   ├── _helpers.tpl
    │   ├── deployment.yaml
    │   ├── hpa.yaml
    │   ├── ingress.yaml
    │   ├── service.yaml
    │   ├── serviceaccount.yaml
    │   └── tests
    │       └── test-connection.yaml
    ├── values.yaml
    └── .helmignore

    Key files:

    • Chart.yaml -- metadata: name, version, appVersion, dependencies, maintainers, home URL.
    • values.yaml -- default configuration values users of your chart can override.
    • templates/ -- Go-templated Kubernetes manifests. Files starting with _ are partials/helpers, not standalone manifests.
    • templates/_helpers.tpl -- reusable template snippets for labels, selector labels, service account names.
    • templates/NOTES.txt -- post-install instructions printed to the user after helm install.
    • charts/ -- subchart dependencies (populated by helm dependency update).

    Customize Chart.yaml

    Edit dm-web/Chart.yaml to describe your application:

    yaml
    apiVersion: v2
    name: dm-web
    description: The DM marketing site and storefront
    type: application
    version: 0.1.0          # chart version (SemVer, bumps independently)
    appVersion: "1.0.0"     # app version you are packaging
    maintainers:
      - name: DM DevOps
        email: [email protected]
    home: https://vps-server.host
    icon: https://vps-server.host/logo.png

    Understand the Templating Language

    Open templates/deployment.yaml. Helm uses Go's text/template with custom functions from the Sprig library plus Helm-specific additions.

    Snippet:

    yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: {{ include "dm-web.fullname" . }}
      labels:
        {{- include "dm-web.labels" . | nindent 4 }}
    spec:
      replicas: {{ .Values.replicaCount }}
      selector:
        matchLabels:
          {{- include "dm-web.selectorLabels" . | nindent 6 }}
      template:
        metadata:
          labels:
            {{- include "dm-web.selectorLabels" . | nindent 8 }}
        spec:
          containers:
            - name: {{ .Chart.Name }}
              image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
              imagePullPolicy: {{ .Values.image.pullPolicy }}
              ports:
                - name: http
                  containerPort: {{ .Values.service.port }}
                  protocol: TCP

    Key constructs:

    • {{ .Values.xxx }} -- reference a value from values.yaml (or user overrides).
    • {{ .Chart.Name }}, {{ .Chart.AppVersion }} -- metadata from Chart.yaml.
    • {{ .Release.Name }}, {{ .Release.Namespace }} -- information about this specific installation.
    • {{ include "dm-web.fullname" . }} -- call a named template defined in _helpers.tpl.
    • {{- ... -}} -- the hyphens trim whitespace and the preceding/following newline.
    • | nindent 4 -- pipe the result through a function that indents every line by 4 spaces, with a leading newline.
    • | default .Chart.AppVersion -- fall back to .Chart.AppVersion if the value is empty.

    Install Your Chart

    bash
    helm install dm-web ./dm-web \
      --namespace apps --create-namespace \
      --set image.repository=ghcr.io/vps-server-host/dm-web \
      --set image.tag=v1.0.0

    Package and Publish

    Package the chart into a versioned tarball:

    bash
    helm package dm-web

    Expected output:

    text
    Successfully packaged chart and saved it to: /root/dm-web-0.1.0.tgz

    You can host the tarball on any HTTP server, GitHub Pages, S3, or push it to an OCI registry like ghcr.io or Harbor:

    bash
    helm push dm-web-0.1.0.tgz oci://ghcr.io/vps-server-host/charts

    Consumers then install with:

    bash
    helm install dm-web oci://ghcr.io/vps-server-host/charts/dm-web --version 0.1.0

    Step 11: Helmfile and Umbrella Charts

    A production cluster rarely runs one chart. It runs 10-30: ingress, cert-manager, monitoring, logging, databases, message queues, applications. You need a way to declare the set of releases as code. Two patterns dominate.

    Umbrella Charts

    An umbrella chart is a Helm chart whose only purpose is to depend on other charts. Create platform/Chart.yaml:

    yaml
    apiVersion: v2
    name: platform
    version: 0.1.0
    type: application
    dependencies:
      - name: ingress-nginx
        version: "4.11.2"
        repository: "https://kubernetes.github.io/ingress-nginx"
      - name: cert-manager
        version: "v1.15.3"
        repository: "https://charts.jetstack.io"
        condition: cert-manager.enabled
      - name: postgresql
        version: "15.5.20"
        repository: "https://charts.bitnami.com/bitnami"
        alias: primary-db

    Fetch the dependencies:

    bash
    helm dependency update ./platform

    This downloads each dependency tarball into platform/charts/. Install the whole stack as one release:

    bash
    helm install platform ./platform -n platform --create-namespace

    Override values per subchart in platform/values.yaml:

    yaml
    ingress-nginx:
      controller:
        service:
          type: LoadBalancer

    cert-manager: enabled: true installCRDs: true

    primary-db: # matches the alias above auth: database: platform primary: persistence: size: 50Gi

    Umbrella charts are Helm-native and require no extra tooling. The downside: they couple everything into one release, so a failure in any subchart can roll back the whole stack.

    Helmfile

    Helmfile solves the same problem by declaring releases in a separate YAML file outside Helm itself. Install:

    bash
    curl -L https://github.com/helmfile/helmfile/releases/download/v0.167.1/helmfile_0.167.1_linux_amd64.tar.gz \
      | sudo tar -xz -C /usr/local/bin helmfile

    Create helmfile.yaml:

    yaml
    repositories:
      - name: ingress-nginx
        url: https://kubernetes.github.io/ingress-nginx
      - name: jetstack
        url: https://charts.jetstack.io
      - name: bitnami
        url: https://charts.bitnami.com/bitnami

    releases: - name: ingress-nginx namespace: ingress-nginx createNamespace: true chart: ingress-nginx/ingress-nginx version: 4.11.2 values: - ./values/ingress-nginx.yaml

    - name: cert-manager namespace: cert-manager createNamespace: true chart: jetstack/cert-manager version: v1.15.3 values: - installCRDs: true

    - name: postgres namespace: databases createNamespace: true chart: bitnami/postgresql version: 15.5.20 values: - ./values/postgres.yaml needs: - cert-manager/cert-manager # deploy after cert-manager

    Apply the full stack:

    bash
    helmfile apply

    Helmfile installs each release as an independent Helm release, respects the needs ordering, and prints a diff before applying. This is the pattern most production teams adopt because it scales cleanly, integrates with GitOps workflows, and makes per-environment overrides trivial (helmfile -e production apply, helmfile -e staging apply).

    For a full GitOps workflow that automatically reconciles your Helmfile (or plain Helm releases) from a Git repo, see our companion guide How to Install ArgoCD on Ubuntu 24.04. For automatic TLS issuance across every ingress you deploy, install cert-manager as the second chart in your stack.

    Troubleshooting

    ProblemCauseSolution
    Error: Kubernetes cluster unreachable~/.kube/config missing, wrong context, or API server unreachableCheck kubectl cluster-info. Set KUBECONFIG=/etc/rancher/k3s/k3s.yaml for k3s. Verify the current context: kubectl config current-context.
    Error: INSTALLATION FAILED: cannot re-use a name that is still in useA release with that name already exists in the namespaceList releases: helm list -A. Either pick a different name or run helm upgrade instead of helm install.
    Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progressA previous Helm operation crashed and left a pending lockCheck: helm list -A --pending. Mark as failed: kubectl -n <ns> rollout status on the resources, then helm rollback <release> <prev-revision> or delete the stuck secret: kubectl -n <ns> delete secret -l owner=helm,name=<release>,status=pending-upgrade.
    Error: failed to download "bitnami/postgresql"Repo index is stale or the chart version no longer existshelm repo update. Check available versions: helm search repo bitnami/postgresql --versions.
    Template renders wrong indentationMissing nindent vs indent, or misplaced {{- whitespace trimRender with helm template and inspect. Remember: nindent N adds a newline then indents; indent N only indents.
    Values not taking effectWrong key path or YAML type mismatchhelm get values <release> to see what Helm thinks the values are. Compare against helm show values <chart> for the canonical key paths.
    pull access denied on OCI chartNot logged into the OCI registryhelm registry login ghcr.io -u <user> before helm install oci://....
    CRDs not upgraded between chart versionsHelm does not re-apply CRDs on upgrade by designManually apply new CRDs from the chart's crds/ directory, or use --force cautiously. cert-manager and similar projects document the CRD upgrade procedure.

    Viewing Helm Logs

    Helm itself is stateless -- release history is stored as Kubernetes Secrets in the release's namespace (type helm.sh/release.v1). To debug a flaky install:

    bash
    helm install --debug --dry-run my-release ./my-chart

    This prints the rendered manifests and Helm's internal decision log. Pair it with -v 6 for verbose HTTP-level tracing against the Kubernetes API.

    FAQ

    Do I need Tiller?

    No. Tiller was the server-side component of Helm 2 and is completely gone in Helm 3. Helm 3 is a pure client that talks directly to the Kubernetes API, stores release metadata in Secrets, and requires no cluster-side install. Every command in this guide assumes Helm 3.

    Where does Helm store release history?

    By default, in a Kubernetes Secret of type helm.sh/release.v1 per release, per revision, in the release's namespace. You can change the backend to ConfigMaps or SQL via the HELM_DRIVER environment variable, but Secrets are the default and recommended because values can contain sensitive data. Inspect with: kubectl -n <namespace> get secrets -l owner=helm.

    How is Helm different from Kustomize?

    Kustomize is a template-free patch tool: you start from a base YAML and overlay patches per environment. Helm is a template engine with packaging, versioning, repositories, and release management. The two can coexist -- you can pipe helm template output into Kustomize, or use the Helm chart inflator inside Kustomize. In practice: use Helm for third-party applications (Prometheus, PostgreSQL, ingress-nginx) where a packaged chart already exists; use Kustomize for in-house apps where you want strict declarative manifests without Go templating. Many teams use both.

    Can I use Helm without the internet?

    Yes. Chart tarballs are self-contained. Download a chart once with helm pull bitnami/postgresql --version 15.5.20 and commit the resulting .tgz to your infrastructure repo. Install with helm install postgres ./postgresql-15.5.20.tgz. For air-gapped clusters, mirror the charts to an internal Harbor or Chartmuseum instance.

    How do I uninstall a chart cleanly?

    bash
    helm uninstall <release-name> -n <namespace>

    This deletes every resource Helm created for that release. By default it also deletes the release history, which means you cannot roll back afterwards -- pass --keep-history if you want to preserve the audit trail. Important: Helm does not delete CRDs or PersistentVolumeClaims automatically. Those are considered data and must be removed manually: kubectl delete pvc -l app.kubernetes.io/instance=<release>.

    Should I commit values.yaml to Git?

    Yes, but never commit secrets. Keep values.yaml in Git; keep passwords, API keys, and TLS certificates in Kubernetes Secrets (referenced from values via existingSecret) or a dedicated secrets manager like sealed-secrets, SOPS, or HashiCorp Vault. The Helm docs on values best practices go deeper on this.

    Next Steps

    With Helm installed and understood, here is the natural progression for building out a real platform:

    • Install cert-manager for automatic TLS -- Follow How to Install cert-manager on Ubuntu 24.04 to issue Let's Encrypt certificates to every ingress Helm deploys. Takes 10 minutes, saves you from manual certificate renewals forever.
    • Set up ArgoCD for GitOps -- How to Install ArgoCD on Ubuntu 24.04 shows how to reconcile your Helm releases (or Helmfile, or Kustomize) from a Git repository automatically. Merge a PR and your cluster updates.
    • Deploy the kube-prometheus-stack chart -- helm install monitoring prometheus-community/kube-prometheus-stack gives you Prometheus, Grafana, Alertmanager, and dozens of pre-built dashboards in one command. It is the reference example of what Helm enables.
    • Write your second chart -- Now that you have scaffolded dm-web, try packaging a real internal service. Use sub-charts for its database, templatize environment-specific overrides, and push the chart to an OCI registry for CI reuse.
    • Read the Helm chart best practices guide -- The official best practices documentation covers naming conventions, label schemas, template composition, and versioning strategies that will save you from rewriting charts six months from now.
    • Explore the Artifact Hub -- artifacthub.io indexes 15,000+ charts from hundreds of repositories. Before you write anything, check if someone already packaged it.

    Need a VPS to run your Kubernetes + Helm lab?
    >
    The CloudCore Starter plan is purpose-built for single-node k3s clusters: 4 vCPU, 8 GB RAM, 75 GB NVMe, unmetered bandwidth, from EUR 7.99/month. Root access, full kernel control, and Ubuntu 24.04 ready to go.
    >
    Deploy in 60 seconds and follow this guide end-to-end.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket