{"id":198,"date":"2024-12-17T01:52:00","date_gmt":"2024-12-17T01:52:00","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=198"},"modified":"2024-12-17T01:52:00","modified_gmt":"2024-12-17T01:52:00","slug":"create-helm-chart","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=198","title":{"rendered":"How to Create Helm Chart [Comprehensive Beginners Guide]"},"content":{"rendered":"<p>Learn how to create a Helm chart with our easy-to-follow Helm Charts Tutorial. This guide covers structure, components, and best practices.<\/p>\n<p>So if you want to learn Helm chart basics and get hands-on with Helm charts, you will love this guide.<\/p>\n<h2 id=\"prerequisites\">Prerequisites<\/h2>\n<p>To get started with Helm charts, you need to have the following.<\/p>\n<ol>\n<li>A working <a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\">Kubernetes cluster<\/a><\/li>\n<li><a href=\"https:\/\/devopscube.com\/install-configure-helm-kubernetes\/\">Helm installed<\/a> on your workstation<\/li>\n<li>A valid <a href=\"https:\/\/devopscube.com\/kubernetes-kubeconfig-file\/\">kubeconfig<\/a> to connect to the cluster<\/li>\n<li>Working <a href=\"https:\/\/devopscube.com\/kubernetes-tutorials-beginners\/\">knowledge of Kubernetes<\/a> and YAML.<\/li>\n<\/ol>\n<h2 id=\"what-is-helm-chart\">What is Helm Chart?<\/h2>\n<p>For the purpose of explanation, I am choosing a very basic example of a website frontend deployment using Nginx on Kubernetes<\/p>\n<p>Let&#8217;s assume you have four different environments in your project.<strong> Dev, QA, Staging<\/strong>, and <strong>Prod<\/strong>. Each environment will have different parameters for Nginx deployment. For example,<\/p>\n<ol>\n<li>In Dev and QA you might need only one replica.<\/li>\n<li>In staging and production, you will have more replicas with pod autoscaling.<\/li>\n<li>The ingress routing rules will be different in each environment.<\/li>\n<li>The config and secrets will be different for each environment.<\/li>\n<\/ol>\n<p>Because of the change in configs and deployment parameters for each environment, you need to maintain different Nginx deployment files for each environment. Or you will have a single deployment file and you will need to write custom shell or python scripts to replace values based on the environment. However, it is not a scalable approach. Here is where the helm chart comes into the picture.<\/p>\n<figure class=\"kg-card kg-image-card kg-card-hascaption\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/helm-chart-drawio-1.png\" class=\"kg-image\" alt=\"what is a helm chart\" loading=\"lazy\" width=\"691\" height=\"570\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/helm-chart-drawio-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/helm-chart-drawio-1.png 691w\"><figcaption><span style=\"white-space: pre-wrap;\">Click to view in HD<\/span><\/figcaption><\/figure>\n<p>Helm charts are a combination of <a href=\"https:\/\/devopscube.com\/create-kubernetes-yaml\/\">Kubernetes YAML manifest <\/a>templates and helm-specific files. You can call it a helm package. Since the Kubernetes YAML manifest can be templated, you don&#8217;t have to maintain multiple helm charts of different environments. Helm uses the <a href=\"https:\/\/pkg.go.dev\/text\/template?ref=devopscube.com\" rel=\"noreferrer noopener\">go templating engine<\/a> for the templating functionality.<\/p>\n<p>You just need to have a <strong>single helm chart<\/strong> and you can modify the deployment <strong>parameters of each environment<\/strong> by just changing a single values file. Helm will take care of applying the values to the templates. We will learn more about it practically in the next sections.<\/p>\n<p>At a high level, Helm Charts reduce the complexity, and kubernetes manifest redundancy of each environment <code>(dev, uat, cug, prod)<\/code> with only one template.<\/p>\n<h2 id=\"helm-chart-structure\">Helm Chart Structure<\/h2>\n<p>To understand the Helm chart, let&#8217;s take an example of Nginx deployment. To deploy Nginx on Kubernetes, typically you would have the following YAML files.<\/p>\n<pre><code class=\"language-bash\">nginx-deployment\n    \u251c\u2500\u2500 configmap.yaml\n    \u251c\u2500\u2500 deployment.yaml\n    \u251c\u2500\u2500 ingress.yaml\n    \u2514\u2500\u2500 service.yaml<\/code><\/pre>\n<p>Now if we create a Helm Chart for the above Nginx deployment, it will have the following directory structure.<\/p>\n<pre><code class=\"language-bash\">nginx-chart\/\n|-- Chart.yaml\n|-- charts\n|-- templates\n|   |-- NOTES.txt\n|   |-- _helpers.tpl\n|   |-- deployment.yaml\n|   |-- configmap.yaml\n|   |-- ingress.yaml\n|   |-- service.yaml\n|   `-- tests\n|       `-- test-connection.yaml\n`-- values.yaml<\/code><\/pre>\n<p>As you can see, the deployment YAML files are part of the template directory (highlighted in bold) and there are helm-specific files and folders. Let\u2019s look at each file and directory inside a helm chart and understand its importance.<\/p>\n<ol>\n<li><strong>.helmignore:<\/strong> It is used to define all the files that we don\u2019t want to include in the helm chart. It works similarly to the <code>.gitignore<\/code> file.<\/li>\n<li><strong>Chart.yaml:<\/strong> It contains information about the helm chart like version, name, description, etc.<\/li>\n<li><strong>templates: <\/strong>This directory contains all the Kubernetes manifest files that form an application. These manifest files can be templated to access values from <strong><code>values.yaml<\/code><\/strong> file. It is similar to Ansible templates.<\/li>\n<li><strong>values.yaml<\/strong>: In this file, we define the values for the templates. For example, image name, replica count, HPA values, etc. <\/li>\n<li><strong>charts:<\/strong> We can add another chart\u2019s structure inside this directory if our main charts have some dependency on others. By default this directory is empty.<\/li>\n<li><strong>templates\/NOTES.txt:<\/strong> This is a plaintext file that supports Go templating to <a href=\"https:\/\/devopscube.com\/helm-notes-txt-file\/\" rel=\"noreferrer\">print post installation instructions for the helm chart.<\/a><\/li>\n<li><strong>templates\/_helpers.tpl:<\/strong> There are scenarios where same logic gets repeated in Helm templates. For example, labels. In such cases you can maintain <a href=\"https:\/\/devopscube.com\/_helpers-tpl-file-in-helm-charts\/\" rel=\"noreferrer\"><strong>reusable template functions<\/strong><\/a> to avoid repeating the same blocks again in your chart.<\/li>\n<li><strong>templates\/tests\/:<\/strong> We can define tests in our charts to validate that your chart works as expected when it is installed.<\/li>\n<\/ol>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2026\/04\/image-32.png\" class=\"kg-image\" alt=\"Helm Chart Structure Illustration\" loading=\"lazy\" width=\"1320\" height=\"1759\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2026\/04\/image-32.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2026\/04\/image-32.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2026\/04\/image-32.png 1320w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h2 id=\"helm-chart-tutorial-github-repo\">Helm Chart Tutorial GitHub Repo<\/h2>\n<p>The example helm chart and manifests used in this Helm Chart Tutorial are hosted on the <a href=\"https:\/\/github.com\/techiescamp\/helm-tutorial?ref=devopscube.com\" rel=\"noreferrer noopener\">Helm Chart Github repo<\/a>. You can clone it and use it to follow along with the guide.<\/p>\n<pre><code class=\"language-bash\">git clone https:\/\/github.com\/techiescamp\/helm-tutorial.git<\/code><\/pre>\n<h2 id=\"create-helm-chart-from-scratch\">Create Helm Chart From Scratch<\/h2>\n<p>To get hands-on with helm chart creation, let&#8217;s <strong>create an Nginx helm chart<\/strong> from scratch.<\/p>\n<p>Execute the following command to create the chart boilerplate. It creates a chart with the name <code>nginx-chart<\/code> with default files and folders.<\/p>\n<pre><code class=\"language-bash\">helm create nginx-chart<\/code><\/pre>\n<p>If you check the created chart, it will have the following files and directories.<\/p>\n<pre><code class=\"language-bash\">nginx-chart\n\u2502   \u251c\u2500\u2500 Chart.yaml\n\u2502   \u251c\u2500\u2500 charts\n\u2502   \u251c\u2500\u2500 templates\n\u2502   \u2502   \u251c\u2500\u2500 NOTES.txt\n\u2502   \u2502   \u251c\u2500\u2500 _helpers.tpl\n\u2502   \u2502   \u251c\u2500\u2500 deployment.yaml\n\u2502   \u2502   \u251c\u2500\u2500 hpa.yaml\n\u2502   \u2502   \u251c\u2500\u2500 ingress.yaml\n\u2502   \u2502   \u251c\u2500\u2500 service.yaml\n\u2502   \u2502   \u251c\u2500\u2500 serviceaccount.yaml\n\u2502   \u2502   \u2514\u2500\u2500 tests\n\u2502   \u2502       \u2514\u2500\u2500 test-connection.yaml\n\u2502   \u2514\u2500\u2500 values.yaml<\/code><\/pre>\n<p>Let&#8217;s cd into the generated chart directory.<\/p>\n<pre><code class=\"language-bash\">cd nginx-chart<\/code><\/pre>\n<p>We&#8217;ll <strong>edit the files one by one<\/strong> according to our deployment requirements.<\/p>\n<h3 id=\"chartyaml\">Chart.yaml<\/h3>\n<p>As mentioned above, we put the details of our chart in <code>Chart.yaml<\/code> file. Replace the default contents of <code>chart.yaml <\/code>with the following.<\/p>\n<pre><code class=\"language-bash\">apiVersion: v2\nname: nginx-chart\ndescription: My First Helm Chart\ntype: application\nversion: 0.1.0\nappVersion: \"1.0.0\"\nmaintainers:\n- email: contact@devopscube.com\n  name: devopscube<\/code><\/pre>\n<ol>\n<li><strong>apiVersion<\/strong>: This denotes the chart API version v2 is for Helm 3 and above, and v1 is for previous versions.<\/li>\n<li><strong>name: <\/strong>Denotes the name of the chart.<\/li>\n<li><strong>description: <\/strong>Denotes the description of the helm chart.<\/li>\n<li><strong>Type<\/strong>: The chart type can be either \u2018<strong>application<\/strong>\u2019 or \u2018<strong>library<\/strong>\u2019. Application charts are what you deploy on Kubernetes. Library charts are re-usable charts that can be used with other charts. A similar concept of libraries in programming.<\/li>\n<li><strong>Version<\/strong>: This denotes the chart version.<\/li>\n<li><strong>appVersion<\/strong>: This denotes the version number of our application (Nginx).<\/li>\n<li><strong>maintainers:<\/strong> Information about the owner of the chart.<\/li>\n<\/ol>\n<p>We should increment the <code>version<\/code> and <code>appVersion<\/code> each time we make changes to the application. There are some other fields like dependencies, icons, etc.<\/p>\n<h3 id=\"templates\">templates<\/h3>\n<p>There are multiple files in <strong><code>templates<\/code><\/strong> directory created by Helm.  In our case, we will work on simple Kubernetes Nginx deployment.<\/p>\n<p>Let&#8217;s remove all default files from the template directory.<\/p>\n<pre><code class=\"language-bash\">rm -rf templates\/*<\/code><\/pre>\n<p>We will add our Nginx YAML files and change them to the template for better understanding.<\/p>\n<p>First <code>cd<\/code> in to the templates directory.<\/p>\n<pre><code class=\"language-bash\">cd templates<\/code><\/pre>\n<p>Create a <strong><code>deployment.yaml<\/code><\/strong> file and copy the following contents.<\/p>\n<pre><code class=\"language-yaml\">apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: release-name-nginx\n  labels:\n    app: nginx\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: nginx-chart\n          image: \"nginx:1.16.0\"\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: http\n              containerPort: 80\n              protocol: TCP\n          volumeMounts:\n            - name: nginx-index-file\n              mountPath: \/usr\/share\/nginx\/html\/\n      volumes:\n        - name: nginx-index-file\n          configMap:\n            name: index-html-configmap<\/code><\/pre>\n<p>If you see the above YAML file, the values are static. The idea of a helm chart is to template the YAML files so that we can <strong>reuse them in multiple environments<\/strong> by dynamically assigning values to them.<\/p>\n<p>To template a value, all you need to do is add the <strong>object parameter<\/strong> inside curly braces as shown below. It is called a <strong>template directive<\/strong> and the syntax is specific to the <strong>Go templating<\/strong><\/p>\n<pre><code class=\"language-yaml\">{{ .Object.Parameter }}<\/code><\/pre>\n<p>First Let&#8217;s understand what is an Object. Following are the three Objects we are going to use in this example.<\/p>\n<ol>\n<li><strong>Release<\/strong>: Every helm chart will be deployed with a release name. If you want to use the release name or access <strong>release-related dynamic values <\/strong>inside the template, you can use the release object.<\/li>\n<li><strong>Chart<\/strong>: If you want to use any values you mentioned in the <strong>chart.yaml<\/strong>, you can use the chart object.<\/li>\n<li><strong>Values<\/strong>: All parameters inside <strong>values.yaml<\/strong> file can be accessed using the Values object.<\/li>\n<\/ol>\n<p>To know more about supported Objects check the <a href=\"https:\/\/helm.sh\/docs\/chart_template_guide\/builtin_objects\/?ref=devopscube.com\" rel=\"noreferrer noopener\">Helm Builtin Object<\/a> document.<\/p>\n<p>The following image shows how the built-in objects are getting substituted inside a template.<\/p>\n<figure class=\"kg-card kg-image-card kg-card-hascaption\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/helm-template-1.png\" class=\"kg-image\" alt=\"helm template directive substitution workflow\" loading=\"lazy\" width=\"941\" height=\"854\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/helm-template-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/helm-template-1.png 941w\" sizes=\"auto, (min-width: 720px) 720px\"><figcaption><span style=\"white-space: pre-wrap;\">Click to view in HD<\/span><\/figcaption><\/figure>\n<p>First, you need to figure out what values could change or what you want to templatize. I am choosing <strong>name<\/strong>, <strong>replicas, container name, image,<\/strong> <strong>imagePullPolicy<\/strong> and <strong>configMap Name <\/strong>which I have highlighted in the YAML file in bold.<\/p>\n<ol>\n<li><strong>name:<\/strong> <code>name: {{ .Release.Name }}-nginx<\/code> : We need to change the deployment name every time as Helm does not allow us to install releases with the same name.<br \/>So we will templatize the name of the deployment with the release name and interpolate <strong>-nginx<\/strong> along with it. Now if we create a release using the name <strong>frontend<\/strong>, the deployment name will be <strong>frontend-nginx<\/strong>. This way, we will have guaranteed unique names.<\/li>\n<li><strong>container name<\/strong>: <code>{{ .Chart.Name }}<\/code>: For the container name, we will use the Chart object and use the chart name from the <strong>chart.yaml<\/strong> as the container name.<\/li>\n<li><strong>Replicas:<code> <\/code><code>{{ .Values.replicaCount }}<\/code><\/strong> We will access the replica value from the <strong>values.yaml <\/strong>file.<\/li>\n<li><strong>image:<\/strong> <code>\"{{ .Values.image.repository }}:{{ .Values.image.tag }}\"<\/code> Here we are using multiple template directives in a single line and accessing the repository and tag information under the image key from the Values file.<\/li>\n<li><strong>configMap Name: <\/strong><code>{{ .Release.Name }}-index-html-configmap.<\/code> Here we are adding the release name to the configmap.<\/li>\n<\/ol>\n<p>Similarly, you can templatize the required values in the YAML file.<\/p>\n<h4 id=\"create-a-deployment-template\">Create a deployment template<\/h4>\n<p>Here is our final <strong><code>deployment.yaml<\/code><\/strong> file after applying the templates. Replace the deployment file contents with the following.<\/p>\n<pre><code class=\"language-yaml\">apiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: {{ .Release.Name }}-nginx\n  labels:\n    app: nginx\nspec:\n  replicas: {{ .Values.replicaCount }}\n  selector:\n    matchLabels:\n      app: nginx\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n        - name: {{ .Chart.Name }}\n          image: \"{{ .Values.image.repository }}:{{ .Values.image.tag }}\"\n          imagePullPolicy: {{ .Values.image.pullPolicy }}\n          ports:\n            - name: http\n              containerPort: 80\n              protocol: TCP\n          volumeMounts:\n            - name: nginx-index-file\n              mountPath: \/usr\/share\/nginx\/html\/\n      volumes:\n        - name: nginx-index-file\n          configMap:\n            name: {{ .Release.Name }}-index-html-configmap<\/code><\/pre>\n<h4 id=\"create-a-service-template\">Create a Service template<\/h4>\n<p>In the same we will also create a <strong><code>service.yaml<\/code><\/strong> template with the following content.<\/p>\n<pre><code class=\"language-yaml\">apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Release.Name }}-service\nspec:\n  selector:\n    app.kubernetes.io\/instance: {{ .Release.Name }}\n  type: {{ .Values.service.type }}\n  ports:\n    - protocol: {{ .Values.service.protocol | default \"TCP\" }}\n      port: {{ .Values.service.port }}\n      targetPort: {{ .Values.service.targetPort }}<\/code><\/pre>\n<p>In the <strong>protocol template directive<\/strong>, you can see a pipe <code>( | )<\/code> . It is used to define the default value of the protocol as TCP. It means, if we dont&#8217;t define the protocol value in <code>values.yaml<\/code> file or if it is empty, it will take TCP as a default value for protocol.<\/p>\n<h4 id=\"create-a-configmap-template\">Create a Configmap template<\/h4>\n<p>Create a <strong><code>configmap.yaml<\/code> <\/strong>and add the following contents to it. Here we are replacing the default Nginx <strong>index.html<\/strong> page with a custom HTML page. Also, we added a template directive to replace the environment name in HTML.<\/p>\n<pre><code class=\"language-yaml\">apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ .Release.Name }}-index-html-configmap\n  namespace: default\ndata:\n  index.html: |\n    &lt;html&gt;\n    &lt;h1&gt;Welcome&lt;\/h1&gt;\n    &lt;\/br&gt;\n    &lt;h1&gt;Hi! I got deployed in {{ .Values.env.name }} Environment using Helm Chart &lt;\/h1&gt;\n    &lt;\/html<\/code><\/pre>\n<h3 id=\"valuesyaml\">values.yaml<\/h3>\n<p>The <strong><code>values.yaml<\/code><\/strong> file contains all the values that need to be substituted in the template directives we used in the templates. <\/p>\n<p>For example, <strong><code>deployment.yaml<\/code><\/strong> template contains a template directive to get the image repository, tag, and pullPolicy from the <strong><code>values.yaml<\/code><\/strong> file. <\/p>\n<p>If you check the following <strong><code>values.yaml<\/code><\/strong> file, we have repository, tag, and pullPolicy key-value pairs nested under the image key. That is the reason we used <strong><code>Values.image.repository<\/code><\/strong><\/p>\n<p>Now, cd in to the charts root folder assuming you are in the templates folder.<\/p>\n<pre><code class=\"language-bash\">cd ..<\/code><\/pre>\n<p>Now, replace the default <strong><code>values.yaml<\/code><\/strong> content with the following.<\/p>\n<pre><code class=\"language-yaml\">replicaCount: 2\n\nimage:\n  repository: nginx\n  tag: \"1.16.0\"\n  pullPolicy: IfNotPresent\n\nservice:\n  name: nginx-service\n  type: ClusterIP\n  port: 80\n  targetPort: 9000\n\nenv:\n  name: dev<\/code><\/pre>\n<p>Now we have the Nginx helm chart ready and the final helm chart structure looks like the following.<\/p>\n<pre><code class=\"language-bash\">nginx-chart\n\u251c\u2500\u2500 Chart.yaml\n\u251c\u2500\u2500 charts\n\u251c\u2500\u2500 templates\n\u2502   \u251c\u2500\u2500 configmap.yaml\n\u2502   \u251c\u2500\u2500 deployment.yaml\n\u2502   \u2514\u2500\u2500 service.yaml\n\u2514\u2500\u2500 values.yaml<\/code><\/pre>\n<h2 id=\"validate-the-helm-chart\">Validate the Helm Chart<\/h2>\n<p>Now to make sure that our chart is valid and, all the indentations are fine, we can run the below command. Ensure you are inside the chart directory.<\/p>\n<pre><code class=\"language-bash\">helm lint .<\/code><\/pre>\n<p>If you are executing it from outside the <strong><code>nginx-chart <\/code><\/strong>directory, provide the full path of <strong><code>nginx-chart<\/code><\/strong><\/p>\n<pre><code class=\"language-bash\">helm lint \/path\/to\/nginx-chart<\/code><\/pre>\n<p>If there is no error or issue, it will show this result<\/p>\n<pre><code class=\"language-bash\">==&gt; Linting .\/nginx\n[INFO] Chart.yaml: icon is recommended\n\n1 chart(s) linted, 0 chart(s) failed<\/code><\/pre>\n<p>To validate if the values are getting substituted in the templates, you can render the templated YAML files with the values using the following command. It will generate and display all the manifest files with the substituted values.<\/p>\n<pre><code class=\"language-bash\">helm template .<\/code><\/pre>\n<p>We can also use <code>--dry-run<\/code> command to check. This will pretend to install the chart to the cluster and if there is some issue it will show the error.<\/p>\n<p>If you are still inside the <code>nginx-chart<\/code>you&#8217;re folder, move out of the folder and run the following command.<\/p>\n<pre><code class=\"language-bash\">helm install --dry-run my-release nginx-chart<\/code><\/pre>\n<p>If everything is good, then you will see the manifest output that will be deployed into the cluster.<\/p>\n<h2 id=\"deploy-the-helm-chart\">Deploy the Helm Chart<\/h2>\n<p>When you deploy the chart, Helm will read the chart and configuration values from the <code>values.yaml<\/code> file and generate the manifest files. Then it will send these files to the Kubernetes API server, and Kubernetes will create the requested resources in the cluster.<\/p>\n<p>Now we are ready to install the chart.<\/p>\n<p>Make sure to run the Helm commands from a directory outside the <code>helm-chart<\/code> folder.<\/p>\n<p>Execute the following command where <strong><code>frontend<\/code><\/strong> is release name and <strong><code>nginx-chart<\/code><\/strong> is the chart name. It installs <strong><code>nginx-chart<\/code><\/strong> in the default namespace<\/p>\n<pre><code class=\"language-bash\">helm install frontend nginx-chart<\/code><\/pre>\n<p>You will get the following output, once its deployed.<\/p>\n<pre><code class=\"language-bash\">NAME: frontend\nLAST DEPLOYED: Mon Jan 12 06:20:20 2026\nNAMESPACE: default\nSTATUS: deployed\nREVISION: 1\nDESCRIPTION: Install complete\nTEST SUITE: None<\/code><\/pre>\n<p>Now you can check the release list using this command: You can also use <code>ls<\/code> instead of <code>list<\/code><\/p>\n<pre><code class=\"language-bash\">$ helm list\n\nNAME      NAMESPACE  REVISION    UPDATED                                 STATUS          CHART             APP VERSION\n\nfrontend  default    1           2026-01-12 06:20:20.865280236 +0000 UTC deployed        nginx-chart-0.1.0 1.16.0<\/code><\/pre>\n<p>Run the kubectl commands to check the deployment, services, and pods.<\/p>\n<pre><code class=\"language-bash\">kubectl get deploy,svc,cm,po<\/code><\/pre>\n<p>We can see the deployment <strong><code>frontend-nginx<\/code><\/strong>, <strong><code>nginx-service<\/code><\/strong> and pods are up and running as shown below.<\/p>\n<pre><code class=\"language-bash\">NAME                             READY   UP-TO-DATE   AVAILABLE   AGE\ndeployment.apps\/frontend-nginx   2\/2     2            2           22m\n\nNAME                      TYPE       CLUSTER-IP    EXTERNAL-IP  PORT(S)  AGE\nservice\/frontend-service  ClusterIP  10.106.34.74  &lt;none&gt;       80\/TCP   22m\nservice\/kubernetes        ClusterIP  10.96.0.1     &lt;none&gt;       443\/TCP  24d\n\nNAME                                      DATA   AGE\nconfigmap\/frontend-index-html-configmap   1      22m\nconfigmap\/kube-root-ca.crt                1      24d\n\nNAME                                  READY   STATUS    RESTARTS   AGE\npod\/frontend-nginx-6ff9d468d5-5sts5   1\/1     Running   0          22m\npod\/frontend-nginx-6ff9d468d5-rhkb5   1\/1     Running   0          22m<\/code><\/pre>\n<p>We discussed how a single helm chart can be used for multiple environments using different <strong><code>values.yaml <\/code><\/strong>files.<\/p>\n<p>To install a helm chart with an external <code><strong>values.yaml<\/strong><\/code> file, you can use the following command with the <code>--values<\/code> flag and path of the values file.<\/p>\n<pre><code class=\"language-bash\">helm install frontend nginx-chart --values env\/prod-values.yaml<\/code><\/pre>\n<p>When you have Helm as part of your CI\/CD pipeline, you can write custom logic to pass the required values file depending on the environment.<\/p>\n<h2 id=\"helm-upgrade-rollback\">Helm Upgrade &amp; Rollback<\/h2>\n<p>Now suppose you want to modify the chart and install the updated version, we can use the below command:<\/p>\n<pre><code class=\"language-bash\">helm upgrade frontend nginx-chart<\/code><\/pre>\n<p>For example, we have changed the replicas from 2 to 1. You can see the revision number is 2 and only 1 pod is running.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-8-15.png\" class=\"kg-image\" alt=\"helm chart upgrade\" loading=\"lazy\" width=\"706\" height=\"395\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-8-15.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-8-15.png 706w\"><\/figure>\n<p>Now if we want to roll back the changes that we have just done and deploy the previous one again, we can use the rollback command to do that.<\/p>\n<pre><code class=\"language-bash\">helm rollback frontend<\/code><\/pre>\n<p>The above command will roll back the helm release to the previous one.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-10-18.png\" class=\"kg-image\" alt=\"helm chart rollback\" loading=\"lazy\" width=\"614\" height=\"330\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-10-18.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-10-18.png 614w\"><\/figure>\n<p>After the rollback, we can see 2 pods are running again. Note that Helm takes the rollback as a new revision, that&#8217;s why we&#8217;re getting the revision as 3.<\/p>\n<p>If we want to roll back to the specific version we can put the revision number like this.<\/p>\n<pre><code class=\"language-bash\">helm rollback &lt;release-name&gt; &lt;revision-number&gt;<\/code><\/pre>\n<p>For example,<\/p>\n<pre><code class=\"language-bash\">helm rollback frontend 2<\/code><\/pre>\n<h2 id=\"uninstall-the-helm-release\">Uninstall The Helm Release<\/h2>\n<p>To uninstall the helm release use uninstall command. It will remove all of the resources associated with the last release of the chart.<\/p>\n<pre><code class=\"language-bash\">helm uninstall frontend<\/code><\/pre>\n<p>If you have deployed the release in a specific namespace, you can pass the namespace flag with the uninstall command as given below.<\/p>\n<pre><code class=\"language-bash\">helm uninstall &lt;release-name&gt; --namespace &lt;namespace&gt;<\/code><\/pre>\n<h2 id=\"package-the-helm-chart\">Package the Helm Chart<\/h2>\n<p>We can package the chart and deploy it to Github, S3, or any chart repository.<\/p>\n<p>Execute the following command to package the <strong><code>nginx-chart<\/code><\/strong>.<\/p>\n<pre><code class=\"language-bash\">helm package chart-name\/<\/code><\/pre>\n<p>For example,<\/p>\n<pre><code class=\"language-bash\">helm package nginx-chart\n\nSuccessfully packaged chart and saved it to: \/home\/vagrant\/helm-tutorial\/nginx-chart-0.1.0.tgz<\/code><\/pre>\n<p>When you package it, it follows <a href=\"https:\/\/semver.org\/?ref=devopscube.com\" rel=\"noreferrer noopener\">semver 2<\/a> version guidelines.<\/p>\n<h2 id=\"debugging-helm-charts\">Debugging Helm Charts<\/h2>\n<p>We can use the following commands to debug the helm charts and templates.<\/p>\n<ol>\n<li><strong><code>helm lint:<\/code><\/strong> This command takes a path to a chart and runs a series of tests to verify that the chart is well-formed.<\/li>\n<li><strong><code>helm get values:<\/code><\/strong> This command will output the release values installed to the cluster.<\/li>\n<li><strong><code>helm install --dry-run:<\/code><\/strong> Using this function we can check all the resource manifests and ensure that all the templates are working fine.<\/li>\n<li><strong><code>helm get manifest:<\/code><\/strong> This command will output the manifests that are running in the cluster.<\/li>\n<li><strong><code>helm diff:<\/code><\/strong> It will output the differences between the two revisions.<\/li>\n<\/ol>\n<pre><code class=\"language-bash\">helm diff revision frontend 1 2<\/code><\/pre>\n<p>The helm diff command is not available by default, it&#8217;s a plugin that you have to <a href=\"https:\/\/github.com\/databus23\/helm-diff?ref=devopscube.com\" rel=\"noreferrer\">install<\/a> to use it.<\/p>\n<h2 id=\"helm-chart-possible-errors\">Helm Chart Possible Errors<\/h2>\n<p>If you try to install an existing Helm package, you will get the following error.<\/p>\n<pre><code class=\"language-bash\">level=ERROR msg=\"release name check failed\" error=\"cannot reuse a name that is still in use\"\nError: INSTALLATION FAILED: release name check failed: cannot reuse a name that is still in use<\/code><\/pre>\n<p>To update or upgrade the release, you need to run the upgrade command.<\/p>\n<p>If you try to install a chart from a different location without giving the absolute path of the chart, you will get the following error.<\/p>\n<pre><code class=\"language-bash\">Error: non-absolute URLs should be in form of repo_name\/path_to_chart<\/code><\/pre>\n<p>To rectify this, you should execute the helm command from the directory where you have the chart or provide the absolute path or relative path of the chart directory.<\/p>\n<h2 id=\"helm-charts-best-practices\">Helm Charts Best Practices<\/h2>\n<p>Following are some of the best practices to be followed when developing a Helm chart.<\/p>\n<ol>\n<li>Document your chart by adding comments and a <strong>README<\/strong> file as documentation is essential for ensuring maintainable Helm charts.<\/li>\n<li>We should name the Kubernetes manifest files after the Kind of object i.e. deployment, service, secret, ingress, etc.<\/li>\n<li>Put the chart name in lowercase only, and if it has more than one word, then separate them with hyphens (-)<\/li>\n<li>In values.yaml file field name should be in lowercase.<\/li>\n<li>Always wrap the string values between quote signs.<\/li>\n<li>Use Helm version 4 for simpler and more secure releases. Check <a href=\"https:\/\/helm.sh\/docs\/overview?ref=devopscube.com\" rel=\"noreferrer\">this document<\/a> for more details<\/li>\n<\/ol>\n<p>Also, to learn more best practices, checkout the <a href=\"https:\/\/devopscube.com\/helm-best-practices-essential-tips-to-know\/\" rel=\"noreferrer\">Helm best practices<\/a> guide.<\/p>\n<h2 id=\"helm-commands-summary-cheat-sheet\">Helm Commands Summary (Cheat Sheet)<\/h2>\n<p>Here\u2019s a quick reference list of common Helm commands you can use in your day-to-day Helm workflows.<\/p>\n<h4 id=\"helm-install\">helm install<\/h4>\n<p>Installs a Helm chart onto your Kubernetes cluster.<\/p>\n<pre><code class=\"language-bash\">helm install &lt;release-name&gt; &lt;chart-path-or-name&gt;<\/code><\/pre>\n<h4 id=\"helm-upgrade\">helm upgrade<\/h4>\n<p>Updates your existing Helm release with new chart changes or updated values.<\/p>\n<pre><code class=\"language-bash\">helm upgrade &lt;release-name&gt; &lt;chart-path-or-name&gt;<\/code><\/pre>\n<h4 id=\"helm-rollback\">helm rollback<\/h4>\n<p>Rolls back a Helm release to a previous revision.<\/p>\n<pre><code class=\"language-bash\">helm rollback &lt;release-name&gt; &lt;revision-number&gt;<\/code><\/pre>\n<h4 id=\"helm-test\"><strong>helm test<\/strong><\/h4>\n<p>Runs the tests defined in the Helm chart\u2019s <code>templates\/tests<\/code> directory against your release.<\/p>\n<pre><code class=\"language-bash\">helm test &lt;release-name&gt;<\/code><\/pre>\n<h4 id=\"helm-lint\"><strong>helm lint<\/strong><\/h4>\n<p>Checks a chart for possible issues like formatting or missing fields.<\/p>\n<pre><code class=\"language-bash\">helm lint &lt;chart-directory&gt;<\/code><\/pre>\n<h4 id=\"helm-template\">helm template<\/h4>\n<p>Renders a chart to show the Kubernetes manifests without installing them. Useful for debugging your templates.<\/p>\n<pre><code class=\"language-bash\">helm template &lt;chart-directory&gt;<\/code><\/pre>\n<h4 id=\"helm-list-helm-ls\"><strong>helm list (helm ls)<\/strong><\/h4>\n<p>Lists all the currently installed releases in the specified namespace (defaults to the current namespace).<\/p>\n<pre><code class=\"language-bash\">helm list<\/code><\/pre>\n<h4 id=\"helm-uninstall\"><strong>helm uninstall<\/strong><\/h4>\n<p>Uninstalls a Helm release, removing all the associated Kubernetes resources from your cluster.<\/p>\n<pre><code class=\"language-bash\">helm uninstall &lt;release-name&gt;<\/code><\/pre>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>To summarize,<\/p>\n<ol>\n<li>We discussed the Helm Chart and its structure in detail.<\/li>\n<li>We created a Helm chart from scratch and deployed it.<\/li>\n<li>Also learned how to upgrade, roll back, and uninstall it.<\/li>\n<\/ol>\n<p>Helm is a very useful package manager for Kubernetes. When you have different environments with custom deployment requirements, Helm provides a great way to templatize kubernetes manifests as per our needs.<\/p>\n<p>Helm-specific functionalities like chart dependencies and chart reusability make it one of the good kubernetes tools.<\/p>\n<p>Also, if you are preparing for <a href=\"https:\/\/devopscube.com\/cka-exam-study-guide\/\" rel=\"noreferrer\">CKA<\/a> or <a href=\"https:\/\/devopscube.com\/ckad-exam-study-guide\/\">CKAD certification<\/a>, Helm is an important topic for the exam.<\/p>\n<p>An alternative to Helm is Kustomize. It does not use templating, but it uses the concept of overlays. Refer to the <a href=\"https:\/\/devopscube.com\/kustomize-tutorial\/\">Kustomize tutorial<\/a> to learn more.<\/p>\n<p>Also, if you check our<a href=\"https:\/\/devopscube.com\/learn-kubernetes-complete-roadmap\/\"> learning kubernetes<\/a> guide, we have mentioned Helm as a must-learn tool for Kubernetes package management.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/create-helm-chart\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Create Helm Chart [Comprehensive Beginners Guide] \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/create-helm-chart\/<\/p>\n","protected":false},"author":1,"featured_media":199,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-198","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/198","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=198"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/198\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/199"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=198"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=198"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=198"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}