Deploying Helm Charts through ArgoCD: Streamlining Kubernetes Deployments

Learn how to deploy Helm Charts with ArgoCD with four different approaches.

Deploying Helm Charts through ArgoCD: Streamlining Kubernetes Deployments
Photo by Miguel Ausejo / Unsplash

In this article, we'll explore the seamless integration of Helm Charts with ArgoCD and unveil the benefits it brings to your deployment workflows. Whether you're a developer looking for streamlined application deployment or part of an operations team looking for automated release management, the marriage of ArgoCD and Helm Charts can greatly enhance your Kubernetes deployment experience.

What are Helm Charts?

Helm Charts are a powerful tool that streamlines the process of deploying applications on Kubernetes. They provide a standardized way to package and manage Kubernetes resources, allowing developers and operators to easily define, version, and share applications. Whether you're deploying a simple microservice or a complex, multi-tier application, Helm Charts provide a convenient and efficient way to manage deployments and their associated dependencies.

What is ArgoCD?

ArgoCD is an open-source continuous delivery (CD) tool designed for deploying and managing applications in Kubernetes clusters. It follows the GitOps approach, meaning that the desired state of the application and the configuration details are specified in a Git repository. ArgoCD then continuously monitors the repository for changes and synchronizes the state of the deployed applications with the desired state specified in the Git repository.

Supported approaches to deploy Helm Charts

At the time of writing, ArgoCD supports these four approaches for deploying Helm Charts:

  1. ArgoCD application pointing to a Helm Chart in a Helm repository
  2. ArgoCD application pointing to a Helm Chart in a Git repository
  3. ArgoCD application pointing to a Helm Dependency in a Git repository
  4. ArgoCD application pointing to a Kustomize folder in a Git repository

Let's go ahead and take a look at each of these approaches, how resources are defined, and the advantages and disadvantages of each.

1. ArgoCD application pointing at a Helm Chart in a Helm repository

The first approach is the most native and straightforward way to install a Helm Chart. The ArgoCD application CRD provides attributes that can be used to directly point to a Helm repository (repoURL) and the desired Helm Chart and version (chart and targetRevision). See the following snippet of a Kubernetes manifest describing an ArgoCD application.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-postgres
spec:
  project: default
  source:
    # The URL of the Helm repository
    repoURL: https://charts.bitnami.com/bitnami
    # The name of the Helm Chart
    chart: postgresql
    # The version of the Helm Chart
    targetRevision: "12.5.8"
    helm:
      # Define the release name (defaults to the name of the ArgoCD application)
      releaseName: my-postgres-database
      # Helm values as block file
      values: |
        auth:
          username: my-user
          password: my-password
  destination:
    server: https://kubernetes.default.svc
    namespace: default

There are even more attributes available that can be used to configure the installation of the Helm Chart. See the ArgoCD sample application, which explains all of the available parameters.

Advantages and disadvantages

Let's start with the sunny side of using this approach: It is quite simple to understand and you only need to define one resource - the ArgoCD application. You can do this using the ArgoCD UI or you can store the manifest in a Git repository. The ease of use makes this approach the first choice for new users of ArgoCD.

Ease of use often means less flexibility and functionality, and this approach is no exception. So let's take a look at the problems with this approach.

Firstly, this approach makes secret management quite difficult. If the Helm Chart provides an option to specify an existing Kubernetes secret you have to manage the lifecycle of that secret in addition to the lifecycle of the ArgoCD application.

Another drawback of this design pattern is the lack of flexibility and customization of objects deployed in the Helm Chart beyond what the original Chart author allows. For example, if the original author does not provide options for configuring the replicas using Helm values, users will not be able to set this option during deployment.

You will also need to give your developers access to the definition of the ArgoCD application manifests. This is something you typically want to avoid and only the Ops or Platform team should be able to create ArgoCD applications.

In addition, this approach creates difficulties when debugging or rendering a Helm Chart from a development machine using the helm lint or helm template command. While parameters defined in the ArgoCD application can be manually replicated for command-line execution, this method is prone to errors and typos.

Since ArgoCD v2.6 you can define multiple sources for an ArgoCD application. With this addition, you could store a values.yaml in a separate Git repository and use it for local execution of helm template. However, this leaves the values.yaml file orphaned in the Git repo without any additional context, such as the Chart repository, name, and version. This can be mitigated with the upcoming approaches.

When to use

If your intention is to use a well-maintained, thoroughly documented Helm Chart that requires minimal troubleshooting, then this approach is highly recommended. It is particularly suitable for prototyping purposes or for deploying applications with a set-it-and-forget-it mentality.

2. ArgoCD application pointing to a Helm Chart in a Git repository

