<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>grop.dev</title><id>https://grop.dev/feeds/tags/kubernetes.xml</id><subtitle>Tag: kubernetes</subtitle><updated>2026-10-03T11:42:51Z</updated><link href="https://grop.dev/feeds/tags/kubernetes.xml" rel="self" /><link href="https://grop.dev" /><entry><title>Argo CD ApplicationSet with templatePatch</title><id>https://grop.dev/posts/argo-cd-applicationset-with-templatepatch.html</id><author><name>Gunnar Grop</name><email>gunnar.grop@protonmail.com</email></author><updated>2026-02-04T12:39:00Z</updated><link href="https://grop.dev/posts/argo-cd-applicationset-with-templatepatch.html" rel="alternate" /><content type="html">&lt;h2&gt;Abstract&lt;/h2&gt;&lt;p&gt;Deploying applications with Argo CD can be done different ways for several different
reasons. For me, it differ quite a bit between the deployments I maintain professionally,
versus the stuff I deploy in my homelab. In my homelab I don't care as much about stability
or maintainability, and I care more about testing interesting things, quickly, and easily.&lt;/p&gt;&lt;p&gt;Therefore I've been trying to utilize Argo CD's &lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/applicationset-specification/&quot;&gt;ApplicationSet&lt;/a&gt; resource to deploy applications
in my homelab as easily and quickly as possible. Preferably I'd only need a single
ApplicationSet to deploy everything. Something i didn't think possible before I realized that
the &lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/applicationset-specification/&quot;&gt;templatePatch&lt;/a&gt; field can be used instead of &lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/applicationset-specification/&quot;&gt;template&lt;/a&gt; for basically everything.&lt;/p&gt;&lt;h2&gt;Writing ApplicationSets&lt;/h2&gt;&lt;p&gt;Normally, you'd write an ApplicationSet something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: example
spec:
  goTemplate: true
  goTemplateOptions: [&amp;quot;missingkey=error&amp;quot;]
  generators:
    - git:
        repoURL: https://git.example.com/argocd
        revision: HEAD
        files:
          - path: &amp;quot;**/values.yaml&amp;quot;
  template:
    metadata:
      name: '{{ .path.basename }}'
    spec:
      project: default
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{ .path.basename }}'
      source:
        chart: example-cahrt
        repoURL: https://example.github.io/charts/
        targetRevision: 1.0.0
        helm:
          valueFiles:
            - '{{ .path.path }}/values.yaml'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case the ApplicationSet uses a git generator to look through a specified git
repository and generates an Application resource for every file it finds called &lt;code&gt;values.yaml&lt;/code&gt;.
Furthermore, the name of the generated Applications will be the name of the directory where
&lt;code&gt;values.yaml&lt;/code&gt; lives.&lt;/p&gt;&lt;p&gt;This ApplicationSet will work great if your're planning on running several instances/deployments
of the same Helm chart! An example git directory structure would look something like this:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;.
├── app1
│   └── values.yaml
├── app2
│   └── values.yaml
└── app3
    └── values.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But that's not quite what I'm after. In my homelab I run many different charts, with