This approach is only suitable if you are developing a Helm Chart yourself and want to deploy the application directly from the Git repository where the source code of the Helm Chart is stored. You will need to point the ArgoCD application to this Git repository as shown here:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: mychart
spec:
  project: default
  source:
    repoURL: https://github.com/christianhuth/blog-code.git
    targetRevision: main
    path: deploying-helm-charts-through-argocd/2/mychart
    helm:
      releaseName: mychart
      valueFiles:
        - values.yaml
        - values-production.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: default

Inside the Git repository, you can define a standard configuration with a values.yaml and an environment-specific configuration with a values-production.yaml - or similar.

Advantages and disadvantages

This design pattern offers a notable advantage by providing Helm developers with a highly immersive experience. It allows them to take full advantage of Helm's features, including helm template and helm lint, within their local environment. As a result, developers can easily render the chart locally for convenient testing.

However, because the Git repository is used for both your development code and the GitOps code, it contains a lot of files that are not needed for the actual deployment, such as .helmignore, Chart.lock or dependent chart *.tgz files that have been downloaded locally for testing. To minimize this clutter in the repository, certain files can be included in the .gitignore file.

Another disadvantage comes with the fact that you have to change the ArgoCD application manifest if you want to change the version of the Helm Chart you're installing. In production environments, you'll usually want to set the version (targetRevision) to a fixed value, such as 12.5.8, and not use branches, such as the main branch in the example above. If your developers want to deploy a new version, say 12.5.9, they would need to make changes to the ArgoCD application manifest as well as to the Helm Chart.

This is also the biggest problem I have with this approach, as it requires multiple branches or tags to model different environments. Each version of your Helm Chart ends up with a new tag and this code base is tightly coupled to the deployment configuration as the value file(s) are stored in the same location.

When to use

This option is ideal for extremely rapid prototyping when developing a custom chart, as you don't need to package and store it in a Helm repository. There is also no need to create a CI pipeline - although this is not really recommended for production environments.

3. ArgoCD application pointing to a Helm Dependency in a Git repository

The next approach is my personal favorite, as it gives you maximum flexibility and reproducibility. As with the second approach, we point the ArgoCD application to a Git repository containing a Helm Chart.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-postgres
spec:
  project: default
  source:
    repoURL: https://github.com/christianhuth/blog-code.git
    targetRevision: main
    path: deploying-helm-charts-through-argocd/3
    helm:
      releaseName: my-postgres-database
      values: |
        auth:
          username: my-user
          password: my-password
      valueFiles:
        - my-values.yaml
  destination:
    server: https://kubernetes.default.svc
    namespace: default

Inside the Git repository, you have a stump Helm Chart definition, which consists only of a Chart.yaml and an (optional) values file - like my-values.yaml in the example above. Inside the Chart.yaml you reference the Helm Chart you want to install by using Helm dependencies.

apiVersion: v2
name: my-postgres
version: 1.0.0
dependencies:
  - name: postgresql
    repository: https://charts.bitnami.com/bitnami
    version: 12.5.8

If you want to define the configuration using a values file, you need to be aware of the additional hierarchy level that needs to be inserted into the file. The name of the root node corresponds to the name of the Helm Chart as defined in the dependencies section.

postgresql:
  auth:
    username: my-user
    password: my-password

If you are using an alias name for the dependency you have to change the name of the root node to this alias - myPostgresInstance in this example:

dependencies:
  - name: postgresql
    repository: https://charts.bitnami.com/bitnami
    version: 12.5.8
    alias: myPostgresInstance
myPostgresInstance:
  auth:
    username: my-user
    password: my-password

With this approach, it's quite easy to model different environments with different versions of the same Helm Chart and its specific values. A repository layout might look like this:

|--- gitops-charts
     |--- common
          |--- values.yaml
     |--- production
          |--- Chart.yaml
          |--- values.yaml
     |--- staging
          |--- Chart.yaml
          |--- values.yaml
     |--- development
          |--- Chart.yaml
          |--- values.yaml

Advantages and disadvantages

Most of the benefits we have identified for the second approach also apply here, such as using the helm template and helm lint command. You can also split the responsibility for the ArgoCD application manifest and the installed Helm Chart and its configuration between different roles and Git repositories, but still, give the Ops team the ability to define values through the ArgoCD Application.

The biggest problem I experience, when teaching this approach is the complexity. People often get confused by this nested Helm Chart mechanism and especially beginners who are just starting to learn about Helm Charts, ArgoCD, or Kubernetes often don't understand where to define what part of the overall architecture.

It also takes longer for a developer to deploy a new version of a Helm Chart. We first have to create a new Helm Chart, store it in the Helm repository, and then adjust the GitOps repository to reference the new version of the Helm Chart through the Helm dependencies. But most of these steps can be automated with a proper CI pipeline and tools like the RenovateBot.

The biggest advantage is the high degree of reproducibility. As long as you don't overwrite an existing version of a Helm Chart - which you should never do anyway - you can be 100% sure that you can reproduce your deployment in any environment.

It also comes with the cleanest separation of concerns. Developers work in their Git repository to develop the Helm Chart. The DevOps team can deploy these Helm Charts in the desired version and configuration through the GitOps repository and the Ops/Platform team can define the ArgoCD applications. This gives you the most control over who can do what in your deployment architecture.

When to use

To manage a Chart with a more complex lifecycle, this pattern allows users to maintain different environments that host different versions of the Chart. Just as images can be promoted across different environments, this pattern facilitates the promotion of changes to a Helm Chart across different environments.

This approach offers significant benefits to larger organizations with a higher degree of separation of responsibilities. The clear separation of concerns provided by this approach enables such organizations to effectively streamline their operations and improve collaboration between different teams or departments.

4. ArgoCD application pointing to a Kustomize folder in a Git repository

All previous approaches have one major disadvantage in common. If the original author of a Helm Chart does not provide the ability to configure certain settings, users will not be able to customize those options to suit their needs. A solution to this problem is provided by the last approach I want to explain, which uses Kustomize to render a Helm Chart.

To do this, you need to create a kustomization.yaml that points to your desired Helm Chart and an ArgoCD application that points to that kustomization.yaml in a Git repository:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: helm-through-kustomize
spec:
  project: default
  source:
    repoURL: https://github.com/christianhuth/blog-code.git
    targetRevision: main
    path: deploying-helm-charts-through-argocd/4
    kustomize: {}
  destination:
    server: https://kubernetes.default.svc
    namespace: default
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
helmCharts:
  - name: postgresql
    repo: https://charts.bitnami.com/bitnami
    version: "12.5.8"
    releaseName: my-postgres-database
    namespace: default
    valuesFile: my-values.yaml
    valuesInline:
      auth:
        username: my-user
        password: my-password

As you can see you can use the full power of Kustomize and define the configuration for installing a Helm Chart with valuesFile and valuesInLine.

Locally you can render the Helm Chart using kubectl with the kustomize subcommand by running kubectl kustomize --enable-helm ..

For this approach to work with ArgoCD, you need to customize the ConfigMap argocd-cm and add the --enable-helm flag to the built-in kustomize command.

apiVersion: v1
kind: ConfigMap
  name: argocd-cm
data:
  kustomize.buildOptions: --enable-alpha-plugins --enable-helm
  ...

If you are using the official Helm Chart argo-cd you can use this configuration:

configs:
  cm:
    kustomize.buildOptions: "--enable-alpha-plugins --enable-helm"

If you are using the ArgoCD Operator, you can adjust the configuration like this:

apiVersion: argoproj.io/v1alpha1
kind: ArgoCD
metadata:
  name: my-argocd
spec:
  extraConfig:
    "kustomize.buildOptions": "--enable-alpha-plugins --enable-helm"

Find more information about rendering Helm Charts using Kustomize with ArgoCD in the official documentation here.

Advantages and disadvantages

The main advantage of this approach is the ability to customize the resources created using Kustomize Overlays. Even if the Helm Chart doesn't provide an option to define the replicas for example, you can change the default configuration by using a simple Overlay. If you don't know about Kustomize Overlays yet, check out the official documentation.

It also allows a clean separation between Dev and Ops, where the Ops people create and manage the ArgoCD Application manifest and the Devs define the configuration and version of the application in the Kustomize manifest.

A disadvantage of this approach is that the ArgoCD configuration has to be adjusted first, which can be difficult for people who don't have much experience with ArgoCD and just want to deploy their application quickly and easily.

The biggest drawback from my point of view is that you lose control over the behavior of the Helm Chart. Many Helm Charts rely on using Helm Hooks to achieve a timely ordered deployment of the Kubernetes resources.

When to use

If you already make extensive use Kustomize to define your applications, then this option is for you. This approach also allows you to override resources created by the Helm Chart, so it's ideal if you don't have control over the behavior of the Helm Chart you're deploying.

Even more options

Thanks to its flexibility ArgoCD offers other options as well and you can also combine techniques, as shown in approach number 4, where we used Helm Charts and Kustomize.

💡
You can find the source code of this article on my GitHub profile as well: https://github.com/christianhuth/blog-code.

One approach that is not natively supported by ArgoCD is the usage of Helmfile. I have written another article that explains this setup in detail. Check it out here.