different requirements. Effectively, I have three distinct types of deployments:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Helm charts with a values file.&lt;/li&gt;&lt;li&gt;Helm charts with a values file and additional manifests.&lt;/li&gt;&lt;li&gt;Just Kubernetes manifests.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;So, with the help of templatePatch I came up with this design:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: meta-applicationset-applications
spec:
  goTemplate: true
  goTemplateOptions: [&amp;quot;missingkey=error&amp;quot;]
  generators:
    - git:
        repoURL: https://git.sr.ht/~gunnargrop/argocd-env-mars.internal
        revision: HEAD
        files:
          - path: &amp;quot;apps/**/meta.yaml&amp;quot;
  templatePatch: |
    spec:
      sources:
        {{- if .sources.chart.enabled }}
        - repoURL: &amp;quot;{{ .sources.chart.repoURL }}&amp;quot;
          chart: &amp;quot;{{ .sources.chart.chart }}&amp;quot;
          targetRevision: &amp;quot;{{ .sources.chart.targetRevision }}&amp;quot;
          helm:
            valueFiles:
              - &amp;quot;$values/{{ .path.path }}/values.yaml&amp;quot;
        {{- end }}
        {{- if .sources.values.enabled }}
        - repoURL: https://git.sr.ht/~gunnargrop/argocd-env-mars.internal
          targetRevision: &amp;quot;{{ .sources.values.targetRevision }}&amp;quot;
          ref: values
        {{- end }}
        {{- if .sources.manifests.enabled }}
        - repoURL: https://git.sr.ht/~gunnargrop/argocd-env-mars.internal
          targetRevision: &amp;quot;{{ .sources.manifests.targetRevision }}&amp;quot;
          path: &amp;quot;{{ .path.path }}/manifests&amp;quot;
        {{- end }}
      {{- with .syncPolicy }}
      syncPolicy:
        {{- toYaml . | nindent 6 }}
      {{- end }}
  template:
    metadata:
      name: &amp;quot;{{ .path.basename }}&amp;quot;
    spec:
      project: &amp;quot;{{ .project }}&amp;quot;
      destination:
        server: https://kubernetes.default.svc
        namespace: &amp;quot;{{ .namespace }}&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The biggest difference here is that &lt;code&gt;spec.source&lt;/code&gt; and &lt;code&gt;spec.syncPolicy&lt;/code&gt; is defined entirely
inside of the templatePatch field. What this allows for is much more powerful gotmpl templating,
where we can have if statements and other handy constructs.&lt;/p&gt;&lt;p&gt;It might be a bit dense to read and understand from a glance, so I'll point out how it works
by showing you what the git repository structure looks like.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;.
└── apps
    ├── app1
    │   ├── meta.yaml
    │   └── values.yaml
    ├── app2
    │   ├── manifests
    │   │   └── secret.yaml
    │   ├── meta.yaml
    │   └── values.yaml
    └── app3
        ├── manifests
        │   ├── deployment.yaml
        │   ├── ingress.yaml
        │   └── service.yaml
        └── meta.yaml&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see this ApplicationSet takes care of deploying all of the different deployment
types I have in my homelab. Importantly every &amp;quot;app&amp;quot; in the git repo have a special &lt;code&gt;meta.yaml&lt;/code&gt;
file. This file is just used to specify a bunch of variables that Argo CD will read and use
in the Application templating.&lt;/p&gt;&lt;p&gt;Here's one example of a &lt;code&gt;meta.yaml&lt;/code&gt; that just deploys regular manifests:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;project: default
namespace: default

sources:
  chart:
    enabled: false

  values:
    enabled: false

  manifests:
    enabled: true
    targetRevision: HEAD

syncPolicy:
  automated:
    prune: true&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And here is an example of a &lt;code&gt;meta.yaml&lt;/code&gt; that deploys a Helm chart:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;project: default
namespace: default

sources:
  chart:
    enabled: true
    repoURL: registry-1.docker.io/example
    chart: example
    targetRevision: 1.0.0

  values:
    enabled: true
    targetRevision: HEAD

  manifests:
    enabled: false

syncPolicy:
  automated:
    prune: true

  syncOptions:
    - CreateNamespace=true

  managedNamespaceMetadata:
    labels:
      trust: enabled&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Both of these files will be picked up by the ApplicationSet, which will create one
Application per file.&lt;/p&gt;&lt;p&gt;This is pretty much all I want! Now I no longer have to write any
Application/ApplicationSet resources manually, since I can just create a simple
&lt;code&gt;meta.yaml&lt;/code&gt; file in a directory in the specified git repository, and the one ApplicationSet
will deploy all of my applications.&lt;/p&gt;&lt;h2&gt;Conclusion&lt;/h2&gt;&lt;p&gt;As you can see, ApplicationSets are even more flexible than at first glance.
Of course, your needs/wants will probably differ from mine. Maybe you want a different
directory structure, or to supply several values files to a Helm deployment?
Argo CD and it's ApplicationSets can probably handle it.&lt;/p&gt;&lt;p&gt;Hopefully this little article has provided you a starting point for how to templatePatch.&lt;/p&gt;&lt;h2&gt;Related Reads&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/&quot;&gt;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Template/&quot;&gt;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Template/&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Security/&quot;&gt;https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Security/&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content></entry></feed>