<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Container Hub - Medium]]></title>
        <description><![CDATA[Anything and everything container related. - Medium]]></description>
        <link>https://medium.com/containers-101?source=rss----2f4a04e0f5fe---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Container Hub - Medium</title>
            <link>https://medium.com/containers-101?source=rss----2f4a04e0f5fe---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 10 Nov 2026 17:08:10 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/containers-101" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Top 30 Argo CD Anti-Patterns to Avoid When Adopting Gitops]]></title>
            <link>https://medium.com/containers-101/top-30-argo-cd-anti-patterns-to-avoid-when-adopting-gitops-1b147879297a?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/1b147879297a</guid>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[argo-cd]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Thu, 21 Aug 2025 09:20:13 GMT</pubDate>
            <atom:updated>2025-08-21T09:20:12.953Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1pVo9akgDs_Q5X37sJbsQg.jpeg" /></figure><p>The time has finally come! After the massive success of our <a href="https://codefresh.io/blog/docker-anti-patterns/">Docker </a>and <a href="https://codefresh.io/blog/kubernetes-antipatterns-1/">Kubernetes guides</a>, we are now ready to see several anti-patterns for <a href="https://codefresh.io/learn/argo-cd/">Argo CD</a>. Anti-patterns are questionable practices that people adopt because they seem like a good idea at first glance, but in the long run, they make processes more complicated than necessary.</p><p>Several times, we have spoken with enthusiastic teams that recognize the benefits of <a href="https://codefresh.io/learn/gitops/">Gitops </a>and want to adopt Argo CD as quickly as possible. The initial adoption phase seems to go very smoothly and more and more teams are getting onboarded to Argo CD. However, after a certain point things start slowing down and developers start complaining about the new process.</p><p>Like several open source projects, Argo CD has several capabilities that can be abused if you don’t have the full picture in your mind. The end result almost always makes life really difficult for developers.</p><p>So keep your developers happy and don’t fall into the same traps. Here is the full list of the antipatterns we will see:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/842/1*QywumoxVMY7rzBbZz7LCEA.jpeg" /></figure><p>The order of anti-patterns follows the timeline of an organization that starts with minimal Argo CD knowledge and slowly migrates several applications to the GitOps paradigm.</p><h3>Anti-pattern 1 — Not understanding the declarative setup of Argo CD</h3><p>Following the <a href="https://codefresh.io/learn/gitops/">GitOps principles</a> means that Argo CD can take your Kubernetes manifests (or Helm charts or Kustomize overlays) from Git and sync them on your Kubernetes cluster. This process is well understood by teams and most people are familiar with saving Kubernetes manifests in Git.</p><p>It is important to note however that even this link between a cluster and a Git repository is a Kubernetes resource itself.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/812/0*8wkBIgNV59cfr2aY.png" /></figure><p>Argo CD introduces its own <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/">Custom Resource Definitions (CRDs)</a> for several of its central concepts such as applications and <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/projects/">projects </a>and also reuses existing Kubernetes CRDs for clusters, secrets, etcs.</p><p><strong>These files should also be stored in Git</strong>. That is the whole point of following GitOps. It doesn’t make sense to use Git only for some files and not store the Applications themselves similarly.</p><p>We see several teams that use the Argo CD UI or CLI to create applications that are not stored anywhere and then have difficulties understanding what is deployed where or how to recreate their Argo CD configuration from scratch.</p><p>It should be noted that the “create new app” button in the Argo CD UI is only for experiments and quick tests. You should not use it at all in a production environment as the created application is not saved anywhere.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UoOqUnsbdpxB7ZSr.png" /></figure><p>Everything that Argo CD needs should be stored in Git. Recreating an Argo CD instance should be a simple process with minimal steps:</p><ol><li>Create a new cluster with Terraform/Pulumi/Crossplane etc.</li><li>Install Argo CD itself using Terraform/<a href="https://argocd-autopilot.readthedocs.io/en/stable/">Autopilot</a>/Codefresh etc.</li><li>Point Argo CD to your ApplicationSets or<a href="https://github.com/kostis-codefresh/many-appsets-demo/blob/main/root-argocd-app.yml"> root app-of-apps file</a></li><li>Finished.</li></ol><p>Recreating your Argo CD instance is a repeatable process that can be performed in less than 5 minutes (explained in anti-pattern 27).</p><p>If you want a comprehensive guide on how to organize your Kubernetes manifests in Git see our <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">Application Set guide</a>.</p><h3>Anti-pattern 2 — Creating Argo CD applications in a dynamic way</h3><p>A related anti-pattern occurs when organizations have an existing process for creating applications and when they adopt Argo CD they simply call the Argo CD CLI or API to pass the information they already have.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VjkHzaN7B13ib4cI.png" /></figure><p>Essentially there is an existing database or application configuration somewhere else and a custom CLI or other imperative tool does the following:</p><ol><li>Extracts application configuration from the existing database</li><li>Creates an Argo CD application or Kubernetes manifest on the fly</li><li>Applies this file to an Argo CD instance without storing anything in Git.</li></ol><p>You fall into this trap if the “official” way of creating Argo CD applications in your organization is something like this:</p><pre>my-app-cli new-app-name | argocd app create -f -</pre><p>Or several times, <a href="https://man7.org/linux/man-pages/man1/envsubst.1.html">envsubst </a>is used like this</p><pre>envsubst &lt; my-app-template.yaml | kubectl apply -f -n argocd</pre><p>The end result is always the same. You have custom Argo CD applications that <strong>are not saved anywhere</strong> in Git. You lose all the main benefits of GitOps:</p><ul><li>You don’t have a declarative file for what is deployed right now</li><li>You don’t have a history of what was deployed in the past</li><li>Recreating the Argo CD instance is not a single-step process any more (see anti-pattern 27)</li></ul><p>To overcome this anti-pattern you need to make sure that you use Argo CD the way GitOps works.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JZMZVG0rdAVj5Z1x.png" /></figure><p>You either need to convert your existing database settings and make them read/write to Git or discard them completely and start using Git as the single source of truth for everything.</p><p>Then, all day 2 operations should be handled by Git.</p><p>The same is true for updating existing applications. If you want to update the configuration of an application, the process should always be the same:</p><ol><li>You (or an external system) change a file in Git</li><li>Argo CD notices the change in Git</li><li>Argo CD syncs the changes to the cluster.</li></ol><p>If you use the Argo CD API or the Kubernetes API to manually patch resources then you are not following GItOps.</p><p>Updating applications in production with any of the following commands goes <strong>against </strong>the GitOps principles.</p><pre>kubectl set image deployment/my-deployment my-container=nginx:1.27.0<br>kubectl patch deployment &lt;deployment-name&gt; [.....patch here…]</pre><p>Use the Kubernetes API only for experiments and local tests, never for production upgrades (see also anti-pattern 10 — disabling auto-sync).</p><h3>Anti-pattern 3 — Using Argo CD parameter overrides</h3><p>Yet another way of updating an Argo CD application in a manner we do <strong>NOT </strong>recommend is the following:</p><pre>argocd CLI app set guestbook -p image=example/guestbook:v2.3</pre><p>The guest book application was just updated to version v2.3. Argo CD syncs the version and everything looks good. But where was this action saved? Nowhere.<br>This command is using the parameters feature of Argo CD that allows you to override any Argo CD application with your own custom properties. Even <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/parameters/">the official documentation</a> has a huge warning about not using this feature as it goes against the GitOps principles.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sPC5erwai2C6goPU.png" /></figure><p>Even if you save the parameter information in the Application manifest, you have now completely destroyed local testing for developers (see anti-patterns 6 and 17).</p><h3>Anti-pattern 4 — Adopting Argo CD without understanding Helm</h3><p>Helm is the package manager for Kubernetes. In its original form it offers several essential features in a single platform:</p><ul><li>A package format for Kubernetes manifests</li><li>A repository specification for storing Helm packages in artifact managers</li><li>A templating system</li><li>A comprehensive CLI</li><li>A lifecycle definition (upgrade, install, rollback, test )</li></ul><p>Argo CD renders all Helm charts using the<a href="https://helm.sh/docs/helm/helm_template/"> template command</a> and completely discards most other lifecycle features. Even though in theory this is a good thing as Argo CD can replace the default Helm lifecycle (e.g. Argo CD comes with its own <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd_app_rollback/">rollback command</a>), Argo CD assumes that you already know how <a href="https://helm.sh/docs/chart_best_practices/templates/">Helm templates work</a>.</p><p>If you are adopting Argo CD and want to use Helm, then make sure that you know how Helm works on its own and how all your applications can be deployed in all your different environments <strong>WITHOUT </strong>Argo CD. Trying to learn Argo CD and Helm together at the same time is a recipe for failure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*uMdRxY9IIpzzv_H1.png" /></figure><p>At the very least you should know how to create Helm hierarchies of values</p><pre>common-values.yaml<br>+-----all-prod-envs.yaml<br>   +----specific-prod-cluster.yaml</pre><p>And how Helm works when loading the hierarchy with the correct overrides</p><pre>helm install ./my-chart/ — generate-name -f common.yaml -f more.yaml -f some-more.yaml</pre><p>If you use Helm umbrella charts, understand how to <a href="https://helm.sh/docs/chart_template_guide/subcharts_and_globals/">override child values from the top chart</a>.</p><p>In particular, pay attention to the following:</p><pre>restaurant:<br> menu: vegetarian</pre><p>This can be a simple value setting for a Helm chart that sets the “restaurant.menu” value to “vegetarian”. It can also be an umbrella chart which has a dependency subchart called “restaurant” which itself has a property called “menu”. Understand why those two approaches are different and the advantages and disadvantages of each.<br>We have already written a complete guide on how to use <a href="https://codefresh.io/blog/helm-values-argocd/">Argo CD with Helm value hierarchies</a>.</p><h3>Anti-pattern 5 — Adopting Argo CD without understanding Kustomize</h3><p>This is the same anti-pattern as the previous one but for Kustomize users. Kustomize is a powerful tool and includes several features such as</p><ul><li>Overlays for different environments</li><li>Reusable <a href="https://kubectl.docs.kubernetes.io/guides/config_management/components/">component </a>configurations</li><li>Common globals (labels, annotations, namespace prefixes)</li><li><a href="https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/configmapgenerator/">Configuration generators</a></li><li>Transformers/<a href="https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/replacements/">Replacements</a></li><li>Remote resources</li></ul><p>Argo CD can reuse all Kustomize features, provided that you have structured your Kustomize files correctly first.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_S0Z7mVelZfIL2-F.png" /></figure><p>Again make sure that your Kustomize files work on their own BEFORE bringing Argo CD into the picture. Well structured Kustomize applications are self-contained. Any developer should be able to use the kustomize (or kubectl) command to extract the configuration for an existing environment without the need for Argo CD.<br>We presented a <a href="https://github.com/kostis-codefresh/gitops-environment-promotion">full example for Kustomize configurations</a> in our <a href="https://codefresh.io/blog/how-to-model-your-gitops-environments-and-promote-releases-between-them/">Argo CD promotion guide</a>.</p><h3>Anti-pattern 6 — Assuming that developers need to know about Argo CD</h3><p>This is the corollary to the previous two antipatterns. Several teams mix Argo CD configuration data with Kubernetes configuration making the life of developers extremely difficult.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Ci2s2t4YLfLEY1Rb.png" /></figure><p>This is a big problem for developers as one of their most important tasks is to run an application locally both during development and when trying to pinpoint difficult bugs in isolation. Creating Argo CD configurations and coupling them with Kubernetes manifests prevents them from understanding how an application runs independently.</p><p>We will see more specific anti-patterns later that also contribute to this problem but it is best to know about this trap in advance when you start working with Argo CD.</p><p>Remember also that developers don’t care about Kubernetes manifests. They only care about source code features. It is one thing to ask them to learn the basics (i.e. Helm values) and a completely different thing to require Argo CD knowledge just to be able to recreate the configuration of an existing environment.</p><p>When you design your Argo CD repository, you should always consider a developer persona who is an expert on Kubernetes but <strong>knows nothing about Argo CD</strong>. Can they recreate any configuration of any application on their laptop without using Argo CD? If the answer is no, it means that you are the victim of this anti-pattern.</p><p>Find out where you have hardcoded Argo CD configurations with Kubernetes configurations and remove the tight coupling. For specific advice see anti-patterns 17 for Helm and 18 for Kustomize.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7Jt5knfDv88m9cj6.png" /></figure><p>We have seen how to split Kubernetes configuration from Argo CD manifests in our <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">Application Set guide</a>.</p><h3>Anti-pattern 7 — Grouping applications at the wrong abstraction level</h3><p>As we explained in the first anti-pattern (store everything in Git), an Argo CD application is just a link between a Git repository and a Kubernetes cluster. It is not a deployment artifact or a packaging format.</p><p>We have seen teams that abuse an Argo CD application as a generic grouping mechanism, using it for microservices or even completely unrelated applications.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hv213iyAAtvohx6h.png" /></figure><p>You need to spend some time understanding what your applications do and how tightly coupled they are. If you have a set of “micro-services” that are always deployed together and upgraded together, you might <a href="https://helm.sh/docs/howto/charts_tips_and_tricks/#complex-charts-with-many-dependencies">want to use an umbrella chart</a> for them.</p><p>Argo CD applications should generally model something that requires individual deployments and updates. If you have several Argo CD applications that you always want to be managed together (but still want to deploy and update individually), then a better choice might <a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/cluster-bootstrapping/#app-of-apps-pattern">be the app-of-apps pattern</a>.</p><p>If several applications need to be deployed to different or similar configurations, then <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/application-set/">Application Sets</a> are the proper recommendation.</p><p>So any time you want to group several applications, ask the following questions:</p><ol><li>Are those applications always deployed and upgraded as a single unit?</li><li>Are those applications related in a business or technical manner?</li><li>Do you want to use different configurations for different clusters for these applications?</li><li>Is this combination of applications always the same? Do you sometimes wish to deploy a subset of them or a superset?</li><li>Are these applications managed by a single team or multiple teams?</li></ol><p>If you are unsure where to start, <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">looking at Application Sets</a> is always the best choice. Check also anti-pattern 19 (attempting to version application CRDs).</p><h3>Anti-pattern 8 — Abusing the multi-source feature of Argo CD</h3><p>This is a close relative of the previous anti-pattern. The <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/multiple_sources/">Multi-source feature</a> of Argo CD was one of the most requested features in the project’s history. The feature was created to solve a single scenario:</p><ol><li>You wish to use an external Helm chart that is not hosted by your organization</li><li>You want to use your own Helm values and still store them in your own Gitrepository</li><li>You need a way to instruct Argo CD to combine the external Helm chart with your own values.</li></ol><p>The feature is finally implemented in Argo CD and you can finally do the following:</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>spec:<br>  sources:<br>  - repoURL: &#39;https://prometheus-community.github.io/helm-charts&#39;<br>    chart: prometheus<br>    targetRevision: 15.7.1<br>    helm:<br>      valueFiles:<br>      - $values/charts/prometheus/values.yaml<br>  - repoURL: &#39;https://git.example.com/org/value-files.git&#39;<br>    targetRevision: dev<br>    ref: values</pre><p>Unfortunately, Argo CD does not limit the number of items you can place in the “sources” array. Several people have misunderstood this, abusing the feature to group multiple (often unrelated) applications.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*uF0YeX4TODapytG8.png" /></figure><p>Don’t fall into this trap. The multi-source feature was never designed to work this way and several standard Argo CD capabilities will either be broken or not work at all if you use multi-sources as a generic application grouping mechanism.<br>The correct way to group applications is with Application Sets. If you want to use multisource applications with Helm hierarchies, <a href="https://codefresh.io/blog/helm-values-argocd/">we have also written an extensive guide</a>.</p><h3>Anti-pattern 9 — Not splitting the different Git repositories</h3><p>If you look at any Kubernetes application from a high level it is comprised of 3 distinct types of files</p><ol><li>The source code</li><li>Kubernetes manifests (deployment, service, ingress etc)</li><li>Argo CD application manifests (or application sets)</li></ol><p>We have already explained that as a <a href="https://codefresh.io/blog/argo-cd-best-practices/">best practice the source code should be separate</a> than the manifests. If you are in a big organization it might also make sense to split Kubernetes manifests from Argo CD manifests.</p><p>If you keep all manifests in a single Git repository you will have issues with both CI (Continuous Integration) and CD (Continuous Deployment) phases. Your CI system will try to auto-build application code when a manifest changes, and Argo CD will try to sync applications when a developer changes the source code.</p><p>There are several workarounds for these scenarios, but why try to solve a problem that shouldn’t exist in the first place?</p><p>We have also seen several variations of the same pattern that make the deployment process even more complicated. A classic scenario to avoid is the following</p><ol><li>The source code of the application is in Git repository A</li><li>The Helm manifest is in Git repository B</li><li>Only the Helm values for the different environments are also stored in Git repository A</li></ol><p>The assumption here is that Helm values are close to the source code that developers need to change. In reality, it never makes sense to have access to Helm values without also having access to the Helm chart that uses them. Either assume that developers know about Helm and show them everything, or assume that they don’t care about Kubernetes at all and offer them a different abstraction that hides Argo CD completely for them.</p><p>Yet another problem is mixing Kubernetes manifests and Argo CD manifests in the same repository but instead of using different folders, you hardcode Kubernetes information into Argo CD information. This is described in detail in anti-patterns 17 and 18.</p><h3>Anti-pattern 10 — Disabling auto-sync and self-heal</h3><p>Several times, migrating to Argo CD is a big undertaking, especially for organizations that have invested a significant amount of effort in traditional pipelines. After all, it can be argued that Argo CD simply replicates what is already possible with an existing Continuous Integration system:</p><ol><li>A change happens in Git in a manifest</li><li>A separate process picks up the Git event</li><li>The process uses kubectl (or a custom script) to apply the changes in the cluster.</li></ol><p>This could not be further from the truth as Argo CD also works the other way around. It monitors changes in the cluster and compares them against what is in Git. Then you can make a choice and either review those changes or discard them altogether.</p><p>This means that Argo CD <a href="https://www.cncf.io/blog/2020/12/17/solving-configuration-drift-using-gitops-with-argo-cd/">solves the configuration drift problem once and for all</a>, something that is <strong>not </strong>possible with traditional CI solutions.</p><p>But this advantage only exists if you let Argo CD do its job. Some organizations disable the <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/#automatic-self-healing">auto-sync/self-heal</a> behavior in Argo CD in an effort to “lock-down” or fully control production systems.</p><p>This is a bad choice because production systems are exactly the kind of systems where you want to avoid configuration drift. Manual changes that happen in production (during hotfixes or other debugging sessions) are one of the biggest factors affecting failed deployments.</p><p>We recommend that you have auto-sync/self-heal for all your systems both production and non-production.</p><p>Locking down a system should not be done in Argo CD itself, but enforced on the cluster and Git level. The most obvious solution is to reject any direct commits in a Git repository that controls your production system and only allow developers to create Pull Requests which must pass several manual and automated checks before landing in the mainline branch that Argo CD monitors.</p><p>If you disable auto-sync/self-heal you are missing the number one advantage of moving to Argo CD from traditional pipelines (eliminating configuration drift).</p><h3>Anti-pattern 11 — Abusing the targetRevision field</h3><p>Promoting applications when adopting Argo CD is one of the biggest challenges for developer workloads. People see the <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/tracking_strategies/">targetRevision field</a> in the Argo CD application and assume it is a promotion mechanism. <br>The first issue is when teams use semantic version ranges to force Argo CD to update an application automatically to a newer version. The second issue is if they continuously update the targetRevision field to <a href="https://codefresh.io/blog/stop-using-branches-deploying-different-gitops-environments/">different branch names</a> or attempt to implement “<a href="https://codefresh.io/blog/creating-temporary-preview-environments-based-pull-requests-argo-cd-codefresh/">preview environments</a>” by pointing an Argo CD application to a temporary developer/feature branch.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*oW59ldjt_ksS0Mfi.png" /></figure><p>We have written <a href="https://codefresh.io/blog/argocd-application-target-revision-field/">a complete guide about the issues of abusing targetRevision</a>.</p><p>In general, we recommend you always use HEAD in the targetRevision field which is also the default value.</p><h3>Anti-pattern 12 — Misunderstanding immutability for container/git tags and Helm charts</h3><p>This is not an anti-pattern with Argo CD per se, but it is closely related to the targetRevision choices, as explained in the previous anti-pattern.</p><p>We have seen several cases of people adopting Argo CD without first understanding the foundations (Helm, container registries, git tags). Several times, people use a specific git tag or Helm version in Argo CD without realizing that:</p><ol><li>Git tags seem to be immutable, but can be deleted and recreated with the same name</li><li>Helm chart versions are mutable. This is how Helm was designed</li><li>Container tags are mutable by default.</li></ol><p>Let’s take these points one by one.</p><p>Container tags are mutable. You can create a container called my-app:v1.2 and then change something and push another container with the same tag. So just because you see the same container tag doesn’t mean that it is actually the same application. Some binary repository implementations don’t allow you to do this, but this is not always the default setting.</p><p>Helm chart versions are also mutable. You can change the contents of a Chart version and use the exact same version. Again, this is how Helm charts are created.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Cg1jho6Pc84XNBuG.png" /></figure><p>In fact Helm offers an additional property — the appVersion which can store the “application” version. So you can have a Helm chart with 3 “version” fields:</p><ul><li>The container image (mutable by default)</li><li>The appVersion field (mutable)</li><li>The Chart version (mutable)</li></ul><p>So unless you control how developers work with code and manifests and also configure your Helm chart repository correctly, you don’t really know if a Helm chart version contains the same thing as another chart with the same version.</p><p>Git tags can also be overwritten. You can see this very easily with any GitHub repo with default settings.</p><pre>git tag -a v1.2 -m &quot;first tag&quot;<br>git push --tags<br>echo &quot;A change&quot; &gt;&gt; README.md<br>git tag -d v1.2<br>git push origin :v1.2<br>git tag -a v1.2 -m &quot;first tag&quot;<br>git push --tags</pre><p>Here we just pushed the same tag (v1.2) twice but with different contents. So if you were using this tag in the targetRevision field of Argo CD, you now have the “same” application without actually having the same contents.</p><p>The end result is that using tags and Helm chart versions in Argo CD doesn’t really restrict your developers unless you actively set up the rest of the ecosystem (Git repositories, Helm repos and binary artifact managers) to also work with immutable data.</p><p>Never assume you have a “locked-down” Argo CD system when the rest of the ecosystem allows developers and operators to create stuff with the same container/Helm/Git version.</p><h3>Anti-pattern 13 — Giving too much power (or no power at all) to developers</h3><p>When adopting Argo CD you need to make a decision about how much power and exposure you want to give to developers. On one end of the spectrum we see installations where developers have full access to the Argo CD UI and can sync/deploy their applications at will. On the other end we see locked down installations where developers are given very little power or Argo CD is completely hidden from them.</p><p>It is important to understand that despite these two extremes, there are several choices in the middle. First, you can configure the Argo CD access (and UI) to show only applications specific to each team. You can even do advanced scenarios where you show applications from other teams but in a read-only mode.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*02LlGEg6szP1rzTS.png" /></figure><p><a href="https://codefresh.io/blog/multi-tenant-argocd-with-application-projects/">In our Argo CD RBAC guide</a>, we have explained how the RBAC for Argo CD works and how you can show content specific only to a developer team.</p><p>There is no right or wrong answer here, but you need to balance the flexibility versus the security that you want to offer to developers.</p><p>On a related note we have already explained that developers <a href="https://codefresh.io/blog/why-environments-beat-clusters-for-dev-experience/">don’t really care about Argo CD manifests</a> and they shouldn’t also be forced to install Argo CD for local testing (see anti-pattern 6) .</p><p>So a recommended workflow would be the following:</p><ol><li>Developers can test and deploy their applications locally without using Argo CD at all</li><li>When they have created a feature branch this should <a href="https://codefresh.io/blog/creating-temporary-preview-environments-based-pull-requests-argo-cd-codefresh/">be converted to a preview/temporary environment using the Pull Request generator</a> without any human intervention</li><li>Once the feature is ready, it will be deployed to production simply by merging the Pull request</li><li>A system designed for promotion should propagate the changes to the next environment (check anti-pattern 30).</li></ol><p>Ideally developers should only come in contact with Argo CD in the last phase and only in the case of a failure. In the happy path scenario where everything works as planned, developers shouldn’t have to debug anything with Argo CD.</p><h3>Anti-pattern 14 — Referencing dynamic information from Argo CD/ Kubernetes manifests</h3><p>This is a more specialized anti-pattern related to number 2 (creating applications in a dynamic way). The second <a href="https://opengitops.dev/">GitOps principle</a> explains that the desired state of your system should be immutable, versionable and auditable.</p><p>This is only true if you store <strong>EVERYTHING </strong>that the application needs in Git (or your chosen storage method). In the case of Kubernetes/Argo CD manifests, this means that all values used should be static and known in advance.</p><p>It is ok if you want to post-process your manifests in some way as long as this happens in a repeatable manner OR you also save the result itself in Git.</p><p>The problem starts when your configuration is not known in advance but requires real-time access to something else.</p><p>The best example to illustrate this is the <a href="https://github.com/argoproj/argo-cd/issues/5202">Helm lookup method</a> which mutates the Helm chart to a different value without knowing in advance what this value is. This is problematic with Argo CD because having access to just the application manifests is not enough to run the application anymore.</p><p>Like anti-patterns 17 and 20 this also makes lives difficult for developers as they cannot run the application locally anymore (anti-pattern 6).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-QNrpZi1UGUA6KQI.png" /></figure><p>Note that the only exception to this rule is secrets. Even though you<a href="https://codefresh.io/blog/handle-secrets-like-pro-using-gitops/"> can store encrypted secrets in Git</a>, it is also ok <a href="https://codefresh.io/blog/gitops-secrets-with-argo-cd-hashicorp-vault-and-the-external-secret-operator/">to reference them from an external source</a>.<br>But make sure to understand <a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/secret-management/">the difference between referencing secrets from manifests versus injecting secrets</a> into manifests.</p><h3>Anti-pattern 15 — Writing applications instead of Application Sets</h3><p>The Application CRD is the main entity in Argo CD that links a cluster and a GitHub repository. If you have a small number of applications or work in a homelab environment it is OK to write these files by hand.</p><p>But for any production installation of Argo CD (used in a company) you generally wouldn’t need to write Application files by hand. In fact you should not even deal with Application files at all.</p><p>The recommendation is <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/application-set/">to use directly Application Sets</a>.</p><p>In a big organization you will rarely have to deal with a single application on its own. Almost always you want to work with a group of applications. Some examples are:</p><ul><li>A set of applications that go to the same cluster</li><li>A set of applications that are managed by the same team</li><li>A set of applications that share a configuration</li><li>A set of applications that should be deployed/updated as a unit.</li></ul><p>Application Sets implement this grouping and also automate the tedious YAML needed. For example if you have 4 applications and 10 clusters you don’t really want to create 40 YAML files by hand.</p><ol><li>You create a single cluster generator that iterates over your clusters</li><li>You create 4 folders for the 4 applications</li><li>The Application set runs and creates automatically all 40 combinations for you.</li></ol><p>Some people resist using Application Sets because they think it is yet another abstraction that hides their real Application manifests.</p><p>This used to be true, but in the latest Argo CD releases <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd_appset_generate/">the CLI allows you to render an application set </a>and see exactly what applications will be created. You can run this command either manually or in a CI pipeline (when a pull request is created) so that you have instant visibility on what will change.</p><p>In general though, as we will see later all application set files should be created only once.They are not a deployment format (anti-pattern 19) and you shouldn’t have to edit ApplicationSets for simple operations.<br>We have written a dedicated guide on <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">how to use Application Sets with Argo CD</a>.</p><h3>Anti-pattern 16 — Using Helm to package Applications instead of Application Sets</h3><p>This anti-pattern is the so called “Helm sandwich”. This is the case where:</p><ol><li>A set of Kubernetes manifests is packaged as a Helm chart</li><li>The Helm chart is referenced from an Argo CD application Manifest</li><li>The Argo CD application manifest itself is packaged in another Helm chart</li></ol><p>Essentially, there are two instances of Helm templating on two different levels.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*90gE42FujUAK-Lao.png" /></figure><p>Teams that adopt this anti-pattern are familiar with Helm and assume that if it works great for plain Kubernetes manifests, it would also work great for Argo CD application manifests.</p><p>So why is this approach an anti-pattern? Using Helm for applications is not an issue on its own, but it is the start for several other anti-patterns:</p><ol><li>Because Helm charts have a version people try to version Applications -&gt; Anti-pattern 19</li><li>Chart version numbers often result in abusing target Revision for promotions -&gt; Anti-pattern 11</li><li>If you use Helm everywhere it is super easy to hard-code Helm values in Application manifests -&gt; Anti-pattern 17</li><li>People miss all the Argo CD specific features of application sets -&gt; Anti-pattern 15</li><li>Distributing applications to different clusters happens with snowflake servers instead of the cluster generator -&gt; Anti-pattern 21</li></ol><p>The biggest problem however is that it ruins again completely the developer experience. You now have 2 levels of Helm values and this is a recipe for disaster:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eSJcn9jtTBhJD2_L.png" /></figure><p>Even speaking for just operators/admins, packaging applications in a Helm chart creates an extra level of indirection that not only is not needed but makes your life more difficult as <a href="https://github.com/argoproj/argo-cd/issues/15007">Argo CD was never created for this “Helm sandwich”</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_xM4--ExvT1fISI-.png" /></figure><p>Our recommendation is obviously to use Application Sets for configuring Applications, as already explained in the previous anti-pattern.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*igV2vA39N4tnQC5k.png" /></figure><p>Application Sets are the preferred way to generate Applications and they support <a href="https://masterminds.github.io/sprig/">the exact same templating functions</a> as Helm charts. Basically if you can template it with Helm, you should be able to template it with Application Sets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jsjc3DsS4ZwnZWbL.png" /></figure><p>Using the “Helm sandwich” pattern makes the process more complex for everybody involved in the software lifecycle.<br>For more information, see our <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">Application Set guide</a>.</p><h3>Anti-pattern 17 — Hardcoding Helm data inside Argo CD applications</h3><p>If you follow the advice we outlined in Antipattern 4 you should have a clear separation between your Helm charts and your Argo CD manifests.</p><p>The Helm charts can be used independently (even by developers) and contain all application settings. The Argo CD application manifest simply defines where this application runs, and only operators need to change this file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*vI8astrPocewHwQ-.png" /></figure><p>Those two kinds of files also have a different lifecycle</p><ul><li>Helm values are expected to change all the time (even by developers)</li><li>Argo CD application manifests are created once and never changed again.</li></ul><p>With Argo CD it is possible to hardcode Helm information inside an Application CRD. But just because you can do this, doesn’t mean it is a good idea to use this feature.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>metadata:<br>  name: my-helm-override<br>  namespace: argocd<br>spec:<br>  project: default<br><br><br>  source:<br>    repoURL: https://github.com/example-org/example-repo.git  <br>    targetRevision: HEAD  <br>    path: my-chart  <br><br><br>    helm:<br>      # DONT DO THIS<br>      parameters:<br>      - name: &quot;my-example-setting-1&quot;<br>        value: my-value1<br>      - name: &quot;my-example-setting-2&quot;<br>        value: &quot;my-value2&quot;<br>        forceString: true # ensures that value is treated as a string<br><br><br>      # DONT DO THIS<br>      values: |<br>        ingress:<br>          enabled: true<br>          path: /<br>          hosts:<br>            - mydomain.example.com<br><br><br>      # DONT DO THIS<br>      valuesObject:<br>        image:<br>          repository: docker.io/example/my-app<br>          tag: 0.1<br>          pullPolicy: IfNotPresent<br>  destination:<br>    server: https://kubernetes.default.svc<br>    namespace: my-app</pre><p>The big problem with this file is that you are mixing 2 different kinds of information with 2 different lifecycles. The application CRD is something that is interesting mostly to operators/administrators while Helm information is interesting to developers and is also expected to change a lot.</p><p>By mixing this information you make manifests harder to understand for everybody.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Ev5vgw7iIEN2HDlX.png" /></figure><p>Now you have Helm information in two places (helm values and the helm property inside the Argo CD application manifest). It is very hard to understand what settings exist where and how to audit deployment history. Argo CD application manifests now must also change all the time especially if they define container images.</p><p>This manifest mixing is also the root of several other anti-patterns such as</p><ul><li>Using Applications as a unit of work (anti-pattern 19)</li><li>Abusing the targetRevision field for promotions (anti-pattern 11)</li><li>Not understanding how Helm hierarchy works (anti-pattern 4)</li><li>Assuming that developers need Argo CD (anti-pattern 6)</li></ul><p>The last point is especially important for developers. If you follow this practice, you have completely destroyed local testing for developers as they cannot run the application on its own anymore.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NFrNGVQ2ytUSEbHs.png" /></figure><p>Even though we speak only about developers and operators here, several other scenarios will make this approach difficult to use</p><ol><li>Security teams will have a hard time understanding the settings for each application</li><li>Your CI system cannot upgrade images just on Kubernetes manifests anymore. It also needs to look at Argo CD manifests and check if image definitions exist there as well</li><li>It couples your Applications to specific Argo CD features</li></ol><p>The correct solution is of course to store all Helm information in Helm values</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>metadata:<br>  name: my-helm-override<br>  namespace: argocd<br>spec:<br>  project: default<br><br><br>  source:<br>    repoURL: https://github.com/example-org/example-repo.git  <br>    targetRevision: HEAD  <br>    path: my-chart  <br><br><br>    helm:<br>      ## DO THIS (values in Git on their own)<br>      valueFiles:<br>      - values-production.yaml<br>  destination:<br>    server: https://kubernetes.default.svc<br>    namespace: my-app</pre><p>Now there is a clear separation of concern between developers and operators.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4k20IcfVIs2ebBIg.png" /></figure><p>People who want to know applications’ settings can look at Helm values, while people who want to know where applications are deployed can look at Argo CD manifests. It is possible to run a Helm application without Argo CD.</p><p>For more information about the different types of manifests and how to split them see <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">our Application Set guide</a>.</p><p>This anti-pattern is even worse if coupled with the previous anti-pattern (the Helm sandwich).</p><p>Now you have 3 places where configuration settings can be stored:</p><ol><li>The Helm values of the chart that gets deployed</li><li>The helm property inside the Application Manifest that references the Helm chart</li><li>The Helm template that renders the Application manifest before getting passed to Argo CD</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IXC86MXWflrhj6ZZ.png" /></figure><p>The result is a nightmare for anybody who wants to understand how an application gets deployed.</p><h3>Anti-pattern 18 — Hardcoding Kustomize data inside Argo CD applications</h3><p>This is the same anti-pattern as the previous one but for Kustomize. Again for convenience Argo CD allows you to hardcode Kustomize information inside an Application YAML:</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>metadata:<br>  name: my-kustomize-override<br>  namespace: argocd<br>spec:<br>  source:<br>    repoURL: https://github.com/example-org/example-repo.git  <br>    targetRevision: HEAD  <br>    path: my-app  <br>   <br>    # DONT DO THIS<br>    kustomize:<br>      namePrefix: prod-<br>      images:<br>      - docker.io/example/my-app:0.2<br>      namespace: custom-namespace<br><br><br>  destination:<br>    server: https://kubernetes.default.svc<br>    namespace: my-app</pre><p>It is the same problem as before where you are mixing different concerns in a single file. Kustomize information should only exist in Kustomize overlays:</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>metadata:<br>  name: my-proper-kustomize-app<br>  namespace: argocd<br>spec:<br>  source:<br>    repoURL: https://github.com/example-org/example-repo.git  <br>    targetRevision: HEAD  <br>    ## DO THIS. Save all values in the Kustomize Overlay itself<br>    path: my-app/overlays/prod  <br><br><br>  destination:<br>    server: https://kubernetes.default.svc<br>    namespace: my-app</pre><p>As explained before this helps developers during local testing. A developer can simply run “kustomize build my-app/overlays/prod” and get the full configuration of how my-app runs in production. No knowledge of Argo CD is required and no local installation of Argo CD is needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OvLty5dGkaUod-IU.png" /></figure><p>Developers can define <em>how </em>an application runs (its settings), while operators can decide <em>where </em>(which cluster) the application is deployed.</p><p>At the same time, several supporting functions are very easy:</p><ul><li>Git history of the overlays is the same as the deployment history</li><li>There is only a single source of truth for configuration (the overlays)</li><li>Developers don’t need to know how to use Argo CD at all</li><li>It is very easy to diff settings between environments.</li></ul><p>A detailed example with Kustomize overlays <a href="https://codefresh.io/blog/how-to-model-your-gitops-environments-and-promote-releases-between-them/">is available in our GitOps promotion guide</a>.</p><h3>Anti-pattern 19 — Attempting to version and promote Applications/Application Sets</h3><p>An Argo CD application is just a link between a cluster and a Git repository. There is NO version field in the Application CRD. An application manifest is neither a packaging format nor a deployment artifact. The same is also true for Application Sets. You never <em>deploy </em>application sets. You just use them to auto-generate application manifests. Application Sets have no version on their own.</p><p>The lack of a version field is not a big problem because the expectation for both Applications and Application Sets is that you create them once and then never update them again. So, a version field is unnecessary as all Argo CD Applications are considered static.</p><p>However, we see several teams that try to use Applications as the unit of work (see anti-pattern 7) or continuously update those files in the targetRevision field (see anti-pattern 11).</p><p>At this point teams try to create their own versioning on top of the Argo CD manifests and of course they fail because Argo CD was never designed this way.</p><p>The same is true for promotions. You cannot really “promote” an Argo CD application from one cluster to the next. It doesn’t work this way. You can only promote values that are referenced from one application to the next (essentially copying them).</p><p>The way promotions work in Argo CD is the following:</p><ol><li>There is an Argo CD application manifest in QA that points to a Helm chart or Kustomize overlay</li><li>There is an Argo CD application manifest in Staging that points to different Helm values or Kustomize overlays</li><li>When it is time to promote you <em>copy </em>the Helm values or Kustomize overlay from the QA files to the Staging files.</li><li>The Argo CD application manifests are not affected in any way. They are exactly the same as they were before.</li></ol><p>See also the related anti-patterns of hardcoding Helm (anti-pattern 17) or Kustomize data (anti-pattern 18) inside applications.</p><p>If you find yourself constantly updating Argo CD application manifests (or Application Sets) you have fallen into this trap. Your Argo CD manifests should be created only once and never touched again. No process is simpler than not needing a process at all (to update Application manifests).</p><p>On a related note, we have created <a href="https://codefresh.io/blog/introducing-the-worlds-first-dashboard-for-gitops-environments/">GitOps Cloud</a> to solve this problem with promotions and allow you to promote applications from one cluster to another.</p><h3>Anti-pattern 20 — Not understanding what changes are applied to a cluster</h3><p>One of the main benefits of using Argo CD is that all your Git tools work out of the box and you can reuse your code review process for your Kubernetes manifests.</p><p>The most basic capability of storing anything in GitHub is creating a Pull Request before merging any changes. This allows humans to review what will change and also run any automated tools to verify and validate the changes.</p><p>Unfortunately this review process will not really work on files such as Helm charts or Application Sets or Kustomize Overlays. Let’s assume you need to review the following change:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*R05945Mitzu0vIwY.png" /></figure><p>You need to manually run Helm in your head to understand what is happening here. Humans never want to do that. Obviously one approach would be to pre-render all your manifests so that reviews only happen in the final content. There are however several alternatives that you can consider such as having your CI system render on the fly the manifests and show you what will actually happen.</p><p>Here is the exact change as before but this time on the final chart.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1020/0*LkS1MJWv8I6lWwo0.png" /></figure><p>We have written a<a href="https://codefresh.io/blog/argo-cd-preview-diff/"> full guide on how to preview and diff your Kubernetes manifests</a> with several other approaches.<br>Specifically for Argo CD you should also check <a href="https://github.com/dag-andersen/argocd-diff-preview">https://github.com/dag-andersen/argocd-diff-preview</a> and also understand that you can use the <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/commands/argocd_appset_generate/">Argo CD CLI to render your Application Sets to Application CRDs</a>.</p><h3>Anti-pattern 21 — Using ad-hoc clusters instead of cluster labels</h3><p>If you have a large number of clusters that you wish to manage with Argo CD, your first question is always <a href="https://codefresh.io/learn/argo-cd/a-comprehensive-overview-of-argo-cd-architectures-2025/">whether to use a single Argo CD instance or multiple ones</a>.</p><p>Once you answer this question the next step is to understand how you can distribute different applications to different clusters. The answer for this question is Application Sets.</p><p>Unfortunately we see a lot of teams that don’t understand <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Generators-Cluster/">how the cluster generator works</a> and instead try to create ad-hoc cluster configurations (the pet vs cattle philosophy).</p><p>Examples to avoid often appear like this:</p><pre>## DO NOT DO THIS <br>- merge:<br>      mergeKeys:<br>        - app<br>      generators:<br>        - list:<br>            elements:<br>              - app: external-dns<br>                appPath: infra/helm-charts/external-dns<br>                namespace: dns<br>              - app: argocd<br>                appPath: infra/helm-charts/argocd<br>                namespace: argocd<br>              - app: external-secrets<br>                appPath: infra/helm-charts/external-secrets<br>                namespace: external-secrets<br>              - app: kyverno<br>                appPath: infra/helm-charts/kyverno<br>                namespace: kyverno<br>        - list:<br>            elements:<br>              - app: external-dns<br>                enabled: &quot;true&quot;<br>              - app: argocd<br>                enabled: &quot;true&quot;<br>              - app: external-secrets<br>                enabled: &quot;false&quot;<br>              - app: kyverno<br>                enabled: &quot;true&quot;<br>    selector:<br>      matchLabels:<br>        enabled: &quot;true&quot;</pre><p>Or this</p><pre>## DO NOT DO THIS <br>Version: argoproj.io/v1alpha1<br>kind: ApplicationSet<br>metadata:<br>  name: my-staging-cluster<br>  namespace: argocd<br>spec:<br>  goTemplate: true<br>  generators:<br>    - matrix:<br>        generators:<br>        - git:<br>            repoURL: &#39;&lt;url&gt;&#39;<br>            revision: HEAD<br>            files:<br>              - path: customConfig/base.yaml<br>              - path: customConfig/{{ .Values.domainId }}/*.yaml<br>        - list:<br>            elements:<br>              - appName: &#39;auth&#39;<br>              - appName: &#39;search&#39;<br>              - appName: ‘billing’<br>              - appName: ‘payments’</pre><p>These kind of configurations create snowflake servers that need constant maintenance. If you have fallen into this trap try to understand how much time you will need to spend in the following scenarios:</p><ul><li>Creating a brand new server</li><li>Migrating an application from one server to another</li><li>Applying a global setting to all your servers</li><li>Making a different configuration change for a subset of your servers.</li></ul><p>This kind of cluster management is even more problematic for developers, as simply understanding what applications are deployed where is not easy (already explained in anti-pattern 6).</p><p>Our recommendation is to create a production-ready setup using cluster labels. We have described all the details in a <a href="https://codefresh.io/blog/argocd-clusters-labels-with-apps/">dedicated guide for the cluster generator and Argo CD application sets</a>.</p><h3>Anti-pattern 22 — Attempting to use a single application set for everything</h3><p>A related anti-pattern is when teams discover application sets and, for some unknown reason, try to cram all their applications into a single application set. This often results in a complex mix of different generators that is hard to understand and hard to debug.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4Ex-j7IXLMsvUz2Q.png" /></figure><p>The recommendation is to have many different application sets in your Argo CD setup. Ideally, you should have different application sets per “type” of application. This type can be anything that makes sense to you. For example:</p><ul><li>An application set for all staging apps</li><li>An application set for all AWS clusters</li><li>An application set for all infra apps</li><li>An application set for the billing team</li><li>An application set for the payments team</li></ul><p>You can slice and dice your applications in different dimensions. But in the end, you will have many application sets, and any time you make a change, you should instantly know <em>where </em>it should happen.</p><ul><li>A new requirement is that all AWS clusters need to get sealed secrets -&gt; Change the AWS application set.</li><li>A new requirement is that the billing team add a new microservice to their setup -&gt; Change the “billing” application set.</li><li>All new clusters must upgrade Prometheus -&gt; Change the “common” application set.</li></ul><p>There is no technical limitation on the number of application sets you can have on a single Argo CD installation. So, spend some time understanding your application sets accordingly.</p><p>As always, <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">our Argo CD application guide</a> is the best starting point.</p><h3>Anti-pattern 23 — Using Pre-sync hooks for db migrations</h3><p><a href="https://argo-cd.readthedocs.io/en/latest/user-guide/sync-waves/">Argo CD phases/waves</a> allow you to define the order of Resources synced within a single Argo CD application. The pre-sync phase, in particular, can be used to check deployment requirements or perform other checks that need to happen before the main sync phase.</p><p>We often see organizations attempting to use pre-sync hooks for database migrations. The assumption here is that the database schema must be updated just before the new application version. Unfortunately, most organizations use legacy DB migration tools, and almost always, they package a DB migration CLI tool in the pre-sync phase, <a href="https://codefresh.io/blog/database-migrations-in-the-era-of-kubernetes-microservices/">which is not the proper approach for Kubernetes applications</a>.</p><p>Using pre-sync hooks for database migrations has several issues. The most important one is that the DB CLI tool is just a black box for Argo CD. The CLI runs and never reports back to Argo CD what really happened with the database. This can leave the DB in an inconsistent state where the main sync phase will always fail.</p><p>The second problem is that Argo CD is based on continuous reconciliation, where an application might be synced for several reasons and in different time frames. Unfortunately the traditional DB CLI tools are rarely created with this scenario in mind. Most times they assume they run inside a typical CI pipeline only once.</p><p>At this point, Argo CD users are looking for several hacks to force the pre-sync hook to run ONLY in the initial application deployment and not in any subsequent sync events, as this either slows down the deployment or breaks the database completely.</p><p>The correct approach is to use a Database migration operator built specifically for Kubernetes. We <a href="https://codefresh.io/blog/using-gitops-for-databases/">have written a full guide</a> using the <a href="https://atlasgo.io/">AtlasGo DB operator</a>.</p><h3>Anti-pattern 24 — Mixing Infrastructure apps with developer workloads</h3><p>We have seen in anti-pattern 7 several ways to group applications with GitOps (applicationsets, apps-of-apps, Helm umbrella charts).</p><p>These grouping methods should always be used for the same types of applications. They should group either infra applications (core-dns, nginx, prometheus) OR the applications that developers create.</p><p>You should never mix applications of different types as you force developers to deal with infrastructure errors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KHiK1LYgllNT2VtP.png" /></figure><p>When you create a new cluster and hand it over to developers, it should already have everything they need. The respective application sets should also have installed any infrastructure applications.</p><p>Mixing both infrastructure and developer applications might be easier for you (to control directly the deployment order) but always results in a bad user experience for developers.</p><p>If developers have access to the Argo CD UI and see a deployment error, they should know immediately that it is something they can fix themselves.</p><h3>Anti-pattern 25 — Misusing Argo CD finalizers</h3><p><a href="https://argo-cd.readthedocs.io/en/stable/user-guide/app_deletion/">Argo CD finalizers</a> allow you to define what happens when an Argo CD application (or application set) is removed. You must understand how finalizers work and the impact of adding/removing a finalizer from a resource.</p><p>Several teams have accidentally deleted one or more Argo CD applications because they never understood how finalizers work. Other times several resources are “stuck” and never recreated because of a misconfiguration with finalizers.</p><p>Finalizers are also very useful when you want to migrate applications from one Argo CD instance to another.<br>We have written <a href="https://codefresh.io/blog/argocd-application-deletion-finalizers/">a comprehensive guide about Argo CD finalizers</a> and how to use them.</p><h3>Anti-pattern 26 — Not understanding resource tracking</h3><p>This anti-pattern is related to the previous one. First of all it is important to understand how Argo CD tracks and “adopts” Kubernetes resources. You can have Kubernetes resources that are not managed by Argo CD, or Argo CD resources that “owned” by other Kubernetes controllers.</p><p>It is vital to know that the relationship between a Kubernetes resource and the Argo CD application that owns it is not always 1–1. You can have</p><ol><li>Argo CD applications that no longer contain any Kubernetes resources</li><li>Kubernetes resources that are no longer owned by an Argo CD application</li></ol><p>The second scenario is achieved with finalizers. This pattern is very useful when you want to migrate applications from one Argo CD instance to another without downtime. The full process is the following:</p><ol><li>Argo CD instance A owns all Kubernetes resources</li><li>You remove all finalizers for all applications (and application Sets)</li><li>You delete all Argo CD applications</li><li>The Kubernetes resources <strong>are still running</strong> just fine. There is no downtime</li><li>You apply the same Argo CD applications to Argo CD instance B</li><li>Argo CD instance B will adopt the same Kubernetes resources as before (with no downtime)</li></ol><p>You can try this exact scenario of moving Argo CD applications between instances without downtime in our <a href="https://learning.codefresh.io/">Gitops Certification (level 3) course</a>.</p><p>The exact process can be used to upgrade an existing Argo CD instance to a new version in the safest way possible.</p><h3>Anti-pattern 27 — Creating “active-active” installations of Argo CD</h3><p>This is the corollary to the previous two anti-patterns. We see several several teams that try to create “active-active” installations of Argo CD with the following requirement:</p><ol><li>The main Argo CD instance is controlling all applications and deployments</li><li>There is a secondary Argo CD instance that is also pointed to the same cluster</li><li>If the main Argo CD instance “fails” the secondary instance “jumps-in”</li><li>When the main Argo CD instance is restored it “adopts” again all applications.</li></ol><p>These teams are disappointed to learn that Argo CD doesn’t support this and even t<a href="https://codefresh.io/learn/argo-cd/a-comprehensive-overview-of-argo-cd-architectures-2025/">he centralized mode is for controlling other Kubernetes clusters</a> and not other Argo CD instances.</p><p>This requirement doesn’t really make sense for Argo CD and teams that look for this “active-active” configuration haven’t really understood resource tracking.</p><p>First of all it is important to understand that Argo CD only deploys applications. It doesn’t really control them. If Argo CD fails, new deployments will stop but <strong>existing applications will continue to work just fine</strong>. And even if those fail for some reason, their pods will be rescheduled/restarted by the Kubernetes cluster (even if Argo CD is no longer operational).</p><p>The disaster recovery scenario for Argo CD is straightforward if your team has everything in Git (see antipattern 1). You can launch a second Argo CD cluster and point it to the same application manifests. Argo CD will then adopt the existing Kubernetes resources.</p><p>This is even possible if the central Argo CD instance has issues but still runs, as you can use finalizers (as explained in the previous section) to migrate applications to the second Argo CD instance without downtime.</p><p>Keeping a second Argo CD instance in “active-active” mode only wastes resources.</p><h3>Anti-pattern 28 — Recreating Argo Rollouts with Argo CD and duct tape</h3><p>Bad deployments always happen regardless of whether you are using Argo CD or not. That is a fact for any software team. So how do failed deployments work if you have adopted GitOps?</p><p>The simplest way to fix a failed deployment is to roll “forward”. Make a new release or fix the Kubernetes manifests and once you commit, Argo CD will deploy the new changes and hopefully bring back the application to a good state.</p><p>Argo CD also includes a “<a href="https://argo-cd.readthedocs.io/en/stable/user-guide/commands/argocd_app_rollback/">rollback</a>” command which simply points the application back to a previous Git hash. This sounds great in theory but it has 2 major problems:</p><ol><li>It works only if auto-sync is disabled (anti-pattern 10)</li><li>It breaks GitOps as your cluster doesn’t represent what is in Git anymore</li></ol><p>At this point, teams start creating custom solutions to overcome these limitations. The most common approaches we see are:</p><ol><li>Trying to detect a failed deployment, and then disable auto-sync on the fly while rolling back</li><li>Using notifications with external metric providers who will automatically try to revert a commit on their own, so Argo CD will sync as usual to the previous version</li></ol><p>These custom solutions are always clunky and create more problems than they solve. You know that your team is falling into this trap if you hear people always asking the question, “How can I disable auto-sync temporarily?”</p><p>The recommended solution is to use <a href="https://argoproj.github.io/rollouts/">Argo Rollouts</a>.</p><p>Argo Rollouts is a progressive Delivery controller designed for this exact scenario — automated rollbacks when things go wrong. It also comes with its own resource (<a href="https://argo-rollouts.readthedocs.io/en/stable/features/analysis/">Analysis</a>) that allows you to <a href="https://argo-rollouts.readthedocs.io/en/stable/best-practices/#prepare-your-metrics">look at your metrics </a>during a deployment and roll back without any human intervention.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/993/0*7N6uFWKPDnJ-VyTQ.png" /></figure><p>Argo Rollouts will handle production deployments while non-production environments can still use plain Argo CD.</p><h3>Anti-pattern 29 — Recreating Argo Workflows with Argo CD, sync-waves and duct tape</h3><p>The<a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/"> sync wave feature</a> of Argo CD allows you to execute tasks before and after the main sync phase. These tasks should ideally be idempotent and quick to finish. Some examples are:</p><ul><li>Sending a notification to another system</li><li>Performing a quick smoke test</li><li>Verifying that a dependency exists</li></ul><p>We see teams that misuse the sync waves in Argo CD with long-running tasks that are part of a bigger process with strict requirements such as</p><ul><li>Automatic retries</li><li>If/else control flows</li><li>Dependency graphs and fan-in/fan-out configuration</li><li>Artifact storage are retrieval</li></ul><p>Sync waves were <strong>NEVER </strong>designed for this kind of requirement. If you try to do this, you will soon resort to custom scripts that nobody wants to maintain. Adopting Argo CD for deployments and then trying to incorporate custom scripts in the sync process is a huge step backwards.</p><p>If you have this kind of process you should use <a href="https://argoproj.github.io/workflows/">Argo Workflows</a> which handle exactly these kinds of requirements.</p><p>Argo Workflows are Kubernetes native workflows that offer you all these features out of the box.</p><p>Therefore the whole sync process must be:</p><ol><li>Run an Argo Workflow before the sync process</li><li>Perform the main Sync phase</li><li>Run another Argo Workflow after the sync process.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*M2CV8lEAwfGviKHc.png" /></figure><p>Argo Workflows will then handle all the heavy lifting using declarative Kubernetes resources instead of custom scripts.</p><h3>Anti-pattern 30 — Abusing Argo CD as a full SDLC platform</h3><p>Despite all the features and the developer-friendly UI, Argo CD is very simple at its core. It is a powerful sync engine that continuously watches what you have in Git and applies the change to your cluster. All the features are centered around this use case.</p><p>However, deploying an application is only part of the software development life cycle (SDLC). Several other requirements must be met before (the CI process) and after (observability) the main deployment.</p><p>We have seen several teams trying to expand Argo CD’s scope and make it something it never was. Most importantly, Argo CD has no visibility in your Continuous Integration (CI) process. Argo CD doesn’t know:</p><ol><li>What are the new features in the container that gets deployed</li><li>Who made the container build</li><li>If the application has passed your unit and integration tests</li><li>If your new container has passed your security scans</li><li>Who approved the Pull request of the source code change</li></ol><p>In fact, Argo CD doesn’t even know that it deploys a new container that includes commits from a source code repo. All it knows is diffing and applying Kubernetes manifests without any insights about the business features behind.</p><p>Attempting to integrate this information into Argo CD either through custom plugins or custom YAML segments is always a clunky process. We understand the need for a unified interface. Developer teams love the Argo CD UI and think they can use it as a central dashboard for everything.</p><p>That is <strong>not </strong>the role of Argo CD. You can create a developer portal that uses Argo CD behind the scenes, but Argo CD is not a developer portal itself.</p><p>If you want to use a central platform for all your Argo CD instances that also combines deployment information with the CI world, <a href="https://codefresh.io/codefresh-signup/">check out Codefresh GitOps cloud</a>.</p><h3>Conclusion</h3><p>We hope this comprehensive guide is useful and has provided several good and bad practices when adopting GitOps. Argo CD is a great tool, but it offers several knobs and switches that can be used with undesirable results.</p><p>Some features can be abused in several ways, simply because no good documentation exists about the history of the feature, what the intended use is, and what to avoid.</p><p>Using this guide you can start your Argo CD journey in the best way possible as you now have the knowledge of what to avoid before investing a significant amount of effort into your Application manifests.</p><p>Here is a summary of all the anti-patterns and our recommendation:</p><ol><li>Not understanding the declarative setup of Argo CD -&gt; Store Application CRDs in Git.</li><li>Creating dynamic Argo CD applications -&gt; Use Git as the single source of truth for application configuration.</li><li>Using Argo CD parameters -&gt; Avoid using the parameters feature as it goes against GitOps principles.</li><li>Adopting Argo CD without understanding Helm -&gt; Understand how Helm works independently before adopting Argo CD.</li><li>Adopting Argo CD without understanding Kustomize -&gt; Ensure your Kustomize files work on their own before integrating with Argo CD.</li><li>Assuming that developers need to know about Argo CD -&gt; Design your Argo CD applications so developers can recreate configurations without Argo CD knowledge.</li><li>Grouping applications at the wrong abstraction level -&gt; Use Application Sets or app-of-apps pattern for proper application grouping.</li><li>Abusing the multi-source feature of Argo CD -&gt; Use multi-source as a last resort and only for edge case scenarios.</li><li>Not splitting the different Git repositories -&gt; Separate source code, Kubernetes manifests, and Argo CD application manifests into different Git repositories.</li><li>Disabling auto-sync and self-heal -&gt; Keep auto-sync/self-heal enabled for all systems, including production.</li><li>Abusing the targetRevision field -&gt; Always use HEAD in the targetRevision field.</li><li>Misunderstanding immutability for container/git tags and Helm charts -&gt; Actively set up the ecosystem (Git, Helm repos, artifact managers) to work with immutable data.</li><li>Giving too much power (or no power at all) to developers -&gt; Balance flexibility and security with Argo CD RBAC, and enable local testing without Argo CD.</li><li>Referencing dynamic information from Argo CD/ Kubernetes manifests -&gt; Store all values used in manifests statically in Git.</li><li>Writing applications instead of Application Sets -&gt; Use Application Sets to automate the creation of Application files.</li><li>Using Helm to package Applications instead of Application Sets -&gt; Learn how Application Sets work and their features.</li><li>Hardcoding Helm data inside Argo CD applications -&gt; Store all Helm information in Helm values, separate from Argo CD manifests.</li><li>Hardcoding Kustomize data inside Argo CD applications -&gt; Store Kustomize information only in Kustomize overlays separate from Argo CD manifests</li><li>Attempting to version and promote Applications/Application Sets -&gt; Promote values or overlays, not Application manifests themselves.</li><li>Not understanding what changes are applied to a cluster -&gt; Use tools or CI systems to preview and diff rendered Kubernetes manifests.</li><li>Using ad-hoc clusters instead of cluster labels -&gt; Use cluster labels and Application Sets to distribute applications to different clusters.</li><li>Attempting to use a single application set for everything -&gt; Have many different Application Sets, each with a different purpose/scope.</li><li>Using Pre-sync hooks for db migrations -&gt; Use a Database migration operator explicitly built for Kubernetes.</li><li>Mixing Infrastructure apps with developer workloads -&gt; Separate infrastructure applications from developer workloads.</li><li>Misusing Argo CD finalizers -&gt; Understand how finalizers work and use them correctly for application deletion and migration.</li><li>Not understanding resource tracking -&gt; Understand how Argo CD tracks and adopts Kubernetes resources.</li><li>Creating “active-active” installations of Argo CD -&gt; Avoid active-active setups, rely on Git and resource tracking for disaster recovery.</li><li>Recreating Argo Rollouts with Argo CD and duct tape -&gt; Use Argo Rollouts for progressive delivery and automated rollbacks.</li><li>Recreating Argo Workflows with Argo CD, sync-waves and duct tape -&gt; Use Argo Workflows for long-running tasks and complex process orchestration.</li><li>Abusing Argo CD as a full SDLC platform -&gt; Use a different system as a developer portal or <a href="https://codefresh.io/blog/introducing-codefresh-gitops-cloud/">promotion orchestrator</a>.</li></ol><p>Let us know in the comment section if we have missed any other questionable practices!</p><p>Happy deployments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b147879297a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/top-30-argo-cd-anti-patterns-to-avoid-when-adopting-gitops-1b147879297a">Top 30 Argo CD Anti-Patterns to Avoid When Adopting Gitops</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Abusing the Target Revision Field for Argo CD Promotions]]></title>
            <link>https://medium.com/containers-101/abusing-the-target-revision-field-for-argo-cd-promotions-bd1c903adc5f?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/bd1c903adc5f</guid>
            <category><![CDATA[argo-cd]]></category>
            <category><![CDATA[continuous-delivery]]></category>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Fri, 01 Aug 2025 14:03:12 GMT</pubDate>
            <atom:updated>2025-08-01T14:03:12.220Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UGgu3PFzQWMqFCKLr0hGZg.png" /></figure><p>In our big guide on <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">how to use ApplicationSets for Argo CD applications</a>, we explained the best practice of having a 3-level structure for all manifests with a clear distinction between Argo CD Application files and Kubernetes resource files.</p><p>In that article, we also outlined several anti-patterns that we have seen in the wild, meaning questionable practices that might seem ok at first glance but are problematic in the long run both for developers and for Argo CD operators.<br>In this guide we want to expand “Antipattern 2 — Working at the wrong abstraction level” and focus on the <strong>targetRevision </strong>field of the Argo CD application manifest.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>metadata:<br>  ## DONT DO THIS<br>  name: my-ever-changing-app<br>  namespace: argocd<br>spec:<br>  project: default<br>  source:<br>    repoURL: https://github.com/example-org/example-repo.git  <br>    targetRevision: dev<br>    ## earlier it was &quot;targetRevision: staging&quot; and before that it was &quot;targetRevision: 1.0.0&quot;,<br>    ## and even earlier it was &quot;targetRevision: 1.0.0-rc&quot;<br>    path: my-staging-app<br>    ## Previously it was &quot;path: my-qa-app&quot;<br>  destination:<br>    server: https://kubernetes.default.svc<br>    namespace: my-app</pre><p>Using the targetRevision field as a poor-man’s promotion mechanism is a big trap that impacts both usability and auditability for your Argo CD applications.</p><h3>How the targetRevision field works</h3><p>The targetRevision field is part of the <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#applications">Application specification</a>, which is the central Argo CD construct for deploying your GitOps applications.</p><p>An Argo CD application describes a link between a Git repository and a Kubernetes cluster. At its most basic form you point Argo CD to the <a href="https://git-scm.com/book/ms/v2/Git-Internals-Git-References">HEAD of a Git repository</a> that contains all your Kubernetes manifests. For convenience, the targetRevision field allows you to define other values apart from HEAD and even select specific Helm versions if you point Argo CD to a Helm chart instead of a Git repository.</p><p>You can see all the possible options <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/tracking_strategies/">for tracking strategies at the official documentation page</a>.</p><p>Our recommendation is to have “targetRevision: HEAD” in all your application sets and very sparingly use “targetRevision: v1.2.3” for Helm charts that define infrastructure applications that remain mostly static (unlike applications created by developers).</p><p>Here is a list of all the possible options and our recommendation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/917/1*D1ESrmxabG_ysJ4ejwpdrw.png" /></figure><p>As always, our recommendation is for production usage of Argo CD in large organizations. On a small scale (e.g. homelabs) or with a small team you can obviously get away with any approach you choose.</p><p>The main problem we see with several teams is abusing the target revision field as a promotion mechanism for developer applications. We will explain the shortcomings of this approach and the advantages of our recommendation.</p><h3>Recommendation: Setting TargetRevision to Git HEAD</h3><p>Before exploring all the alternative options let’s set the baseline and see why our recommendation of using the default HEAD value is the proper one. This scenario is <a href="https://codefresh.io/blog/how-to-model-your-gitops-environments-and-promote-releases-between-them/">where environments are based on Helm values and Kustomize overlays (or Git folders)</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UbTCakqxnfnx1XEX.png" /></figure><p>We will compare the following aspects of each approach:</p><ul><li>Direct changes to code and simplifying day-to-day operations</li><li>Helping developers understand how and when an application is deployed</li><li>Enable easy auditing (one of the main benefits of GitOps)</li><li>Addressing break-glass scenarios and urgent production hotfixes</li></ul><p>Setting the value to HEAD and instructing Argo CD to look at the latest version of the manifests/charts/overlays contained in that folder is the most direct and most straightforward approach for developers. It makes any deployment a single step. A developer can update the Kubernetes files and immediately see the change in any affected environment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ADwRM6U2HT2xLlw9.png" /></figure><p>The same is true for hotfixes or rollbacks. Developers can change the status of an environment with the familiar <a href="https://git-scm.com/docs/git-revert">git revert</a> and <a href="https://git-scm.com/docs/git-reset">git reset</a> commands.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*4eaY73PRlXNTCcU_.png" /></figure><p>In fact this is the only setup where developers don’t need to know what Argo CD does at all. We have already explained that developers <a href="https://codefresh.io/blog/why-environments-beat-clusters-for-dev-experience/">don’t really care about Argo CD manifests</a>, and keeping them happy by allowing deployments without tampering with Argo CD manifests is the optimal process for them.</p><p>Auditing is also as simple as possible. Since Argo CD continuously tracks what is committed in the Git repository of the Kubernetes resources, the deployment history is the SAME as Git history.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*q1ppWfEnLcEvmzkN.png" /></figure><p>The advantages of using Git history as deployment history cannot be overstated. In a large organization and with large numbers of environments, when something breaks the first questions everybody asks are always the same:</p><ol><li>What did we change in this environment?</li><li>When did the change happen and who did it?</li><li>What was the previous version that worked correctly?</li></ol><p>Answering these questions quickly is a considerable advantage, especially during an active incident when timing is critical.</p><p>In summary, using HEAD for targetRevision is the solution that is fully GitOps compliant (as far as auditing is concerned), easy for developers to use, and flexible enough to cover any possible edge case scenarios and urgent <a href="http://hotfixes.it/">hotfixes.</a></p><p>Let’s compare it with the additional tracking options that Argo CD offers.</p><h3>Avoid using environment names for targetRevision</h3><p>The first approach that we can dismiss right away is using branch names for environments (dev, qa, staging, prod etc).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nK1vwM24jipAKUBm.png" /></figure><p>This is the practice where teams either point specific Argo CD applications to long-running Git branches or they use the targetRevision field to “mimic” promotion in the following way:</p><ol><li>An application is first pointed to the “dev” branch of the manifests using targetRevision</li><li>Then the Application manifest is updated to point to the “qa” branch of manifests</li><li>Then, finally, the targetRevision field is set to “staging” or whichever branch is the one before production.</li><li>The cycle starts again</li></ol><p>First, if you use the targetRevision field for long-running static branches, you have bigger problems than environment promotions. We have written a complete guide with all the details <a href="https://codefresh.io/blog/stop-using-branches-deploying-different-gitops-environments/">about the problems with the branch-per-environment approach</a>.</p><p>The biggest problem with this approach, however, is that it completely misses all the benefits of auditing that come from GitOps.</p><p>If you constantly update the targetRevision field to different branch names it is tough (if not impossible) to reason about the history of your deployments.</p><p>In our baseline scenario of using HEAD, if you want to find what was running in a specific application last Thursday, you can go to your Git history and see which commit was active on the respective Git repository. This is a single-step process that anyone can complete in less than a minute.</p><p>If you use branch names in the targetRevision field, looking at history now becomes a multi-step process:</p><ol><li>First, you need to go to the Git repository that holds the application manifest and find the git commit that was done last Thursday</li><li>Read the application file and see which branch was inserted in the targetRevision (let’s say it was “dev”).</li><li>Then you need to go to the Git repository of the Kubernetes manifests and the dev branch, and see what was committed last Thursday</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*HdM_7QyW-M8kDeIO.png" /></figure><p>This process is more complex and prone to errors, as you manually need to correlate different git repos and commits for different functionalities (application manifests vs. Kubernetes manifests). Humans should not have to do this under pressure at 3 a.m. (when an incident often happens).</p><p>Auditing deployments becomes even more chaotic if you use Application Sets. First, you would have to checkout the Git repos (with the correct revisions) and use the argocd cli to recreate how your application set looked last Thursday before you actually reach the appropriate Kubernetes resources that existed in the cluster.</p><p>What completely breaks down this process is all the “temporary fixes” that developers will make if you allow them. A widespread pattern we see is the temporary change of the targetRevision field to another branch “just for testing purposes.”</p><p>For example, a team that deploys typically its QA environment from the “qa” branch will often point the ArgoCD application to the “staging” branch to debug an issue that occurred in staging using the resources from the QA branch.</p><p>Or, several times, teams create ad-hoc preview environments by pointing different environments to feature branches from individual developers. This is even worse as developer branches can be removed at any time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*OY_vtaSHI3PBEp8u.png" /></figure><p>In summary, using branch names for the targetRevision field is a very complex process that presents many issues regarding auditing and deployment history. If you work in an organization that has specific financial and legal requirements, you will spend a lot of time trying to keep the auditors happy and manually reconstructing what and where was deployed in the past.</p><h3>Avoid using semantic ranges in targetRevision</h3><p>The targetRevision field can also work with version ranges for both Git tags and Helm versions. This sounds great in theory. You enter a value like 3.4.x and then Argo CD will automatically deploy 3.4.0, 3.4.1, 3.4.2 and so on.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aXikHWsxFrABoptm.png" /></figure><p>You think you have solved the promotion problem with Argo CD but in reality you just introduced two major issues that are especially important to developers.</p><p>The first problem is that you have now missed all of GitOps’s auditing capabilities. If you use this pattern, you don’t really have a deployment history in the Git repository with the Argo CD manifests.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*y6vjsjGk-9IJfXXo.png" /></figure><p>If somebody asks what you had deployed in a past moment, you cannot see this information in the Git repository anymore. You need to correlate information between versions from your artifact manager that holds Helm charts or locate the dates where each Git tag was created. This is a cumbersome process and again it is not something that you want to do during an incident.</p><p>If your organization has strict legal requirements, using version ranges in targetRevisions will complicate auditing on a magnitude of order.</p><p>But the biggest problem is that you can no longer roll back to a previous version. As Argo CD honors semantic version rules, it will only deploy newer versions of an application. This becomes a big problem when critical issues are found in production.</p><ol><li>You have 3.4.x as a value in targetRevision</li><li>Version 3.4.2 is deployed right now in production</li><li>You create a new Git tag with version 3.4.3</li><li>Argo CD deploys it and it has a critical issue</li><li>You cannot really go back to 3.4.2 anymore in an automated way</li></ol><p>You would have to manually edit the application file and change the targetRevision to 3.4.2 yourself. Then, remember to switch it back to 3.4.x when the issue with 3.4.3 is fixed and 3.4.4 is released.</p><p>With this requirement you just forced 2 extra commits that humans have to do during incidents (where you usually want to avoid manual steps).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CDwbvinUMERSFMGZ.png" /></figure><p>This process also leaves room for human error. People might forget to switch the targetRevision field back to semantic versioning and wonder why new releases are not getting deployed anymore.</p><p>In summary, while version ranges look like an easy way to gain “free promotions”, the problems they create are more impactful than their benefits. The same issues apply if you use semantic versioning for Helm charts.</p><h3>Avoid using Git hashes in targetRevision</h3><p>This is the case when an organization wants the “safest” process possible and forces all environments to point to a specific Git hash.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QUa3QOOjLh7AhYUW.png" /></figure><p>This is a truly locked system, as Argo CD will not deploy anything new anymore. We see this pattern often in financial companies and other companies that want to restrict developers to the greatest extent.</p><p>First of all, let’s clarify the perceived “safety” of this approach. As the Argo CD documentation clearly states, even if you set up an Argo CD application with a specific Git Hash, <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/parameters/">parameter overrides</a> will still take effect. We don’t recommend using parameter overrides, but this means that a person can still affect this application configuration (either by mistake or on purpose).</p><p>So <strong>don’t</strong> assume that using a specific hash in the targetRevision field is a bulletproof method for “securing” your Argo CD applications.</p><p>On the other hand, you have ruined the developer experience for all your teams. Nothing gets deployed unless somebody also changes the targetRevision. Developer self-service is not possible at all. Every time developers create a new release, another human or system must update the targetRevision field.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*g5yMimeLzFi1dCWk.png" /></figure><p>The experience is even worse during incidents. We already know that <a href="https://codefresh.io/blog/why-environments-beat-clusters-for-dev-experience/">developers don’t care about Git hashes</a>, so understanding what is deployed where becomes a lengthy and error-prone process. Rolling back is also super difficult as the only way to do it is by switching the targetRevision.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CBMg4myQhkqSw1pZ.png" /></figure><p>Git hashes are immutable. This means that it is impossible for an external system (e.g. CI) to make any changes to your manifests or understand what needs to be deployed next. Instead, the external system (or human) must always work in 2 steps:</p><ol><li>First, somebody needs to commit the new version of the manifests with application updates (i.e. bumping the container image)</li><li>Then you also need to obtain the new Git Hash to put into targetRevision and do a <em>separate </em>commit</li></ol><p>It is impossible to do both tasks in a single step as the Git hash from the first action is needed in the second one.</p><p>If you assign this responsibility to humans, you just introduced manual steps into your deployment process. If you use an external system, you just added complexity for the sake of complexity.</p><p>In summary, using Git hashes in the targetRevision field goes against all DevOps principles and significantly slows down deployments. Using this approach in non-production environments is always a sign that the organization doesn’t really trust its own developer teams.</p><h3>Use (if needed) Git tags in targetRevision</h3><p>The last choice for the targetRevision field is to use numbered Git Tags.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2_nLdwfzSTkfTYuZ.png" /></figure><p>This is an acceptable practice, but we recommend it <strong>only </strong>for locking down production environments. It is better than using plain hashes, as with Git versions, developers can understand where each version is deployed. However, it still suffers from all the issues mentioned in the previous section. Developers cannot deploy or rollback on their own, and extra effort is required during incidents. Like Git hashes, Git tags are considered immutable, meaning you need special care to update them when a new release occurs.</p><p>Using Git versions as the target revision in production environments is a good practice if you want to fully control what goes into production. But don’t use this technique for non-production environments. Even if your organization is under legal restrictions, there is no point in making the lives of developers (especially for their QA/staging/dev environments) difficult.</p><p>Therefore, our recommendation is:</p><ol><li>Use a specific Git Tag in the targetRevision field <strong>ONLY </strong>in production environments</li><li>Use the simple HEAD tracking method in every other environment.</li></ol><p>This gives you the best of both worlds. Developers can deploy fast in non-production environments and can change versions at will. But when it comes to production, they need a human (or external system) to actually update the targetRevision field to a new version.</p><p>Note, however, that Git tags can be deleted and recreated with different contents. So, unless you set proper permissions in your Git repository, using a specific Git tag does not guarantee that your environment is locked down. If the same tag gets associated with a different Git hash, Argo CD will happily redeploy the application.</p><h3>Use specific chart versions for Infrastructure charts</h3><p>The targetRevision field can also accept specific versions for Helm charts. This is a good pattern to follow but only for <em>infrastructure Helm charts</em> (coreDNS, sealed-secrets, prometheus etc.). Basically you should pin your Helm charts only if all the following apply:</p><ul><li>The chart is stored in a Helm repository (and not in Git)</li><li>The chart represents off-the-shelf software and not something your developers create</li><li>You never “promote” these charts from one environment to another. They just represent infrastructure applications.</li></ul><p>If you use Helm charts for your own applications (the ones your developers create) then follow the advice of the first section of this guide. Put them in Git and use HEAD in the targetRevision field. This will give you all the benefits of easy updating, history as auditing, and following the GitOps principles.</p><p>It is worth mentioning that Helm versions are <em>mutable </em>by default. Unless your artifact manager has specific support for this, a developer can push a different Helm chart with the same version and override the contents of the previous one. So even if you set targetRevision for a Helm chart to version 2.3.4 it doesn’t mean that it has the same contents of chart 2.3.4 as it was last week unless you configure Helm chart versions as immutable. Some developers prefer to bump only appVersion without also bumping the chart version if they have never changed anything in the chart itself.</p><h3>Summary</h3><p>We have now seen all the choices for the targetRevision field and examined the advantages and disadvantages.</p><p>The HEAD tracking method is the simplest, most direct, and flexible for developers. It makes incident response as painless as possible, as rolling back can be performed by anyone with simple Git commands. It also allows developers to self-serve their needs. It makes auditing straightforward. We recommend using HEAD as the value in the targetRevision field whenever possible.</p><p>For production environments, we understand if you are using a specific Git tag/version. However, employ this approach sparingly and only for systems where you want to “restrict” developers. Make sure you understand all the limitations of this approach. Tags are mutable by default, and you have introduced two extra steps in all your deployment processes.</p><p>We strongly recommend <strong>against </strong>using branch/environment names in the targetRevision field. It completely breaks GitOps history and makes auditing a nightmare.</p><p>We recommend <strong>against </strong>using version ranges. Again, you lose all the benefits of GitOps auditing.</p><p>We also recommend <strong>against </strong>using Git hashes. Especially in non-production environments, it slows down your developers.</p><p>Happy deployments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bd1c903adc5f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/abusing-the-target-revision-field-for-argo-cd-promotions-bd1c903adc5f">Abusing the Target Revision Field for Argo CD Promotions</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Laser Focused Kubernetes Deployments Using Argo Rollouts and Header Based Routing]]></title>
            <link>https://medium.com/containers-101/laser-focused-kubernetes-deployments-using-argo-rollouts-and-header-based-routing-b2fac95c8479?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/b2fac95c8479</guid>
            <category><![CDATA[argo-rollouts]]></category>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[progressive-delivery]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Mon, 23 Jun 2025 13:47:39 GMT</pubDate>
            <atom:updated>2025-06-23T13:47:39.306Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xJzAG3HlNJuiWvkMd8EG_w.jpeg" /></figure><p>A Kubernetes cluster with default configuration has access to only two deployment strategies:</p><ul><li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#recreate-deployment">Recreate </a>(causes downtime)</li><li><a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#rolling-update-deployment">Rolling Update</a> (avoids downtime but you cannot preview or validate the next version in advance)</li></ul><p>To get access to <a href="https://codefresh.io/learn/kubernetes-deployment/top-6-kubernetes-deployment-strategies-and-how-to-choose/">more advanced deployment strategies</a> such as blue/green and canaries you need to use a dedicated <a href="https://codefresh.io/learn/software-deployment/understanding-progressive-delivery-concepts-and-best-practices/">Progressive Delivery</a> controller such as <a href="https://codefresh.io/learn/argo-rollouts/">Argo Rollouts</a>.</p><p>We have previously covered several <a href="https://codefresh.io/blog/minimize-failed-deployments-argo-rollouts-smoke-tests/">basic </a>and <a href="https://codefresh.io/blog/multi-service-progressive-delivery-with-argo-rollouts/">advanced scenarios</a> for Argo Rollouts in our blog. Today we answer another common question which is how you can select which of all live users will have access to the canary deployment.</p><p>As a reminder, with a canary deployment, you gradually shift traffic to your live users for the new version’s pods. The canary is finished when 100% of live users see the new pods or when something goes wrong and you revert all of them to the previous/stable version.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*nO1KEYdHmXV39Kg2.png" /></figure><p>In the example above, we start the canary by shifting 20% of network requests to the v2 container, then 50%, and finally 100%. The key point here is that unless you do something special, the network traffic requests that go to the new version are random. Some users might even see both application versions if you are not careful.</p><p>In the real world you almost always want specific groups of people to be part of the canary process. Some examples are:</p><ul><li>“Only our internal users should see this new version”</li><li>“Only French users must be part of the canary”</li><li>“Asia should stay in the old version, the US will see the canary only”</li><li>“Only users who have checked the ‘preview checkbox’ must see the canary”</li><li>“The payment gateway should stay in the old version. The intranet should see the new version”</li></ul><p>So can we still use Argo Rollouts to cover these use cases? The answer is yes, and in this guide we explain two approaches, one basic and one advanced and also compare the advantages and disadvantages.</p><p>The methods we are going to use to decide which users see the canary are</p><ol><li>Static Routing with extra URLs (limited but simple to implement)</li><li>Header based Routing (more powerful but also more complex to implement)</li></ol><p>If you want to try the examples on your own, all resources are available at <a href="https://github.com/kostis-codefresh/rollouts-header-routing-example">https://github.com/kostis-codefresh/rollouts-header-routing-example</a></p><h3>Understanding the blast radius of your deployments</h3><p>The central promise of Argo Rollouts is automatic rollbacks. You deploy a new version and then within 1–2 hours (ideally 15 minutes) either the new version is promoted as stable or it is automatically reverted.</p><p>This sounds great in theory, but in practice, you need to understand who will be affected if a deployment fails. Let’s say you are doing canary deployments and need 1 hour to get good metrics to decide about the new version’s health. If the metrics fail, some users will have issues for 1 hour. Is this acceptable? Could you control which users face the disruption and who never participate in the canary?</p><p>If you read the official Argo Rollouts documentation, the assumption is that the Rollout controller only focuses on a single application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*1tIgTupLRsxW_ltq.png" /></figure><p>In a big organization most services have dependencies. This is especially true for companies that have adopted microservices. So instead of looking at a single service independently, you need to understand how the application works inside the whole cluster.</p><p>A more realistic example would be the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*unww4_e4OqjbpNXA.png" /></figure><p>Here we have an e-shop application with different kinds of users</p><ul><li>External partners can interact with the inventory of the application</li><li>Internal/Intranet users provide customer support and handle the store management</li><li>The general public accesses the public website to order/buy items.</li></ul><p>If we choose Progressive Delivery for the “auth” service shown in the middle, we see that even though it is a single service, it is a runtime requirement for 3 other services (portal, admin, store). So even if we apply a canary approach, a failed deployment will affect all users of our application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kiCRYwfnJ4frqXij.png" /></figure><p>Therefore if you need 2 hours for a canary deployment , and you have a failure then ALL your users will be affected for 2 hours. Wouldn’t it be nice if you could control which user groups are affected and which are not?</p><h3>Isolating specific users instead of random network requests</h3><p>Making a decision about which users see the canary process and which do not is only one aspect of the deployment process. The other aspect is verifying whether a user is part of the canary. Then, all network requests should always be directed to the preview/canary version of the application.</p><p>A widespread misconception about Argo Rollouts is that integrating with <a href="https://argo-rollouts.readthedocs.io/en/stable/features/traffic-management/">a traffic provider</a> allows you to send specific users to the canary version. Unfortunately, this is <strong>not </strong>true in the default configuration.</p><p>Even if you use a traffic provider, the percentage of requests that go to the canary application is completely RANDOM. If you set up Argo Rollouts with a canary step of 30%, Argo Rollouts will only guarantee that 30% of all network requests will go to the canary process. But there is no guarantee that these are from the <em>same </em>users.</p><p>This leads to a very common problem for several organizations: Multiple requests from the same user result in different application versions (both old/stable and preview/canary).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*2_CRC6anvRIzJKJ1.png" /></figure><p>In the example of 30%, Argo Rollouts will indeed send 30% of the total network requests to the canary version, but if you look at the network requests for a single user, you might have the case where the first request is not part of the 30%, the next one is, the next is not and so on. This limitation can be catastrophic for applications with a Graphical Interface, as the user might see different components on screen with each subsequent network request (if the canary version also affects the application’s UI).</p><p>In the real world, companies don’t want a random percentage of requests to go to the canary version. You want to apply the percentage to individual users.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*J3m8T5yI4Nn4-P9d.png" /></figure><p>The expectation is that if you set up a canary of 30%, you expect 30% of <em>users </em>to see the canary and 70% of them are still on the old/stable version. If you log the network requests of a single user however, you want all of them to go to <strong>EITHER </strong>the canary version <strong>OR </strong>the stable version and never both.</p><p>So can Argo Rollout support this use case of user segmentation instead of network request segmentation?</p><h3>Example application — Visualize your canary</h3><p>Our example Rollout can be found at <a href="https://github.com/kostis-codefresh/rollouts-header-routing-example/">https://github.com/kostis-codefresh/rollouts-header-routing-example/</a></p><p>This repository includes</p><ul><li>The source code of the example application</li><li>A setup for the <a href="https://traefik.io/traefik/">Traefik proxy</a></li><li>Example <a href="https://argo-rollouts.readthedocs.io/en/stable/features/specification/">Rollout definitions</a></li><li>Examples of HTTP routes from the <a href="https://gateway-api.sigs.k8s.io/">Gateway API</a></li></ul><p>The highlight of the example application is that you can see visually which requests hit one version or the other.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/966/0*TtU_0EZy56nme-ix.png" /></figure><p>In the screenshot above, a canary is in progress between the application’s v1 and v2. The dashboard performs multiple requests (one for each box shown), allowing you to examine your canary networking in a very simple way.</p><h3>Approach 1 — Static URL routing</h3><p>Let’s start with the first use case — reducing the blast radius from a failed deployment. The solution is to create a different URL for each group of users who participate in the deployment process.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GMvbObf_jHpqA43l.png" /></figure><p>We have 3 URLs:</p><ol><li>The canary/default URL where requests are routed to the canary. Users of this URL will follow the canary traffic as it increases</li><li>A URL that ALWAYS sends requests to the canary/preview version regardless of the defined percentage</li><li>A URL that ALWAYS sends requests to the stable/old version regardless of the defined percentage.</li></ol><p>Instead of having a single URL for the canary, we can give each user group a different URL according to their risk acceptance.</p><p>In the previous example of the e-shop application we can easily accommodate the following imaginary requirements:</p><ol><li>We want our external partner never to see the canary at all. They will be shown the stable version until the last possible moment</li><li>We want our public users to be part of the canary process as normal</li><li>We want our own employees to “see” the new version right away so that they can detect problems as early as possible</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/811/0*ikYZtKlvrCzKa-wa.png" /></figure><p>In this example we use different URL paths, but we could do the same thing with hostnames (e.g. <a href="http://canary.auth.com/">canary.auth.com</a>, <a href="http://preview.auth.com/">preview.auth.com</a>, <a href="http://stable.auth.com/">stable.auth.com</a>).</p><p>Now when a canary process is started</p><ul><li>Users who follow the /stable endpoint will always see the old/stable application version</li><li>Users who follow the /preview endpoint go to the new version straight away</li><li>Users who follow the /canary endpoint participate in the canary as usual.</li></ul><p>Here is a timeline for each user group. Blue indicates that network requests go to the old/stable version, and green indicates that they go to the new canary version.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Mmdh9gxUd0LCkOgi.png" /></figure><p>The end result for all groups is precisely the same. They see the new version of the application. The big difference is in failed deployments. If a deployment fails and the canary reverts, users that follow /stable (external partners in our example above) have no impact at all.</p><p>Instead of affecting everybody, we have completely isolated our external patterns and also have a different risk acceptance between the general public and our own internal users.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3tyH7iobcdeXWZci.png" /></figure><p>Implementing this approach with Argo Rollouts is straightforward. Instead of using just one network endpoint (for the canary), you <a href="https://github.com/kostis-codefresh/rollouts-header-routing-example/blob/main/static-routing/httproutes.yml">create an additional one pointing at the stable service and one more for the preview service.</a></p><p>If you run our example, you can now access 3 URLS (/canary, /stable, /preview). If you start a canary process only the /canary will gradually move to the new version.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aH3A_7YeZOAO9Kpo.png" /></figure><p>Users who visit /preview will see the new version right away:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UO6BmvB4HJI22GhN.png" /></figure><p>Users who visit /stable will always view the stable version regardless of the state of the canary process:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*t_Q8A5dba-tfwWIZ.png" /></figure><p>This approach needs no source code changes and can be implemented quickly. It has however three significant limitations:</p><ul><li>It is static in the sense that you need to decide in advance which user groups will visit which URL</li><li>You need to notify all dependent services about the new URLs if they don’t want to follow the default behaviour</li><li>It still works at the level of network requests instead of actual users</li></ul><h3>Approach 2 — Dynamic URL routing</h3><p>The main limitation of static routing is that you need to identify which user group will use which service in advance. Once you make this selection, you cannot change it after the canary has started.</p><p>We still haven’t addressed the problem of users versus requests. In the case of the canary endpoint a random number of <em>requests </em>can see the canary instead of real users.</p><p>Using HTTP headers instead of simple endpoints can improve network isolation. Argo Rollouts can detect optional HTTP headers and make decisions accordingly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*74DUQTDPW3bJTxHF.png" /></figure><p>In the example above Argo Rollouts will send to the canary all requests that have an HTTP header “X-Canary:true”.</p><p>Now we have the capability to have canaries for users instead of just networks. We can modify our application source code to enable this header on the fly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1005/0*Kb_rI9l5PtEbb66H.png" /></figure><p>All requests for users with this header present will be redirected to the canary user. This user group will always see the canary, so this approach works great even for Graphic applications.</p><p>HTTP headers are fully dynamic. You can change them on the fly. There are several networking products such as load balancers, api gateways, service meshes that allow you to inject headers or modify headers in a network request.<br>We can activate this pattern by creating a standard <a href="https://github.com/kostis-codefresh/rollouts-header-routing-example/blob/main/dynamic-routing/smart-route.yml">HTTP route</a> and then instructing <a href="https://github.com/kostis-codefresh/rollouts-header-routing-example/blob/main/dynamic-routing/rollout.yml">the canary</a> to create a second one on the fly only if a specific header exists.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Rollout<br>metadata:<br>  name: smart-rollouts-demo<br>spec:<br>  replicas: 5<br>  strategy:<br>    canary:<br>      canaryService: smart-canary-service<br>      stableService: smart-stable-service<br>      trafficRouting:<br>        managedRoutes:<br>        - name: always-preview<br>        plugins:<br>          argoproj-labs/gatewayAPI:<br>            httpRoutes:<br>              - name: my-smart-route<br>                useHeaderRoutes: true<br>            namespace: default<br>      steps:<br>        - setHeaderRoute:<br>            name: always-preview<br>            match:<br>              - headerName: X-Canary<br>                headerValue:<br>                  exact: &quot;yes&quot;  <br>        - setWeight: 25                        <br>        - pause: {}<br>        - setWeight: 100</pre><p>If you launch the application and this HTTP header is not present, you will see a standard canary with both versions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*eW3b8RYtjUziRnt-.png" /></figure><p>If you activate the header then all requests of this user go to the canary!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*bUB-1suEv5lBREtq.png" /></figure><p>This setting is now per user. If you open another browser (to simulate a different user), you will see the standard canary behavior again.</p><p>In this simple example, the application itself controls the HTTP header. In a real application, a networking component might do this for you (for example, adding this header only to French users).</p><h3>Conclusion</h3><p>In this guide we have seen two additional approaches to decide which users can look at the new version during a canary instead of a random percentage of requests (default behavior for Argo Rollouts)</p><p>With these approaches</p><ul><li>We have complete control over the impact of a failed deployment. We can choose user groups that will always be redirected to the stable version even when a canary is in progress</li><li>Splitting user groups according to their risk acceptance can be decided in advance or updated on the fly</li><li>Canary behavior is now per user instead of per network request</li></ul><p>In both cases, you might need to make source code changes to use a different endpoint or enable/disable a specific HTTP header.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b2fac95c8479" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/laser-focused-kubernetes-deployments-using-argo-rollouts-and-header-based-routing-b2fac95c8479">Laser Focused Kubernetes Deployments Using Argo Rollouts and Header Based Routing</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Distribute Your Argo CD Applications to Different Kubernetes Clusters Using Application Sets]]></title>
            <link>https://medium.com/containers-101/distribute-your-argo-cd-applications-to-different-kubernetes-clusters-using-application-sets-b945c2903452?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/b945c2903452</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[argo-cd]]></category>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[multi-cluster]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Tue, 17 Jun 2025 10:56:22 GMT</pubDate>
            <atom:updated>2025-06-17T10:56:22.349Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EXKGokuX7yYkvLzohZs8YQ.jpeg" /></figure><p>In the previous article in this series, we explained <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">how Argo CD application Sets work</a> and how to use them for organizing your applications in different environments or groups. We received a lot of positive feedback from our readers, and many teams now use the associated Git repository <a href="https://github.com/kostis-codefresh/many-appsets-demo">as a starting point for their own Argo CD setup</a>.</p><p>Even though we covered Application Sets, and <a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/applicationset/Generators-Git/">more specifically the Git generator</a>, we never explained how to assign different applications to different clusters. This is a common question from teams managing multiple clusters with different application settings per environment.</p><p>In this article, we complete the Application Set puzzle and analyze:</p><ul><li>How to decide which application goes to which cluster</li><li>How to have different application settings per environment</li><li>How to split your clusters into different groups with cluster labels</li><li>How to combine the Argo CD Git Generator with the Cluster generator</li><li>How you can simplify your day-to-day operations using cluster labels.</li></ul><p>For more details, we’ve again included an <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd">example Git repository</a>.</p><h3>Managing multiple Kubernetes clusters with Argo CD</h3><p><a href="https://codefresh.io/learn/argo-cd/argocd-applicationset-multi-cluster-deployment-made-easy-with-code-examples/">Argo CD ApplicationSets</a> let you automate your Application manifests in Argo CD. If you adopt ApplicationSets, you no longer need to deal with individual Argo CD applications’ YAML. You can simply point Argo CD to your clusters and folders, and all the possible combinations get created on the fly for you.</p><p>We’ve already seen that you can use ApplicationSets to deploy multiple applications on a single cluster.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/859/0*rgz-oVky7l6PCx-X.png" /></figure><p>We’ve also seen the other dimension — how to deploy the same application to different clusters:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*fMuiht3okc-vxnmX.png" /></figure><p>In this guide, we cover the most complex scenario where we have multiple applications and multiple clusters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7Q5SGPzyMJvO7Nbr.png" /></figure><p>To achieve this scenario, we need to use the <a href="https://codefresh.io/learn/argo-cd/argocd-applicationset-multi-cluster-deployment-made-easy-with-code-examples/">Cluster Generator of Argo CD</a>. This means you need to connect all your clusters to a single Argo CD instance. This is the hub-and-spoke setup of Argo CD. See our <a href="https://codefresh.io/learn/argo-cd/a-comprehensive-overview-of-argo-cd-architectures-2025/">Argo CD architecture guide</a> for different configurations and the advantages and disadvantages of each one.</p><p>Using a combination of the cluster and the Git generator, we can create a 2-dimensional matrix of all the pairs (cluster-app) and have Argo CD deploy everything with a single file.</p><p>This approach is a great starting point, but in a real organization, we need 2 more capabilities:</p><ol><li>The ability to enable/disable some applications for some clusters</li><li>The ability to have different configurations (for example, Helm values) according to the cluster the application belongs to.</li></ol><p>The final result is not a full 2-D matrix because some applications won’t exist in all environments. We want to achieve this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Edwa1_J-mBiXASOZ.png" /></figure><p>In the example above, Sealed Secrets is NOT present in Cluster C. And the Cert manager is not present in Cluster A. In addition, the “Billing Application” needs to have a different configuration for each cluster.</p><p>So, can we achieve these requirements with Application Sets?</p><h3>Anti-pattern — Creating Snowflake servers with ad-hoc combinations</h3><p>When faced with the problem of distributing different applications to different clusters, many teams jump straight into very complex solutions that combine multiple Application Set generators. Unfortunately, most hard code custom combinations in the application set files.<br>A classic example of this approach is trying to individually enable/deactivate a specific application for a particular cluster. We advise <strong>AGAINST </strong>using such Application Set structures.</p><pre> ## DO NOT DO THIS <br>- merge:<br>      mergeKeys:<br>        - app<br>      generators:<br>        - list:<br>            elements:<br>              - app: external-dns<br>                appPath: infra/helm-charts/external-dns<br>                namespace: dns<br>              - app: argocd<br>                appPath: infra/helm-charts/argocd<br>                namespace: argocd<br>              - app: external-secrets<br>                appPath: infra/helm-charts/external-secrets<br>                namespace: external-secrets<br>              - app: kyverno<br>                appPath: infra/helm-charts/kyverno<br>                namespace: kyverno<br>        - list:<br>            elements:<br>              - app: external-dns<br>                enabled: &quot;true&quot;<br>              - app: argocd<br>                enabled: &quot;true&quot;<br>              - app: external-secrets<br>                enabled: &quot;false&quot;<br>              - app: kyverno<br>                enabled: &quot;true&quot;<br>    selector:<br>      matchLabels:<br>        enabled: &quot;true&quot;</pre><p>This file creates snowflake/pet servers where you need to define exactly what they contain. The final result is brittle, requiring significant effort when any major change happens. There are several challenges with this setup:</p><ul><li>It works directly on individual clusters (instead of cluster groups, as we’ll see later in the guide), so it never scales as your requirements change.</li><li>It forces you to hardcode application combinations inside Application Sets. This makes the generators your new unit of work instead of your Kubernetes manifests.</li><li>It makes all day-2 operations lengthy and cumbersome procedures.</li><li>It makes reasoning about your clusters super difficult. Understanding what’s deployed where is no longer trivial.</li></ul><p>The final two points cannot be overstated. This approach might look ok at first glance, but the more clusters you have, the more complex it will become.</p><ol><li>If somebody asks which clusters contain kyverno, you need to scan all individual files for the “enabled” property of the “kyverno” line.</li><li>Every time you add a new cluster to your setup, you need to copy/paste the list of components from another cluster and start enabling/deactivating each individual component. If you have many components and many clusters, this is an error-prone process that you should avoid at all costs.</li><li>If you add a new component, you need to go to all your existing files and add it to all the enabled/deactivated lists.</li><li>It only addresses the first requirement (enabling/deactivating applications for clusters) but not the second one (having different configurations per cluster for the same application).</li></ol><p>There is a better way to distribute applications to Argo CD clusters. The approach we DO recommend is using cluster generator labels.</p><h3>Working with cluster groups instead of individual clusters</h3><p>In a large organization, <a href="https://codefresh.io/blog/why-environments-beat-clusters-for-dev-experience/">you don’t really care about individual clusters</a>. You care about cluster groups. Argo CD doesn’t model the concept of a cluster group on its own, but you can replicate it using cluster labels.</p><p>You need to spend some time thinking about the different types of clusters you have and then assign labels to them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*yCihdteaw7Ifuxrw.png" /></figure><p>The labels can be anything that makes sense to your organization</p><ul><li>Environment types (for example, QA/staging/prod)</li><li>Regions or countries</li><li>Department or teams</li><li>Cloud provider or other technical difference</li><li>Any other special configuration that distinguishes one or more clusters from the rest</li></ul><p>After you have those labels, you can slice and dice your cluster across any dimension and start thinking about cluster groups instead of individual cluster labels.</p><p>Because, ultimately, 99% of use cases resolve around cluster groups rather than individual clusters.</p><ul><li>“I want all my production clusters to have application X with Y settings.”</li><li>“I want all my AWS clusters to have X authentication enabled.”</li><li>“Team X will control this environment while team Y will control that environment.”</li><li>“All European clusters need this application.”</li><li>“Application X is installed on both US-East and US-West regions, but with different configurations.”</li><li>“Just for our QA environment, we need this load testing app deployed.”</li></ul><p>We’ll see in detail all the advantages when using cluster labels, but one of the easiest ways to understand the flexibility of this approach is to examine what happens for a very common scenario — adding a brand new cluster.</p><p>In most cases, a new cluster is “similar” to another cluster. A human operator needs to “clone” an existing cluster, or at the very least, define the new properties of the new cluster in the configuration file.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YaC6E6CepQaftKcF.png" /></figure><p>If you use cluster labels (as we suggest), the whole process requires zero modifications to your application set files.</p><ol><li>You create the cluster with your favorite infra tool (Terraform/Pulumi/Crossplane, etc)</li><li>You assign the labels on this cluster (for example, it’s a new QA cluster in US East)</li><li>Finished!</li></ol><p>Argo CD automatically detects this new cluster when it collects all its clusters, and deploys everything that needs to be deployed in the cluster according to its labels. There’s no configuration file to edit to “enable/deactivate” your apps. The process cannot get any easier than this.</p><p>Notice that this setup also helps with communication between developers and operators/infrastructure people. Opening a ticket for a new cluster and having several discussions about the contents of the new cluster significantly slows down development time.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*D068ptW84rOzL2VS.png" /></figure><p>In most cases, developers want a cluster that either mimics an existing one or has similar configuration to another cluster group. This makes your job very easy, as you can map directly to cluster labels what developers need.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JZR_aQmF_AngyLsS.png" /></figure><p>Creating a new cluster can be a hectic process because you need to validate that it matches the expected workloads and is “similar” to your other clusters. If you use cluster labels, then Argo CD takes care of everything in minutes instead of hours.</p><h3>Organizing your Argo CD clusters with different labels</h3><p>Let’s see how all our use cases can work together with a semi-realistic example. You can find all Argo CD manifests at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd">https://github.com/kostis-codefresh/multi-app-multi-value-argocd</a> if you want to follow along.</p><p>The repository contains:</p><ul><li>A set of scripts to create 7 clusters running on the same machine using K3d</li><li><a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/k3d-hub-and-spoke/start.sh#L32">Different labels</a> for organizing those clusters into different groups</li><li>Example <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/appsets">Application Sets</a> that distribute applications to those cluster groups</li><li><a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/charts">Helm charts</a> and <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/kustomize-apps">Kustomize overlays</a> for placeholder applications</li></ul><p>Here are the 7 clusters that we define with <a href="https://k3d.io/stable/">K3d</a>. In a real organization, these clusters would be created with Terraform or another similar tool.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*q3u05sOIr7BOxDMS.png" /></figure><p>We’ve assigned several example labels on those clusters. Notice that even before talking about applications, the clusters themselves exist in 2 dimensions:</p><ul><li>A promotion flow (QA -&gt; staging -&gt; production) on the horizontal axis</li><li>A region setting (US/EU/Asia) on the vertical axis</li></ul><p>The “hub” cluster contains the Argo CD instance that manages all the other clusters. In our example, this cluster only has Argo CD and no end-user applications, so it doesn’t take part in our application sets (it has a label type=hub instead of type=workload).</p><p>You can verify or change the labels of each cluster by the <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#clusters">Cluster Secret</a> in the main Argo CD instance. Here’s an example of a QA cluster that shows the assigned labels as created by our example GitHub repository.</p><pre>apiVersion: v1<br>data:<br>  [...snip..]<br>kind: Secret<br>metadata:<br>  annotations:<br>    managed-by: argocd.argoproj.io<br>  labels:<br>    argocd.argoproj.io/secret-type: cluster<br>    cloud: gcp<br>    department: billing<br>    env: qa<br>    region: eu<br>    type: workload<br>  name: cluster-k3d-qa-eu-serverlb-1347542961<br>  namespace: argocd</pre><p>We’re now ready to look at some typical scenarios. It’s impossible to cover all possible use cases, so we’ll see some representative scenarios for each use case.</p><p>The major question that you need to ask yourself is whether you want to deploy an application across different environments with the exact same configuration OR you want a different configuration per environment. The latter is obviously more complex and requires a good understanding of your Kustomize Overlays and Helm value hierarchies, but it’s closer to how a real organization works:</p><p>Here are the <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/appsets">scenarios </a>we’ll see:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/726/1*C4nZDSkuOcXZUcBwZ6aKoQ.png" /></figure><p>Notice that in our example repository, our applications are grouped in folders by type: <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/simple-apps">manifests</a>, <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/kustomize-apps">Kustomize, </a>or <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/charts">Helm apps</a>.</p><p>In a real organization, you might have different sub-folders for each type, but it’s simpler if you only have to manage one type of application (for example, Kustomize for your own developers and Helm charts for external applications).</p><h3>Scenario 1 — Run some applications on all workload clusters</h3><p>Let’s see a very simple use case. We want to deploy all the following common applications to all our clusters only, excluding the Argo CD “hub” cluster. We can take advantage of the “workload” label and point Argo CD to a folder that has all our common applications.</p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: simple-apps/*<br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;</pre><p>You can see the full Application Set at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/01-common-apps.yml">01-common-apps.yml</a>. This file instructs Argo CD to:</p><ol><li>Gather all connected clusters that have the “type=workload” label</li><li>Gather all the Kubernetes manifests found under “simple-apps”</li><li>Create all the combinations between those clusters and those apps</li><li>Deploy the resulting Argo CD applications.</li></ol><p>If you’re not familiar with generators, please read <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">our Application Set Guide</a>. If you deploy this file, you’ll see the following:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wyGEiq1uTpdWpreL.png" /></figure><p>We got 18 applications (6 clusters multiplied by<a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/simple-apps"> 3 apps</a>) in a single step. Isn’t this cool?</p><h3>Scenario 2- Choose only GCP clusters and exclude those in AWS</h3><p>In the next example, we want to install all the applications under `simple-apps` folder only in our Google Cloud clusters, but those applications should not exist in our Amazon clusters. Again, we have created the appropriate labels in advance. In our imaginary organization, all non-production servers run in GCP.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kIrjpZ1vQw6WhXYI.png" /></figure><p>The admin server also runs in AWS, and this is why it won’t get picked up by our application set. You can find the full manifest at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/02-gcp-only.yml">02-gcp-only.yml</a>.</p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: simple-apps/*<br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;    <br>                cloud: &quot;gcp&quot;</pre><p>This Application Set is similar to the previous one, but now we’re matching 2 labels — one for Google Cloud and one for all our “workload” clusters.</p><p>If you apply it, you get several applications, but only to non-prod environments.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*kGcgkuhLXjU2HjUu.png" /></figure><p>Argo CD created a list of applications for only the QA and Staging cluster groups, as they contain clusters that run on Google Cloud.</p><h3>Scenario 3 — Choose only European Clusters</h3><p>The big power of labels will become clear when you get requirements that need to work with clusters in an unusual or non-linear way. Let’s imagine a scenario where you need to do something specific to all European clusters because of <a href="https://en.wikipedia.org/wiki/General_Data_Protection_Regulation">GDRP regulations</a>.</p><p>At this point, most teams realize that the primary way of organizing their clusters was by type (qa/staging/prod), and they modelled the region as a secondary parameter. This creates several challenges and makes people ask the same question, “Does product X support deployments in regions?”.</p><p>But when using the cluster generator, <em>all </em>labels are first-level constructs, allowing you to make any selection possible. We can focus on European clusters by just defining our region the same way as any other scenario.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*cFWcH6MKgNuY5bN-.png" /></figure><p>Today, only the QA and Production environments have a European server. But tomorrow, you might add one in the Staging environment WITHOUT any modifications in your application Set.</p><p>We select all European servers by region with file <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/03-eu-only.yml">03-eu-only.yml</a></p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: simple-apps/*<br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;    <br>                region: &quot;eu&quot;  </pre><p>Deploying this application set will instruct Argo CD to place all the applications under simple-apps folder only in the European servers:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VrR_v840qHuAS3aQ.png" /></figure><p>If you add a new European Region in the Staging environment, then in the next Argo CD sync, that cluster also gets all applications defined for Europe, with zero effort from the administrator.</p><h3>Scenario 4 — Choose a specific cluster among a cluster group</h3><p>If it wasn’t clear from the previous examples, the label selector for clusters works in an “AND” manner by default. So the more labels you add in the selector, the more specific the application set becomes.</p><p>This means that even if you really want to select a single cluster among a group, you can just define all the labels that correctly identify it.</p><p>We want to select the Asian Environment for Production (which is a specific Kubernetes cluster).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gExuB--kyKuTNyQ-.png" /></figure><p>The application set that selects this cluster is at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/04-specific-cluster.yml">04-specific-cluster.yml</a>.</p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: simple-apps/*<br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;  <br>                region: &quot;asia&quot;    <br>                env: &quot;prod&quot;    </pre><p>The labels we have defined in the application set map only to one cluster. Argo CD will look at this application set and find all clusters that have type=workload AND region=asia AND env=prod.</p><p>Applying the file you will see the following</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*whTeItgr4-6leGXF.png" /></figure><p>As expected, Argo CD deployed all the applications under simple-apps folder only to the production cluster in Asia.</p><h3>Scenario 5 — Different Kustomize overlays for the QA clusters</h3><p>For simplicity, in all the previous examples, all our applications use the same configuration across all clusters. So even if our cluster generator selected multiple clusters, they all used <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/simple-apps">the plain manifests</a> we defined.</p><p>While this approach can work for some trivial applications, you almost certainly want to use a different configuration per cluster. This can take the form of DNS names, database credentials, security controls, rate limiting settings, etc.</p><p>For our next example, we’ll <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/kustomize-apps">use Kustomize overlays</a>. For each application, we have the base configuration plus extra settings in overlays or <a href="https://kubectl.docs.kubernetes.io/guides/config_management/components/">Kustomize components</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/275/0*DhZxd9PDQHud5X4t.png" /></figure><p>We have covered Kustomize overlays in detail <a href="https://codefresh.io/blog/how-to-model-your-gitops-environments-and-promote-releases-between-them/">in the promotion article</a> and explained how they work with Argo CD in <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">our Application Set guide</a>, so make sure you read those first if you’re not familiar with overlays.</p><p>For the cluster selector, we’ll choose the QA environment this time (which corresponds to 2 clusters).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wtLWZJWYQtP4VFcB.png" /></figure><p>The application set that selects the QA clusters and deploys applications with the respective configuration is at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/05-my-qa-appset.yml">05-my-qa-appset.yml</a>.</p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: kustomize-apps/*/envs/qa  <br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;    <br>                env: &quot;qa&quot;  </pre><p>The Matrix generator selects all clusters that match the QA/Workload labels and <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/kustomize-apps">applies only the application</a>s that have a QA overlay.</p><p>Apply the file, and you see all QA deployments:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-hukHsCbkgGHbsuT.png" /></figure><p>The important point here is that for each application, only the QA overlay is selected.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/968/0*vzZDHecqPks6a-LK.png" /></figure><p>You can see in the Git repository that the “Invoices” application comes <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/tree/main/kustomize-apps/invoices/envs">with configurations for all environments</a>, but we appropriately employ only the QA one in our application set.</p><h3>Scenario 6 — Different Kustomize settings for US and EU in production</h3><p>There are many more examples we can show with this setup. Be sure to read the documentation of <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Generators-Cluster/">the cluster generator</a>. One important point is that you can use the output of this generator as input to another generator.</p><p>As a final example with Kustomize, let’s see a scenario where we want to deploy our applications to Production Europe and Production US, but not in Asia.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8mRRQh3JD2LXnxFs.png" /></figure><p>Remember that by default, server labels work in “AND” mode. So if we simply list “us” and “eu” as labels, Argo CD will try to find all clusters that have both labels at the same time. We don’t want this, as no cluster matches this description.</p><p>Also, unlike the previous example where we specifically asked for the “QA” overlay, now we want to choose the overlays that match whatever the cluster type/region is (either prod-us or prod-eu).</p><p>You can find the full application set at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/06-my-prod-appset.yml">06-my-prod-appset.yml</a>.</p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;      <br>                env: &quot;prod&quot;<br>              matchExpressions:<br>              - key: region<br>                operator: In<br>                values:<br>                  - &quot;eu&quot;<br>                  - &quot;us&quot;        <br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: &#39;kustomize-apps/*/envs/{{.name}}&#39;</pre><p>The first thing to show here is the matchExpressions block. This lets you choose clusters in an “OR” manner. We want all clusters that are either EU or US AND in production.</p><p>The second point is using the output of the cluster generator as input to the Git generator. The “{{.name}}” variable will render to the name of the cluster matched, forcing the Git generator to load the respective Kustomize overlay for each environment.</p><p>Apply the file and you will see production deployment in EU and US but not in Asia:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*O3UFI384ZFm9p0vD.png" /></figure><p>And most importantly, you see that each server loads the configuration for its own region:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JtGCO10OZW8nXGTE.png" /></figure><p>You should now understand how to select any combination of clusters and apply your exact choice of Kustomize overlays according to the “type” of each cluster.</p><h3>Scenario 7 — A Helm hierarchy of values for the QA environment</h3><p>Cluster labels can also work with your Helm charts and values.</p><p>As a starting example, let’s deploy our Helm charts to the two QA clusters using the same configuration for both.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DrdAxIJZMsKHw8Lg.png" /></figure><p>You can find the full application set at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/07-helm-qa-only.yml">07-helm-qa-only.yml</a>.</p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: charts/*<br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;    <br>                env: &quot;qa&quot;  </pre><p>The generator part of the file selects our charts and applies them to all clusters with the QA/workload label.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*VvAq0ZMnTVVlIrOp.png" /></figure><p>We have 2 example charts in the Git repository, so Argo CD created 4 applications for us (1 for each region).</p><h3>Scenario 8 — Different Helm values for the European environments</h3><p>Like the Kustomize example, we want to make our examples more advanced and have different value files per environment.</p><p>The same Git repository also contains a set of Helm values for each environment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/272/0*9IUZBUEc9g4eo-nE.png" /></figure><p>We have covered Helm value hierarchies and Argo CD applications <a href="https://codefresh.io/blog/helm-values-argocd/">in our Helm guide</a>, so please read that guide first if you don’t know how to create your own value hierarchies.</p><p>Let’s deploy our Helm charts at all European servers:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*GPgMmd7j1WGYg-7_.png" /></figure><p>This time, however, we want to specifically load the European values only instead of all values.</p><p>You can find the full application set at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/08-helm-eu.yml">08-helm-eu.yml</a></p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;      <br>                region: &quot;eu&quot;                        <br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: charts/*  </pre><p>The generator part is straightforward. It applies all charts to clusters with the EU/Workload labels. The smart selection of values happens in the “sources” section of the generated application:</p><pre>sources:<br>  - repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>    path: &#39;{{.path.path}}&#39;<br>    targetRevision: HEAD<br>    helm:<br>      valueFiles:<br>      - &#39;$my-values/values/{{index .path.segments 1}}/common-values.yaml&#39;  <br>      - &#39;$my-values/values/{{index .path.segments 1}}/app-version/{{index .metadata.labels &quot;env&quot;}}-values.yaml&#39;                <br>      - &#39;$my-values/values/{{index .path.segments 1}}/regions/eu-values.yaml&#39;              <br>      - &#39;$my-values/values/{{index .path.segments 1}}/envs/{{index .metadata.labels &quot;env&quot;}}-eu-values.yaml&#39;                  <br>  - repoURL: &#39;https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git&#39;<br>    targetRevision: HEAD<br>    ref: my-values</pre><p>Here we apply the appropriate values according to:</p><ol><li>The chart name (index .path.segments 1)</li><li>The environment label that exists on the cluster (index .metadata.labels “env”)</li></ol><p>In this example, you see how you can query the cluster itself for its own metadata.</p><p>If you apply this file, you see both charts deployed in the European servers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*y4n8gW0LyHKSlxgk.png" /></figure><p>But most importantly, you see that each environment gets the correct values according to its type:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*v351iseZ_L-zpd-E.png" /></figure><p>Notice that in both cases, we still have some common values that apply to both environments.</p><h3>Scenario 9 — Different Helm values for all 3 Production regions</h3><p>As a final example with Helm, let’s deploy to all production regions with the appropriate settings for each one.</p><p>We choose all 3 regions in our cluster generator.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mopjPB-KkAuLHI4c.png" /></figure><p>You can find the full application set at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd/blob/main/appsets/09-helm-prod.yml">09-helm-prod.yml</a>.</p><p>Like before, we select all 3 regions in an “OR” manner and apply our charts.</p><pre>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - matrix:<br>      generators:<br>        - clusters:    <br>            selector:<br>              matchLabels:<br>                type: &quot;workload&quot;      <br>                env: &quot;prod&quot;<br>              matchExpressions:<br>              - key: region<br>                operator: In<br>                values:<br>                  - &quot;eu&quot;<br>                  - &quot;us&quot;  <br>                  - &quot;asia&quot;                      <br>        - git:<br>            repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>            revision: HEAD<br>            directories:<br>            - path: charts/* </pre><p>For each application, we query each cluster for its environments and region.</p><pre>sources:<br>  - repoURL: https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git<br>    path: &#39;{{.path.path}}&#39;<br>    targetRevision: HEAD<br>    helm:<br>      valueFiles:<br>      - &#39;$my-values/values/{{index .path.segments 1}}/common-values.yaml&#39;  <br>      - &#39;$my-values/values/{{index .path.segments 1}}/app-version/{{index .metadata.labels &quot;env&quot;}}-values.yaml&#39;              <br>      - &#39;$my-values/values/{{index .path.segments 1}}/env-type/{{index .metadata.labels &quot;env&quot;}}-values.yaml&#39;  <br>      - &#39;$my-values/values/{{index .path.segments 1}}/regions/{{index .metadata.labels &quot;region&quot;}}-values.yaml&#39;              <br>      - &#39;$my-values/values/{{index .path.segments 1}}/envs/{{index .metadata.labels &quot;env&quot;}}-{{index .metadata.labels &quot;region&quot;}}-values.yaml&#39;                  <br>  - repoURL: &#39;https://github.com/kostis-codefresh/multi-app-multi-value-argocd.git&#39;<br>    targetRevision: HEAD<br>    ref: my-values</pre><p>All charts are now deployed in all regions:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*wYcQKjCC5Vjftttb.png" /></figure><p>You can also verify that each environment picks the correct settings from the value hierarchy:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QOfU1YXfLxoEkd3K.png" /></figure><p>You have now seen how to apply value hierarchies with Application Sets and cluster labels.</p><h3>Day 2 operations</h3><p>We now reach the most important point of this guide. We’ve seen how cluster labels let you define exactly what goes into which cluster. You might be wondering why this is the recommended solution and how it’s better than other approaches you’ve seen.</p><p>The answer is that with cluster labels you treat your application sets in a “create-and-forget” function. After the initial set up, you shouldn’t need to touch your application sets at all. This means that maintenance effort is zero, which is always the OPTIMAL way of evaluating any architecture decision.</p><p>Let’s see some semi-realistic scenarios of using our recommendation in a real organization.</p><p>Imagine you just organized all your application sets with cluster labels. All files are committed in Git, and all applications are successfully deployed. Everything runs smoothly.</p><h3>Scenario A — Removing a server</h3><p>On Monday, you need to decommission the US/prod server. You remove the “us” and “prod” tags from the server. In the next sync, the cluster generator from <em>all </em>related appsets doesn’t pick it up and nothing gets deployed there. You don’t really care how many application sets touched this cluster. They will all stop deploying there automatically.</p><p>Changes you had to do in your Application Sets: <strong>ZERO</strong></p><h3>Scenario B — Deploying a new application</h3><p>On Tuesday, a developer wants a new application in the QA environment. You commit a new overlay for QA configuration for that app. All QA application sets pick it up in the next sync and deploy it to any/all clusters that deal with QA. You don’t really care how many application sets affect QA or how many clusters are contained in QA. They will all get the new application in the same manner.</p><p>Changes you had to do in your Application Sets: <strong>ZERO</strong></p><h3>Scenario C — Adding a new Cluster</h3><p>On Wednesday, you need to add a new cluster to replace the decommissioned one. You create the new cluster with Terraform/Pulumi/Crossplane/whatever and just assign it the appropriate tags (“us”,”prod”, “workload”,”aws”). All respective Application Sets see the new cluster in the next sync and deploy <em>whatever needs to be deployed there. </em>You don’t really care how many Application Sets touch this cluster. The cluster will get the exact same applications as it had before.</p><p>Changes you had to do in your Application Sets: <strong>ZERO</strong></p><h3>Scenario D — Copying an application</h3><p>On Thursday, a developer says that a specific application that exists in staging also needs to go to QA.</p><p>You copy the staging overlay to a QA overlay for this application and ask the developer about the correct settings. In the next sync, all the QA Application Sets pick it up and deploy it. The developer doesn’t need to know anything about application sets or cluster labels. In fact, they could just do this deployment on their own if they had access to the Kustomize overlays.</p><p>Changes you had to do in your Application Sets: <strong>ZERO</strong></p><h3>Scenario E — Central cluster change</h3><p>On Friday, you’re told that ALL your clusters now need <a href="https://github.com/bitnami-labs/sealed-secrets">sealed-secrets</a> installed. You add a new configuration for sealed secrets in your “common” folder and commit it to Git. Then the “Common” Application Set (that applies to all clusters) picks it up and applies it to all clusters.</p><p>Changes you had to do in your Application Sets: <strong>ZERO</strong></p><p>Essentially, the Application Sets only need to change when you need to add another dimension to your servers (i.e., new labels) for something that was not expected. If you completed a proper evaluation in the beginning, and communicated to all parties how all the servers are going to be used, then this scenario won’t happen very often. For daily operations, the Application Sets just sit in the Git repository without anybody (operators or developers) having to make any changes at all.</p><p>The other big advantage of cluster labels is that they work the same, regardless of how many servers you have. The Application Sets that work with labels will automatically update on their own, even if they manage 1, 10, or 100s of servers that you connect to the central Argo CD instance.<br>Let’s compare those same scenarios with the approach we do <strong>NOT </strong>recommend, where application sets explicitly enable/deactivate components/apps in each specific server.</p><pre>## Do not do this<br>- merge:<br>      mergeKeys:<br>        - app<br>      generators:<br>        - list:<br>            elements:<br>              - app: external-dns<br>                appPath: infra/helm-charts/external-dns<br>                namespace: dns<br>              - app: argocd<br>                appPath: infra/helm-charts/argocd<br>                namespace: argocd<br>              - app: external-secrets<br>                appPath: infra/helm-charts/external-secrets<br>                namespace: external-secrets<br>              - app: kyverno<br>                appPath: infra/helm-charts/kyverno<br>                namespace: kyverno<br>        - list:<br>            elements:<br>              - app: external-dns<br>                enabled: &quot;true&quot;<br>              - app: argocd<br>                enabled: &quot;true&quot;<br>              - app: external-secrets<br>                enabled: &quot;false&quot;<br>              - app: kyverno<br>                enabled: &quot;true&quot;<br>    selector:<br>      matchLabels:<br>        enabled: &quot;true&quot;</pre><p>What actions do you need for each scenario?</p><ul><li>Scenario A — Removing a server</li></ul><ol><li>You need first to locate all Application Sets that “choose” this server.</li><li>You then need to edit all application Sets and “deactivate” all the components they contain.</li><li>You need to commit and sync all changes.</li><li>There is a risk that you either forgot an application set or forgot to “deactivate” a line</li><li>The more servers you have, the more complex is the process</li></ol><ul><li>Scenario B — Deploying a new application</li></ul><ol><li>You first need to locate all Application Sets that choose the servers that need this application.</li><li>You need to edit all those application Sets and add a new line for this application.</li><li>You need to commit and sync all changes.</li><li>There’s a risk that you either forgot an application set or forgot to add a line for the new application.</li><li>The more servers you have, the more complex the process.</li></ol><ul><li>Scenario C — Adding a new Cluster</li></ul><ol><li>You need to understand how this cluster is “similar” to other clusters.</li><li>You either need to create a new Application Set for this cluster or locate all Application Sets that touch it.</li><li>You need to add all new lines of enabled/disabled components for this cluster.</li><li>There’s a risk that you either forgot an application set or forgot to add a line for the new application.</li></ol><ul><li>Scenario D — Copying an application</li></ul><ol><li>You need first to locate all Application Sets that choose the news servers for this application.</li><li>You need to edit all those application Sets and locate the line for this application and change it to “enabled”.</li><li>You need to commit and sync all changes.</li><li>There’s a risk that you either forgot an application set or forgot to “enable” the component.</li><li>The more servers you have, the more complex the process.</li></ol><ul><li>Scenario E — Central change</li></ul><ol><li>You need first to locate all Application Sets that you manage.</li><li>You need to edit all those application Sets and add a new line for this common application.</li><li>You need to commit and sync all changes.</li><li>There’s a risk that you either forgot an application set or forgot to add a line for the new application</li><li>The more servers you have, the more complex the process.</li></ol><p>It shouldn’t be a surprise that having snowflake servers where you must enable/deactivate each application individually is a much more complex process than working with cluster groups identified by labels.</p><h3>Developers and self-service</h3><p>At the start of this guide, we talked about effective communication with developers. Another major reason that makes cluster labels the optimal solution is that they’re fully automated. At each sync, the Argo CD cluster generator detects which clusters have the appropriate labels and does whatever needs to be done (deploy or undeploy an application).</p><p>Your developers don’t need to know anything about cluster labels. In fact, they don’t even need to know about Application Sets. Developers can work with a Git repository that holds standard Helm charts/Kustomize overlays/plain manifests, and their instructions are super simple:</p><ul><li>If they add a new overlay in the QA folder, then that application will be deployed in the QA environments regardless of the number of servers.</li><li>If they delete an overlay, that application will be undeployed.</li><li>If they want a brand new application, they can just commit the new overlays or Helm values in a specific folder, and Argo CD will pick it up.</li></ul><p>Developers can work on their own without opening any tickets or waiting for you to do something for them.</p><p>This comes in complete contrast to the anti-pattern we explained above, where you manually enable/deactivate applications in each specific cluster. If you follow this approach, all actions become a two-step process:</p><ol><li>The developer adds their overlay or Helm values in a Git repository.</li><li>Then you MUST go to all your Application Sets and manually “enable” the new application.</li></ol><p>Preventing developers from deploying their applications and waiting for you to do something is the fastest way to create bottlenecks in your organization.</p><p>There is no reason for this complexity when using cluster labels is a much better choice.</p><h3>Conclusion</h3><p>In this guide, we explained in detail how to create cluster groups with Argo CD using custom labels. We have also seen:</p><ul><li>How to use the cluster generator to select a cluster in an “AND” and “OR” fashion.</li><li>How to deploy applications to multiple clusters using the same configuration.</li><li>How to deploy Kustomize applications with different overlays per cluster.</li><li>How to deploy Helm applications with different value sets per cluster.</li><li>How to perform common day-2 operations with many Argo CD clusters.</li><li>Why our recommended approach is the optimal one, as the number of clusters and developers grows in your organization.</li></ul><p>You can find all Application Sets and manifests at <a href="https://github.com/kostis-codefresh/multi-app-multi-value-argocd">https://github.com/kostis-codefresh/multi-app-multi-value-argocd</a>.</p><p>Happy labeling 🙂</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b945c2903452" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/distribute-your-argo-cd-applications-to-different-kubernetes-clusters-using-application-sets-b945c2903452">Distribute Your Argo CD Applications to Different Kubernetes Clusters Using Application Sets</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Environments Beat Clusters For Dev Experience]]></title>
            <link>https://medium.com/containers-101/why-environments-beat-clusters-for-dev-experience-f6eef0cd928b?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/f6eef0cd928b</guid>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[developer-experience]]></category>
            <category><![CDATA[cloud]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Mon, 02 Jun 2025 08:44:41 GMT</pubDate>
            <atom:updated>2025-06-03T07:26:18.754Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Kw8MdTCsElA3SlgZ2TX5dw.jpeg" /></figure><p>The cloud ecosystem has reached a turning point. Tools for operators/administrators are now mature and can handle most day-to-day operations that deal with Kubernetes clusters. Finally, we can turn our focus to application developers and their needs.</p><p>If you look at all the Kubernetes tools available, you’ll understand that most of them treat Kubernetes as another form of infrastructure. You can easily find tools that install Kubernetes, monitor Kubernetes, secure Kubernetes, do cost estimations for Kubernetes, etc. But how many Kubernetes tools can you find that target application developers and their day-to-day responsibilities?</p><p>Several companies even try to hide Kubernetes completely from developers by using <a href="https://en.wikipedia.org/wiki/Leaky_abstraction">leaky abstractions</a> or so-called developer portals. These adoption efforts almost always fail simply because nobody asked the developers what they really need. Don’t fall into this trap.</p><p>In this article, we see some common examples of what companies “think” about developers’ needs versus what developers need in practice, in the context of application development for Kubernetes.</p><h3>Confusing clusters with environments</h3><p>When designing a deployment workflow, most teams center the discussion around individual clusters. You often hear people talk about direct mappings between clusters and “environments”.</p><ul><li>“This is our production cluster.”</li><li>“Our staging cluster is down.”</li><li>“We need a new cluster for QA.”</li></ul><p>This forces all Kubernetes tools to expose clusters as first-level constructs in their user interface (UI). If your team has created any kind of dashboard for Kubernetes, I can bet that the left-panel navigation contains a “cluster” entry where people can look at individual clusters.</p><p>In reality, developers never care about individual clusters. Most times, they care about:</p><ul><li>Cluster groups that behave the same (e.g., prod-us, prod-asia, prod-eu).</li><li>A set of namespaces within a big cluster (most common in shared qa/staging clusters).</li><li>A combination of the above.</li></ul><p>Developers have a different mindset:</p><ul><li>“I am ready to ship this feature to production.”</li><li>“Oh, my new feature is failing in the QA environment.”</li><li>“That is strange, application 1.23 works ok in QA, but presents an error in the staging environment.”</li></ul><p>So, how many clusters are in production, QA, or staging? It does NOT matter to developers. Folks only think that developers care about environment settings and, more specifically, what differs between environment configurations.</p><p>This means that if your internal portal/developer platform looks like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*G3PKDxdeLFIv3W9L.png" /></figure><p>You need to redesign it like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*__SoW9i2XNNBtkW6.png" /></figure><p>Let me repeat that again. Developers do NOT care about individual Kubernetes clusters. They mostly care about the different settings between the cluster groups that represent each “environment”.</p><h3>Promotions are more important than deployments</h3><p>We’ve established that developers prefer thinking about environments and not individual clusters. Let’s see another common misconception with tools that target developers. If you look at the most typical scenario of how a feature reaches production, this is the process:</p><ol><li>A developer performs an initial <strong>deployment </strong>in the first environment — let’s call it QA environment.</li><li>After passing several tests and reviews, the feature gets <strong>promoted </strong>to the staging environment.</li><li>After passing several tests, the feature gets <strong>promoted </strong>to production.</li><li>Depending on the company, there might be several other intermediate environments where <strong>promotions </strong>happen (e.g., load testing).</li></ol><p>Several tools promise to simplify deployments for Kubernetes developers. It turns out that developers are actually interested in promotions. There are 3 main reasons for that:</p><ol><li>A deployment (where code is packaged in a brand new artifact) happens only once in the first environment. In all the subsequent environments, developers want to promote an existing image/configuration/release/artifact and not deploy anything from scratch</li><li>Problems with promotions have more impact. Promoting to production is always a risky process. Deploying to QA is not.</li><li>Promotions can often fail due to external configuration not directly controlled by developers.</li></ol><p>The last point cannot be overstated. One of the most common cases for failed deployments in production is the difference in environment configuration. Developers dutifully tested their application in QA and staging, and everything worked fine. Then the application failed to deploy in production because of an unexpected change in production settings completely outside the scope of the application container.</p><p>Continuing our wireframe from an imaginary developer portal, most teams think that developers need this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*T8ux5j4I9IF11EHM.png" /></figure><p>The UI is problematic for many reasons:</p><ol><li>Developers can deploy an older version to any environment by mistake.</li><li>Developers need to manually correlate versions between environments and understand what was in the next/previous environment.</li><li>Most times, versions correspond to Docker image versions that exclude external configuration.</li></ol><p>But as explained already, developers just want to promote. So they would prefer this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ypdChCXQKqguCx37.png" /></figure><p>Notice that this makes a developer’s job very easy. There are also several guardrails in place. At a minimum, the production drop-down can ONLY promote what is currently in staging and nothing else (or maybe the last 3 versions that are available in staging). You can also perform checks and present warnings in several scenarios (for example, if a developer tries to promote something to production that is clearly failing in staging).</p><h3>Developers don’t care about Git hashes</h3><p>Git hashes are great when it comes to source code operations. When developers perform basic merges, cherry-picks, and rebase scenarios, they do care about Git hashes. But when it comes to deployments, Git hashes mean nothing:</p><ul><li>Git hash names have no specific order. You cannot look at 2 hashes and understand which is newer/older.</li><li>Git hashes can only capture the state of a source code snapshot or the specific commit for Kubernetes manifests, but never both at the same time.</li><li>They allow for mistakes when it comes to deployments, especially when developers have to copy/paste hashes between different tools.</li></ul><p>What developers care about instead is software versions. Version numbers are simple to read, simple to reason, and simple to understand.</p><p>Products and dashboards that expose Git hashes as a central concept are cumbersome and difficult to use. It’s ok if Git hashes are an additional piece of information for a deployment, but they should never be used for direct promotions or other day-to-day operations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*8TyeLf58V2g7zx4h.png" /></figure><p>What developers really want to see are versions. Or, at least some kind of numbering where ordering is easy to understand.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*r40QXkA57Jg5see8.png" /></figure><p>So, where do these versions come from?</p><p>Here we reach one of the biggest misconceptions about container images for Kubernetes. Several tools (and dashboards) simply use a special tag with a version number on a container that gets “promoted” from each environment to the next. Using a container image version for the promotion process might make sense at first glance.</p><p>However, this approach misses 2 facts completely:</p><ul><li>Docker tags are <strong>mutable by default</strong>. Just because you see a container called my-image:v1.0 in one environment and another container my-image:v1.0 in another environment, that doesn’t mean they’re the same application. They might be completely different.</li><li>Used as an application version, the container tag only works in 80% of cases. Developers sometimes need to promote a container image AND associated configuration (like configmaps and secrets). In those cases, the container tag is NOT enough to understand what gets promoted and where.</li></ul><p>You can partially solve the first problem with tooling support. You need to instruct ALL tools that take part in the software lifecycle to treat container tags as immutable. This is also a very basic requirement for any package registry that your organization is using.</p><p>You can solve the second problem with Helm charts. Helm charts give you access to 2 additional “version” properties (in addition to your container tag). You can annotate a Helm chart with an “application” version and also have a different version for the chart itself. This way, when you promote a Helm chart, you can promote a container image PLUS additional configuration in a single step.</p><p>However, not all organizations use Helm charts. Several teams prefer to use Kustomize or even plain manifests. In this case, the versioning problem still exists.</p><p>Unfortunately, most tools assume that developers only care about container images and center their whole interface around image tags.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Trw8R_rI4GpAbX-f.png" /></figure><p>Developers would prefer a system that lets them promote configuration and container tags at the same time. This would cover 100% of their needs and cater even to edge case scenarios where they only promote configuration, while the container image stays the same.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Q8ZXAWDecheRR0za.png" /></figure><p>Tools that expose Git hashes and assume developers only work with container tags completely miss the way Kubernetes applications work.</p><h3>Don’t abuse pipelines as promotion mechanisms</h3><p>Many developers see Continuous Delivery (CD) as the next evolution of Continuous Integration (CI). After all, before you deploy a container image, you need to build and test it first.</p><p>Several teams that switch to cloud-native development make their first step in CD by abusing their existing CI pipelines. Developers love to see a single pipeline that shows the whole picture for a specific feature, from the initial code commit all the way through to production.</p><p>The main problem here is that the typical CI pipeline only knows what is happening WHILE it’s running. After it finishes, it has no visibility into the actual cluster.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*7MoEpBl58FoJ373m.png" /></figure><p>This leads to the classic problem of failed deployments in the following manner:</p><ol><li>A developer commits a new feature (or merges something in a branch).</li><li>The CI pipeline starts and then builds/tests a container image with success.</li><li>The CI pipeline deploys the image to a Kubernetes cluster and optionally runs some checks.</li><li>Everything looks good, and the pipeline shows its status as “green”.</li><li>Ten minutes later, the application has issues (memory leaks, wrong dependencies, missing DB).</li><li>Developers get paged about a failed deployment, even though the pipeline STILL shows as green.</li></ol><p>Your organization probably falls into this trap if developer teams always talk about “lack of deployment visibility”, “wasting time to troubleshoot deployments”, “not enough production access”, and similar complaints.</p><p>In reality, developers look at the CI system for the basic build. Then they need to go to another system (usually a metrics/monitoring solution) to understand what’s happening with their application.</p><p>The key takeaway here is that instead of a basic CI pipeline, you need a system that gives developers real-time information about deployments and promotions. In that system, the “green” status means that the application is healthy RIGHT NOW, not 5 minutes before.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*tx4OqIeP3fhgR1Mc.png" /></figure><p>Now developers can use a single interface for deploying/promoting, and understanding if the application is healthy. They can go to their metrics solution when things go wrong, but in the happy path scenario, a single system can tell them if the application is successfully running in a Kubernetes cluster (or a specific environment, as we saw earlier in the article).</p><h3>Stop deploying and start promoting</h3><p>You should now understand what developers actually need and why existing solutions aren’t designed with Kubernetes/GitOps in mind. There are several initiatives right now for investing in developer portals in big organizations, and, unfortunately, developers don’t always have a say about exactly what they need from an internal platform.</p><p>The next question is whether you actually need to create a platform like this from scratch. You might think that Argo CD is a solution that helps developers with Kubernetes deployments and that simply adopting Argo CD will make developers happy. In reality, Argo CD is a great sync engine, but doesn’t try to solve changes between applications, promotions, or environments. For example, Argo CD doesn’t have the concept of environments, instead only operating with individual clusters.<br>This is why we extended Argo CD to create <a href="https://codefresh.io/blog/introducing-codefresh-gitops-cloud/">Codefresh GitOps Cloud</a>. We looked at existing solutions and understood that developer experience is always an afterthought, even in newer platforms that are supposedly designed with Kubernetes in mind.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5zvO_4oXbp1hUTtv.png" /></figure><p>Codefresh GitOps Cloud implements all the best patterns we explained that developers need:</p><ol><li>It works with <a href="https://codefresh.io/blog/introducing-the-worlds-first-dashboard-for-gitops-environments/">environments </a>instead of individual clusters. Each environment can be one cluster, a set of clusters, a set of namespaces, application labels, or any combination of those.</li><li>It’s based around promotions. Developers can easily understand what’s different between 2 environments and how to move an application from one environment to the next.</li><li>Git hashes are there if you need them. The central construct, however, <a href="https://codefresh.io/blog/argocd-promotions-with-products/">is products and their versions</a>. A product is a new entity that includes an application along with its configuration and its container images. When you promote from one environment to another, <a href="https://codefresh.io/blog/argocd-gitops-versions/">you promote the whole application</a> and not individual container images.</li><li>The graphical dashboard always shows real-time information. When you see a “green” checkmark, it means that an application is running successfully in an environment right now. Developers can detect right away what deployment was successful and what failed without going to another system.</li><li><a href="https://codefresh.io/blog/codefresh-software-delivery-platform-and-the-argo-project/">Argo CD</a> and its amazing sync engine power everything behind the scenes.</li></ol><p>You can use Codefresh GitOps Cloud today along with your existing CI system (like Jenkins and GitHub Actions). GitOps Cloud doesn’t replace your CI solution. It makes developers happy by giving them a dedicated platform for application promotions, which is what developers really need (instead of playing deployments).</p><p>Oh, one more thing. If you already have <a href="https://codefresh.io/blog/bring-your-own-argocd/">your own Argo CD instance, you can bring it along</a>!</p><p>Ready to start your GitOps journey with Codefresh? Try GitOps Cloud <a href="https://codefresh.io/codefresh-signup/">free for 45 days now</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f6eef0cd928b" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/why-environments-beat-clusters-for-dev-experience-f6eef0cd928b">Why Environments Beat Clusters For Dev Experience</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Helm Hierarchies in Multi-Source Argo CD Applications for Promoting to Different GitOps…]]></title>
            <link>https://medium.com/containers-101/using-helm-hierarchies-in-multi-source-argo-cd-applications-for-promoting-to-different-gitops-133c3bc93678?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/133c3bc93678</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[argo-cd]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[helm]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Wed, 30 Oct 2024 14:26:29 GMT</pubDate>
            <atom:updated>2024-10-30T14:26:28.908Z</atom:updated>
            <content:encoded><![CDATA[<h3>Using Helm Hierarchies in Multi-Source Argo CD Applications for Promoting to Different GitOps Environments</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eu20CLzkzStkjwvl-f2JCg.png" /></figure><p>Two of the most popular guides we’ve written are the <a href="https://codefresh.io/blog/how-to-model-your-gitops-environments-and-promote-releases-between-them/">GitOps promotion guide</a> and the <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">ApplicationSet guide</a>. Used together, they explain an end-to-end solution for organizing your GitOps applications and promoting them between different environments, while keeping things <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY </a>by using application sets.</p><p>Both of these guides use Kustomize. We offered some hints for Helm users, but this is a dedicated guide for Helm applications. Organizations that choose Helm have several other challenges to overcome when adopting GitOps. This is because you can also store Helm charts in dedicated Helm repos (apart from Git). Helm charts have versions on their own.</p><p>Unfortunately, we’ve seen teams try to replicate features in Argo CD that Helm already supports. Or, some teams try to adopt Argo CD without first understanding the basics of Helm.</p><p>In this guide, we include several Helm-related topics to consider when adopting GitOps and Argo CD in your organization.</p><p>We describe:</p><ul><li>The recommended Helm structure for GitOps repositories</li><li>When to use the <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/multiple_sources/">multi-source feature of Argo CD</a> and when <strong>not </strong>to use it</li><li>How to create Helm value hierarchies and why this is important</li><li>Common Helm bad practices and misconceptions that people carry over to Argo CD</li></ul><p>If you like our guides but wonder how our advice works with Helm and not Kustomize, then this guide is for you!</p><p>You can find the full example at <a href="https://github.com/kostis-codefresh/multi-sources-example">https://github.com/kostis-codefresh/multi-sources-example</a>.</p><h3>How Helm values work</h3><p>The original design for Helm is to have a generic chart (without any hardcoded configuration) and then use several different value files that hold environment-specific configuration. We’ve seen this scenario in detail in <a href="https://codefresh.io/blog/helm-deployment-environments/">our guide about Helm values</a>. It’s also possible to have default values for a chart if no external values are added.</p><p>You need to decide if your Helm chart will map to a specific application or not. One approach is to have a Helm chart for every application you develop. In this case, the Helm chart has 2 versions. One version keeps a history about the chart itself and the second version (appVersion in the Chart.yml file) keeps track of the application contained in the chart.</p><p>With the second approach, you have <a href="https://github.com/gimlet-io/onechart">a reusable “single” chart</a> that’s targeted at many applications. It doesn’t hold a specific application on its own. You always need to combine it with values that define the image/tag/configuration.</p><p>Both approaches are valid and Argo CD supports both. It makes sense, however, to select a single strategy for your organization.</p><p>Assuming the classic trinity of QA/Staging/Prod you should be able to easily install any chart with the appropriate configuration, like this:</p><pre>helm install ./my-chart/ --generate-name -f values-qa.yaml<br>helm install ./my-chart/ --generate-name -f values-staging.yaml<br>helm install ./my-chart/ --generate-name -f values-production.yaml</pre><p>In this very simple example, the charts <a href="https://github.com/codefresh-contrib/helm-promotion-sample-app/tree/master/chart">are saved in Git as normal files</a> along with their possible configurations.</p><h3>Creating reusable configuration with Helm value hierarchies</h3><p>The structure explained above works for most organizations. In some cases, however, the application has a complex configuration you need to define for a large number of environments. You could duplicate the values file for each environment, but this would create a cumbersome setup where you need to change common configuration between some environments.</p><p>To overcome this challenge, Helm also supports <strong>value hierarchies</strong>. This means that instead of defining a single value file when installing a chart, you can define multiple. Helm then merges all value files, and for properties defined multiple times, the last file wins.</p><p>This means that you can do:</p><pre>helm install ./my-chart/ — generate-name -f common.yaml -f more.yaml -f some-more.yaml</pre><p>If you have properties that are both in common.yaml and in more.yaml, the latter will prevail. In the same way, all properties defined in some-more.yaml will function as “overrides” for everything else you defined in the previous files.</p><p>The obvious advantage of this pattern is that you can create a hierarchy of Helm value files where “early” files contain common values for most environments and “late” files contain specific values for individual deployments.</p><p>A possible setup would be:</p><pre>common-values.yaml<br>+-----all-prod-envs.yaml<br>    +----specific-prod-cluster.yaml</pre><p>This means common-values get shared between ALL your deployments unless they’re overridden in “all-prod-envs” for all your production environments, which can also be overridden by “specific-prod-cluster.yaml” for a specific cluster.</p><p>To make this work, you need to apply all files in the expected order — remembering the last one wins.</p><pre>helm install ./my-chart/ — generate-name -f common-values.yaml -f all-prod-envs.yaml -f specific-prod-cluster.yaml</pre><p>If you’re using Helm for your own applications, it’s essential you create an appropriate hierarchy for all your Helm applications that balances flexibility with minimal duplication.</p><p>It’s also important to remember that Helm has these hierarchies built-in. They’re unrelated to Argo CD.</p><h3>Using Helm value hierarchies with Argo CD</h3><p>After spending time designing your hierarchy of Helm values, you’re now ready to use Argo CD. The good news is that Argo CD <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/helm/#values-files">supports Helm hierarchies out-of-the-box</a> without any additional configuration.</p><p>Here’s an example application:</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>metadata:<br>  name: my-app<br>  namespace: argocd<br>spec:<br>  project: default<br>  source:<br>    chart: my-chart<br>    repoURL: https://github.com/my-example-app<br>    targetRevision: 2.43<br>    helm:<br>      valueFiles:<br>      - common-values.yaml<br>      - all-prod-envs.yaml            <br>      - specific-prod-cluster.yaml<br>  destination:<br>    server: &quot;https://kubernetes.default.svc&quot;<br>    namespace: default </pre><p>The valueFiles property accepts an array of value files. Like the Helm CLI, each file mentioned in the list overrides all values from the previous files. The last file wins over the previous ones.</p><p>Why is this important? We’ve seen several teams try to replicate this pattern of default/environment-specific values using several hacks and workarounds, as we described<a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/"> in the previous guide</a>. Usually, the teams that use these workarounds aren’t aware how Helm value hierarchies work.</p><p>Don’t fall into the trap of adding another layer of templating to your applications or appsets just to replicate Helm value hierarchies.</p><h3>Using local Helm values with charts from Helm repositories</h3><p>All the previous examples assume your Helm chart and its values are in Git. That’s always our recommendation, especially for your own applications. Some organizations, however, prefer to store charts in Helm repositories or OCI registries. Argo CD supports this scenario, but you need to understand the implications:</p><ul><li>Helm repositories (and OCI artifacts) are mutable. This means that you might download Helm chart 1.2.3 today with different contents than what you downloaded yesterday as version 1.2.3. And, of course, this <a href="https://opengitops.dev/">breaks the GitOps principles</a>.</li><li>There’s no built-in auditing or history for changes made in the chart. If you use Git you get these features for “free”.</li><li>There are risks regarding integrity and security. Can you verify that the chart you downloaded from the registry is the one the creator uploaded? Again, if you use your own Git repo, you can monitor and optionally approve all changes that happen on the chart.</li></ul><p>If you insist on loading charts from external repositories, you should still keep your own values in your own Git repository. Argo CD can then merge the two <a href="https://argo-cd.readthedocs.io/en/latest/user-guide/multiple_sources/">using multiple sources</a>.</p><p>Multiple sources is a feature of Argo CD created specifically for this scenario. In the most basic case, you should define 2 sources. One contains your external Helm chart and the other your Helm values.</p><p>Like before, you can also have Helm hierarchies.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>metadata:<br>  name: my-fancy-app<br>  namespace: argocd<br>spec:<br>  project: default<br>  destination:<br>    server: https://kubernetes.default.svc<br>    namespace: prod-us<br>  sources:<br>    - repoURL: https://acme.github.io/my-charts/<br>      chart: my-app<br>      targetRevision: 2.43<br>      helm:<br>        valueFiles:<br>        - $values/common-values.yaml      <br>        - $values/all-prod-envs.yaml                <br>        - $values/specific-prod-cluster.yaml<br>    - repoURL: &#39;https://github.com/example-org/all-my-values.git&#39;<br>      targetRevision: HEAD<br>      ref: values</pre><p>Notice here the relationship between the “ref:values” field and the “$values” prefix in the helm property. Of course, you can choose any name for the reference and not just “values”.</p><p>It should be clear that with this syntax, Argo CD has built-in support for Helm value hierarchies. Argo CD will load the chart from the external source and combine it with all the value files defined in order (last one wins).</p><p>On a related note, don’t abuse the multiple source feature of Argo CD. It’s NOT a generic way to group applications. Usually, you should have just 2 sources (as shown below) of related data that form a single application. If you find yourself grouping unrelated apps or having more than 2 to 3 sources, you need to look at app-of-apps and Application Sets instead.</p><h3>Using Application Sets with Helm value hierarchies</h3><p>If you deploy your Helm charts as an individual Argo CD application, you should do the same for your Application Sets. We covered Application Sets in detail <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">in our previous guide</a>.</p><p>Again, the key point here is that Application Sets also support Helm value hierarchies. And, like everything else in the application, you can template your chart values, folder, revision, or anything else you want.</p><p>Application Sets also work with multiple sources. So we can combine everything that we have seen so far in a single Application Set, like this:</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: ApplicationSet<br>metadata:<br>  name: my-fancy-apps<br>  namespace: argocd<br>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - list:<br>      elements:<br>      - env: qa<br>        type: non-prod           <br>      - env: prod-eu <br>        type: prod      <br>      - env: prod-us<br>        type: prod      <br>  template:      <br>    metadata:<br>      name: &#39;{{.env}}&#39;<br>    spec:<br>      # The project the application belongs to.<br>      project: default<br><br><br>      sources:<br>        - repoURL: https://acme.github.io/my-charts/<br>          chart: my-chart<br>          targetRevision: 2.43<br>          helm:<br>            valueFiles:<br>            - $values/common-values.yaml          <br>            - $values/all-{{.type}}-envs.yaml              <br>            - $values/{{.env}}-values.yaml  <br>        - repoURL: &#39;https://github.com/example-org/all-my-values.git&#39;<br>          targetRevision: HEAD<br>          ref: values<br>     <br>      # Destination cluster and namespace to deploy the application<br>      destination:<br>        server: https://kubernetes.default.svc<br>        namespace: &#39;{{.env}}&#39;</pre><p>This Application Set defines 3 environments (qa, prod-eu, prod-us). It uses Helm hierarchies and an external chart.</p><p>Notice that we can template the location of all value files without any additional templating solution.</p><p>We see many teams who place their application CRDs (or applicationSet CRDs) inside Helm charts to template them on a second level. This complexity isn’t needed. Application Sets on their own cover most common scenarios. Application Sets are a way of templating (the same as Helm). Using Helm to generate Application Sets (or Applications) is a recipe for disaster.</p><p>If there’s something you can’t template with Application Sets, it’s <a href="https://github.com/argoproj/argo-cd/issues">better to open an enhancement request</a> instead of bringing yet another layer of templating into the mix.</p><h3>A complete example of GitOps environments with Helm values</h3><p>Let’s see how everything works with a semi-realistic example. You can find all the Kubernetes manifests and Argo CD resources at <a href="https://github.com/kostis-codefresh/multi-sources-example">https://github.com/kostis-codefresh/multi-sources-example</a>.</p><p>The repository contains:</p><ul><li>A generic Helm chart both in Git and as part of a Helm repo served by GitHub pages at <a href="https://kostis-codefresh.github.io/multi-sources-example/">https://kostis-codefresh.github.io/multi-sources-example/</a></li><li><a href="https://github.com/kostis-codefresh/multi-sources-example/tree/main/my-values">Helm values</a> for different environments of the chart</li><li>Example <a href="https://github.com/kostis-codefresh/multi-sources-example/tree/main/appsets">applicationsets </a>that reference the Helm chart</li><li><a href="https://github.com/kostis-codefresh/multi-sources-example/tree/main/example-apps">Example apps</a> that use Helm hierarchies</li></ul><p>The environments are similar to those defined <a href="https://codefresh.io/blog/how-to-model-your-gitops-environments-and-promote-releases-between-them/">in the promotion guide</a>, but this time we use Helm values instead of Kustomize overlays.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/302/0*-k-uHDhHaNv0jl9P.png" /></figure><p>We have the following environments:</p><ul><li>Integration testing (non-GPU and GPU-enabled)</li><li>QA (only one instance, located in the US)</li><li>Staging in EU, Asia, US</li><li>Production only in EU and US</li></ul><p>Similar to <a href="https://kubectl.docs.kubernetes.io/guides/config_management/components/">Kustomize reusable components</a>, we also have a structure of reusable Helm values that you can use in a hierarchical manner.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/308/0*q7NPbgD9mCsZ9ttp.png" /></figure><p>The expectation is that to form a full Helm release, you need to combine multiple value files in order.</p><p>For example, to deploy the application to the “Staging” environment in the EU region, you need to do the following with plain Helm:</p><pre>helm install ./my-chart/ --generate-name --create-namespace -n staging-eu \<br>-f my-values/common-values.yaml \<br>-f my-values/app-version/staging-values.yaml \<br>-f my-values/env-type/non-prod-values.yaml \<br>-f my-values/regions/eu-values.yaml \<br>-f my-values/envs/staging-eu-values.yaml</pre><p>Remember that the last setting wins. You can always override the common settings with different configurations with files deeper in the tree.<br>Of course, we don’t want to use Helm, but install the same hierarchy with Argo CD. Here’s the <a href="https://github.com/kostis-codefresh/multi-sources-example/blob/main/example-apps/staging-eu-app.yaml">respective Argo CD application</a>.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: Application<br>metadata:<br>  name: staging-eu-app<br>  namespace: argocd<br>  finalizers:<br>    - resources-finalizer.argocd.argoproj.io    <br>spec:<br>  project: default<br>  destination:<br>    server: https://kubernetes.default.svc<br>    namespace: staging-eu<br>  sources:<br>    - repoURL: https://github.com/kostis-codefresh/multi-sources-example.git<br>      path: my-chart<br>      targetRevision: HEAD<br>      helm:<br>        valueFiles:<br>        - $values/my-values/common-values.yaml          <br>        - $values/my-values/app-version/staging-values.yaml              <br>        - $values/my-values/env-type/non-prod-values.yaml  <br>        - $values/my-values/regions/eu-values.yaml                <br>        - $values/my-values/envs/staging-eu-values.yaml<br>    - repoURL: &#39;https://github.com/kostis-codefresh/multi-sources-example.git&#39;<br>      targetRevision: HEAD<br>      ref: values<br>  syncPolicy:<br>    syncOptions:<br>      - CreateNamespace=true  <br>    automated:<br>      prune: true<br>      selfHeal: true</pre><p>Notice again the hierarchy of value files. The file also follows the best practices outlined in <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">our Application Set guide</a>.</p><p>If you deploy this application with Argo CD, you can easily verify that the settings are correctly defined according to all the values “merged” in the correct order (the last one wins in case of duplicate properties).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_e-3PJ3BQB-l79bK.png" /></figure><p>That is great. But as we have said many times before, you shouldn’t deal with individual applications. We can also use Helm hierarchies with Application Sets.</p><p>There are many ways to slice and dice your apps. The following examples just show the possible combination of Helm hierarchies.</p><h3>Application Set on a single dimension</h3><p>Here’s a very simple Application Set that includes a <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Generators-List/">list generator</a> to model the regions for a single application.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: ApplicationSet<br>metadata:<br>  name: my-staging-appset<br>  namespace: argocd<br>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - list:<br>      elements:<br>      - region: us<br>      - region: eu<br>      - region: asia      <br>  template:      <br>    metadata:<br>      name: &#39;staging-{{.region}}&#39;<br>    spec:<br>      # The project the application belongs to.<br>      project: default<br><br><br>      sources:<br>        - repoURL: https://github.com/kostis-codefresh/multi-sources-example.git<br>          path: my-chart<br>          targetRevision: HEAD<br>          helm:<br>            valueFiles:<br>            - $values/my-values/common-values.yaml          <br>            - $values/my-values/app-version/staging-values.yaml              <br>            - $values/my-values/env-type/non-prod-values.yaml  <br>            - $values/my-values/regions/{{.region}}-values.yaml                <br>            - $values/my-values/envs/staging-{{.region}}-values.yaml<br>        - repoURL: &#39;https://github.com/kostis-codefresh/multi-sources-example.git&#39;<br>          targetRevision: HEAD<br>          ref: values<br>     <br>      # Destination cluster and namespace to deploy the application<br>      destination:<br>        server: https://kubernetes.default.svc<br>        namespace: &#39;staging-{{.region}}&#39;<br><br><br>      # Sync policy<br>      syncPolicy:<br>        syncOptions:<br>          - CreateNamespace=true  <br>        automated:<br>          prune: true<br>          selfHeal: true</pre><p>Here, the Helm value locations are templated according to the application’s region. An alternative is to use the list generator for a list of apps, clusters, or environment types.Deploying this application set creates 3 applications, one for each region.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*bqBORfwu62tFs3jC.png" /></figure><p>Again, you can easily verify that each deployment gets the appropriate settings according to the respective Helm value hierarchy.</p><h3>Application Set with multiple templated values</h3><p>In the previous example, we only looped on the “region” property of the value folders. We can use the list generator to <a href="https://github.com/kostis-codefresh/multi-sources-example/blob/main/appsets/2-many-keys-list/all-my-envs-appset.yaml">loop over several different properties</a>.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: ApplicationSet<br>metadata:<br>  name: all-my-envs<br>  namespace: argocd<br>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - list:<br>      elements:<br>      - env: integration-gpu<br>        region: us<br>        type: non-prod<br>        version: qa<br>      - env: integration-non-gpu<br>        region: us<br>        type: non-prod<br>        version: qa<br>[...snip…]    <br>      - env: prod-us<br>        region: us<br>        type: prod<br>        version: prod        <br>  template:      <br>    metadata:<br>      name: &#39;{{.env}}&#39;<br>    spec:<br>      # The project the application belongs to.<br>      project: default<br><br><br>      sources:<br>        - repoURL: https://github.com/kostis-codefresh/multi-sources-example.git<br>          path: my-chart<br>          targetRevision: HEAD<br>          helm:<br>            valueFiles:<br>            - $values/my-values/common-values.yaml          <br>            - $values/my-values/app-version/{{.version}}-values.yaml              <br>            - $values/my-values/env-type/{{.type}}-values.yaml  <br>            - $values/my-values/regions/{{.region}}-values.yaml                <br>            - $values/my-values/envs/{{.env}}-values.yaml<br>        - repoURL: &#39;https://github.com/kostis-codefresh/multi-sources-example.git&#39;<br>          targetRevision: HEAD<br>          ref: values<br>     <br>      # Destination cluster and namespace to deploy the application<br>      destination:<br>        server: https://kubernetes.default.svc<br>        namespace: &#39;{{.env}}&#39;</pre><p>In the example above, each deployment depends on different factors according to the application version, if it’s production or not, its region, and so on.</p><p>This application set actually creates ALL our applications in a single step.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Y--voahuHe7FWsL_.png" /></figure><p>Again, if you look at the resources of each individual application, you see each one has a different configuration according to all Helm values “merged” in the file tree.</p><h3>Using a Helm repository with a hierarchy of Helm values</h3><p>In all the previous examples, we used the Helm chart from Git, which is also our recommendation. However, we know some teams like to use OCI registries or Helm repositories. You can modify the previous Application Set and refer <a href="https://github.com/kostis-codefresh/multi-sources-example/blob/main/appsets/3-with-helm-repo/all-my-envs-from-repo-appset.yaml">to a Helm repository instead of a Git folder</a> for the Helm chart.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: ApplicationSet<br>metadata:<br>  name: all-my-envs-from-repo<br>  namespace: argocd<br>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - list:<br>      elements:<br>      - env: integration-gpu<br>        region: us<br>        type: non-prod<br>        version: qa<br>[...snip…]<br>      - env: prod-us<br>        region: us<br>        type: prod<br>        version: prod        <br>  template:      <br>    metadata:<br>      name: &#39;{{.env}}&#39;<br>    spec:<br>      # The project the application belongs to.<br>      project: default<br><br><br>      sources:<br>        - repoURL: https://kostis-codefresh.github.io/multi-sources-example<br>          chart: my-chart<br>          targetRevision: 0.1.0<br>          helm:<br>            valueFiles:<br>            - $values/my-values/common-values.yaml          <br>            - $values/my-values/app-version/{{.version}}-values.yaml              <br>            - $values/my-values/env-type/{{.type}}-values.yaml  <br>            - $values/my-values/regions/{{.region}}-values.yaml                <br>            - $values/my-values/envs/{{.env}}-values.yaml<br>        - repoURL: &#39;https://github.com/kostis-codefresh/multi-sources-example.git&#39;<br>          targetRevision: HEAD<br>          ref: values<br>     <br>      # Destination cluster and namespace to deploy the application<br>      destination:<br>        server: https://kubernetes.default.svc<br>        namespace: &#39;{{.env}}&#39;<br><br><br>      # Sync policy<br>      syncPolicy:<br>        syncOptions:<br>          - CreateNamespace=true  <br>        automated:<br>          prune: true<br>          selfHeal: true</pre><p>You can see that we now specifically define the 0.1.0 chart <a href="https://kostis-codefresh.github.io/multi-sources-example/">from a Helm repo hosted in GitHub pages</a> instead of loading the chart from a Git revision.</p><p>Other than that, the application set is exactly the same. The end result after deployment is also the same as the previous example.</p><h3>Saving environment configurations on their own files</h3><p>The last example has a very long list generator. As you add more environments, the list will become even longer. It’s far easier to <a href="https://github.com/kostis-codefresh/multi-sources-example/blob/main/appsets/4-final/all-my-envs-appset-with-version.yaml">extract this useful information in individual files</a>. We can use the <a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/applicationset/Generators-Git/#git-generator-files">Git file generator to achieve this</a>.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: ApplicationSet<br>metadata:<br>  name: all-my-envs-from-repo-with-version<br>  namespace: argocd<br>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - git:<br>      repoURL: https://github.com/kostis-codefresh/multi-sources-example.git<br>      revision: HEAD<br>      files:<br>      - path: &quot;appsets/4-final/env-config/**/config.json&quot;      <br>  template:      <br>    metadata:<br>      name: &#39;{{.env}}&#39;<br>    spec:<br>      # The project the application belongs to.<br>      project: default<br><br><br>      sources:<br>        - repoURL: https://kostis-codefresh.github.io/multi-sources-example<br>          chart: my-chart<br>          targetRevision: &#39;{{.chart}}&#39;<br>          helm:<br>            valueFiles:<br>            - $values/my-values/common-values.yaml          <br>            - $values/my-values/app-version/{{.version}}-values.yaml              <br>            - $values/my-values/env-type/{{.type}}-values.yaml  <br>            - $values/my-values/regions/{{.region}}-values.yaml                <br>            - $values/my-values/envs/{{.env}}-values.yaml<br>        - repoURL: &#39;https://github.com/kostis-codefresh/multi-sources-example.git&#39;<br>          targetRevision: HEAD<br>          ref: values<br>     <br>      # Destination cluster and namespace to deploy the application<br>      destination:<br>        server: https://kubernetes.default.svc<br>        namespace: &#39;{{.env}}&#39;</pre><p>In this example, we extracted all environment configuration in <a href="https://github.com/kostis-codefresh/multi-sources-example/tree/main/appsets/4-final/env-config">config.json files</a>. We also templated the chart version, so we can define differently for each environment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/292/0*ImYEaXX07bmSOy5u.png" /></figure><p>This lets you edit each environment individually. For example, if we want chart version 0.1.0 in all production environments and 0.2.0 in all other environments.</p><p>Here is the configuration for prod-us:</p><pre>{<br>    &quot;env&quot;: &quot;prod-us&quot;,<br>    &quot;region&quot;: &quot;us&quot;,<br>    &quot;type&quot;: &quot;prod&quot;,<br>    &quot;version&quot;: &quot;prod&quot;,<br>    &quot;chart&quot;: &quot;0.1.0&quot;<br>  }</pre><p>And the one for QA:</p><pre>{<br>    &quot;env&quot;: &quot;qa&quot;,<br>    &quot;region&quot;: &quot;us&quot;,<br>    &quot;type&quot;: &quot;non-prod&quot;,<br>    &quot;version&quot;: &quot;qa&quot;,<br>    &quot;chart&quot;: &quot;0.2.0&quot;<br>  }</pre><p>Now you have a very flexible Argo CD setup that can cover most scenarios with minimal effort.</p><p><strong>Scenario 1</strong> — Bootstrap all apps in a single step -&gt; Deploy the Application Set</p><p><strong>Scenario 2 </strong>— Add a new setting to all European deployments -&gt; change the file my-values/regions/eu-values.yaml</p><p><strong>Scenario 3</strong> — Making a common change to all environments apart from production -&gt; change the file my-values/env-type/non-prod-values.yaml</p><p><strong>Scenario 4</strong> — Change chart version in Production US -&gt; change the file env-config/prod/us/config.json</p><p><strong>Scenario 5</strong> — Make the QA environment inherit settings from Europe region instead of US -&gt; change the file env-config/qa/config.json</p><p><strong>Scenario 6</strong> — Create a brand new environment called load testing with the same settings as Integration (GPU) -&gt; copy the folder env-config/integration/gpu/ to env-config/load-testing and edit the config.json file accordingly.</p><h3>Conclusion: Start simple</h3><p>In this guide, you’ve seen how to combine different configuration settings with your Helm charts and how to employ Helm value hierarchies to model your environments without duplication. You also learned the proper way to use the multi-source feature of Argo CD.</p><p>We’ve shown that plain Application Sets can go a long way toward modeling all usual cases, even with Helm charts. You don’t need to template Application Sets with an extra Helm layer, and you certainly don’t need to resort to hacks for “default” and “extra” values.</p><p>We recommend using this structure as a starting point in your organization. Only add complexity if it’s really required by your business needs.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=133c3bc93678" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/using-helm-hierarchies-in-multi-source-argo-cd-applications-for-promoting-to-different-gitops-133c3bc93678">Using Helm Hierarchies in Multi-Source Argo CD Applications for Promoting to Different GitOps…</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Securing Argo CD in a Multi-Tenant Environment with Application Projects]]></title>
            <link>https://medium.com/containers-101/securing-argo-cd-in-a-multi-tenant-environment-with-application-projects-39ad30893fd5?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/39ad30893fd5</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[argo-cd]]></category>
            <category><![CDATA[continuous-delivery]]></category>
            <category><![CDATA[gitops]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Thu, 17 Oct 2024 11:21:02 GMT</pubDate>
            <atom:updated>2024-10-17T11:21:02.539Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uFxwuXRKTA4gkHq9qjcevw.png" /></figure><p>One of Argo CD’s standout features is its powerful user interface (UI) that shows the live status of all applications and the respective Kubernetes resources. Both developers and operators can quickly understand the status of their deployments by looking at the UI and drilling down into all the different views of Argo CD applications. Some teams even use the Argo CD UI as a generic Kubernetes dashboard and management interface (especially after the <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/web_based_terminal/">addition of the web-based terminal feature</a>).</p><p>As organizations scale their Argo CD use, they understand that using a single Argo CD instance across separate teams is only possible with proper security controls. Argo CD is designed to handle multi-tenant installations and has comprehensive controls for limiting and isolating applications between developer teams. This lets operators define who has access to what application and with what limits.</p><p>In this guide, we examine the individual building blocks (<a href="https://argo-cd.readthedocs.io/en/stable/user-guide/projects/">application projects</a>, <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/">security policies</a>, user groups, and roles) and explain how you can set up a multi-tenant Argo CD installation for your organization.</p><h3>Popular use cases for multi-tenant Argo CD installations</h3><p>Each organization has different requirements when it comes to risk and ease of management. We’ve seen many ways that teams prefer to use (or not use) Argo CD in a multi-tenant/multi-use environment.</p><p>Firstly, we have the use case where developers have full access to the Argo CD UI and can debug/sync/edit their deployments with full power. This is great if your developers already have some Kubernetes knowledge and know the principles of GitOps.</p><p>After your team reaches a certain size, you need some restrictions in place, especially with larger developer teams. The classic scenario is where developers have full access to non-production environments, but read-only access to production environments. They can look at the status of production and possibly retrieve logs, but can’t change anything on the deployment.</p><p>The restrictions on developers can be as strict as you want. Argo CD supports isolating developers to specific namespaces and clusters, hiding completely unrelated applications from the UI, and even setting up different access for different application actions. For example, a developer might have access to take logs from an Argo CD application, but they’re restricted from triggering a re-sync of the application.</p><p>At the other end of the spectrum, some organizations choose to hide Argo CD completely from developers. Argo CD is reserved for operators only. Developers interact with their applications using an external system that often sits on top of Argo CD and abstracts several Kubernetes concepts (like an internal developer platform or portal).</p><p>In all scenarios, it’s important to decide whether you let developers to create new applications on their own or only edit existing ones. You also need to consider <a href="https://codefresh.io/blog/creating-temporary-preview-environments-based-pull-requests-argo-cd-codefresh/">how to handle ephemeral environments</a>.</p><p>Three important things to remember are:</p><ol><li>Argo CD comes with its own role-based access control (RBAC) mechanisms that aren’t related to Kubernetes RBAC.</li><li>There are several moving parts that coordinate together (single sign-on (SSO), roles, app-projects).</li><li>This guide is about your internal users. If you want to achieve multi-tenancy for external users and customers, <a href="https://www.youtube.com/watch?v=hFiHU6W4_z0">you probably need to look at other approaches</a>.</li></ol><p>There isn’t a one-size-fits-all solution, of course. It’s also impossible to cover all variations in a single guide. There are several designs that you will see in real life.</p><p>Consider this guide a gentle introduction to the security controls of Argo CD. We explain the theory behind the security controls and share a semi-realistic example to give you ideas on how to implement multi-tenancy in your own organization.</p><h3>Argo CD security overview</h3><p>There are two different aspects when it comes to multi-tenant Argo CD installations:</p><ol><li>Limits over where applications get deployed. Which Kubernetes resources can people deploy and from which Git repos.</li><li>Security over Argo CD resources. Who can see an application, sync it, delete it etc.</li></ol><p>These two aspects are independent and are linked together via the <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/projects/">application project resource</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/996/0*Avba6nm8SD3aACvs.png" /></figure><p>The other pieces of the puzzle are:</p><ol><li>Users, groups, and <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/">SSO configuration</a> (single sign-on)</li><li>Local users (optional)</li><li>Global security rules and <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/">per-project security policies</a></li><li>Grouping of policies into roles</li><li>Argo CD resource actions</li><li>Other associated application resources such as clusters, namespace, and Git repos</li></ol><p>We briefly cover all these components in the next sections.</p><h3>How to define access for Argo CD resources</h3><p>Let’s start with Argo CD resources (i.e., applications). The RBAC model behind Argo CD resources follows the who-what-how model. Resources get secured via RBAC policies. Essentially, you can define who has access to what application and how they can access it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_nl0DN3eA0h3gSPi.png" /></figure><p>Even though Argo CD can define security controls with individual objects (“allow David to edit application billing”), in reality, it’s most useful to treat similar objects in groups. Therefore, all individual parts of a policy are mostly applied between groups instead of individual entities.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*DvZ8h2etWlSRcmGt.png" /></figure><p>After you have all groups/roles/apps defined, you can apply policies on a global or project level.<br>You define policies in the <a href="https://casbin.org/">casbin format</a> and follow the syntax:</p><pre>p, &lt;role/user/group&gt;, &lt;resource&gt;, &lt;action&gt;, &lt;object&gt;, &lt;effect&gt;</pre><p>The first p stands for “policy”.</p><p>Here are some examples of policies:</p><pre>“p, infra:read-only, applications, get, infra/*, allow”</pre><p>This policy says, “All users that belong to the infra/read-only group are allowed to view/get all applications that belong to the infra app-project”.</p><p>Another example:</p><pre>“p, developers:full-access, applications, delete, prod/*, deny”</pre><p>This policy says, “All users that belong to the developers:full-access group are NOT allowed to delete applications that are part of the prod app-project”.</p><p>You can find all the details for what actions and resources are available in the <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/#rbac-model-structure">official Argo CD documentation</a>.</p><p>The important point here is that you need to do some analysis and split all your Argo CD applications into app projects. Different organizations will select different meanings for the projects. A project can be a specific team of people, a department, “environment”, or anything else that makes sense in your organization for application groups. We recommend using projects for each team (both developer teams and infra teams).</p><p>After you’ve defined all your policies, you should store them <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/">declaratively </a>in your application projects under <strong>.spec.roles.</strong></p><p>The second important point is that contrary to other security designs, a “role” in Argo CD is a set of policy resources. You first create an app project, then apply policies to it, and finally connect policies to users.</p><p>This means you need to anticipate how your applications will get used and design appropriate roles. For example, if you follow the scenario where a group of developers can see all applications in Argo CD but in “view-only” mode, then you need to create a “view-only” security policy in all the respective application projects.</p><h3>How to define access for Kubernetes resources</h3><p>RBAC policies define how users can interact with the main Argo CD resources (applications). The application project has additional capabilities for securing Kubernetes resources.</p><p>Remember that an Argo CD application is, in its essence, just a link between a Git repository and a Kubernetes cluster/namespace. In a multi-tenant environment, you almost always want to restrict:</p><ol><li>What clusters/namespaces developers can deploy to</li><li>Which Git repos/paths can be used for applications</li><li>What kind of Kubernetes resources can be affected</li><li>Optionally, when deployments can happen</li></ol><p>The default project is an “allow-everything” project that has no restrictions at all.</p><pre>spec:<br>  sourceRepos:<br>  - &#39;*&#39;<br>  destinations:<br>  - namespace: &#39;*&#39;<br>    server: &#39;*&#39;<br>  clusterResourceWhitelist:<br>  - group: &#39;*&#39;<br>    kind: &#39;*&#39;</pre><p>Using the default project is great for experimenting with Argo CD, but we recommend you don’t use it in a production setup and only have your own projects defined.</p><p>Application projects are very flexible when it comes to cluster resources. You can isolate developers to specific namespaces, specific types of resources, and days of deployments using <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/sync_windows/">sync windows</a>.</p><p>Here is an example application project:</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: AppProject<br>metadata:<br>  name: team-revenue<br>  namespace: argocd<br><br>spec:<br>  description: Team with developers for Revenue<br><br>  sourceRepos:<br>  - &quot;https://github.com/acme/revenue/*&quot;<br><br>  destinations:<br>  - namespace: &quot;revenue&quot;<br>    server: https://kubernetes.default.svc<br><br><br>  clusterResourceBlacklist:<br>  - group: &quot;&quot;<br>    kind: &quot;Namespace&quot;<br><br><br>  namespaceResourceBlacklist:<br>  - group: &quot;&quot;<br>    kind: &quot;ResourceQuota&quot;<br>  - group: &quot;networking.k8s.io&quot;<br>    kind: &quot;NetworkPolicy&quot;</pre><p>This project defines what the “Revenue” team can do.</p><ol><li>They can only deploy from Git repos under acme/revenue.</li><li>They can only use the namespace “revenue” in the local cluster.</li><li>They cannot create new namespaces, resource quotas, and network policies.</li></ol><p>While the <a href="https://argo-cd.readthedocs.io/en/stable/user-guide/commands/argocd_proj/">argocd CLI includes several commands for managing projects</a>, again we recommend you <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/project-specification/">declare everything in the Project CRD</a> and use a declarative setup for a production environment.</p><h3>A complete example with two developer teams</h3><p>We’ve seen how the application project is the centerpiece of Argo CD security in multi-tenant environments.</p><p>Let’s take a look at a full example. In our imaginary company, we have 2 developer teams (A and B) that continuously deploy applications. We also have infrastructure applications (i.e., cert-manager) handled by the operator team.</p><p>Here are our requirements:</p><ul><li>Admin/operator can view/edit all applications (including infra apps).</li><li>Team B can only view/edit their own applications and nothing else.</li><li>Team A can view/edit their own applications and view-only applications from team B.</li><li>No developer from either team can view the infrastructure applications.</li><li>No developer can delete any application (even their own).</li><li>All applications (including infra) can only be synced from a specific GitHub organization/user.</li></ul><p>You can find all Argo CD settings at <a href="https://github.com/kostis-codefresh/intro-argocd-rbac">https://github.com/kostis-codefresh/intro-argocd-rbac</a> . Note that for simplicity, we used <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/#local-usersaccounts">local users</a> instead of SSO.</p><p>We have <a href="https://github.com/kostis-codefresh/intro-argocd-rbac/tree/main/apps">4 applications in total</a> — one for each team and 2 infrastructure apps. The admin of Argo CD can see all of them and can perform any action.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*65YVtMRjNWd-Edwg.png" /></figure><p>Developers from team B are the most constrained. They can only look at their application and nothing else.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*mzD8fhQhb__ic8NQ.png" /></figure><p>They can sync at will, but they cannot delete the application. Pressing the delete button presents an error message.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/358/0*uM2h8XhHF94JOCCy.png" /></figure><p>Developers from team A can see both apps from both teams, but not the infrastructure apps.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*D4qzvNISykvqUXkh.png" /></figure><p>Even though they can sync/edit their own application, they cannot affect the application of team B. If they try to perform a sync on the application of team B, again they will get an error message.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*yWnUi-6ZwmGlI2PC.png" /></figure><p>With this set up, we’ve satisfied our initial requirements. Let’s look under the hood to see how everything was implemented.</p><h3>Using app-projects per team</h3><p>We mentioned in the introduction using application projects to represent teams. In our example, <a href="https://github.com/kostis-codefresh/intro-argocd-rbac/tree/main/app-projects">we defined 3 projects, one for each team</a> (A, B, and infra).</p><p>The most interesting project is the one from team B:</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: AppProject<br>metadata:<br>  name: team-b<br>  namespace: argocd<br>  finalizers:<br>    - resources-finalizer.argocd.argoproj.io  <br>spec:<br>  description: Team B<br>  destinations:<br>  - name: &#39;*&#39;<br>    namespace: &#39;*&#39;<br>    server: &#39;*&#39;  <br>  roles:<br>  - description: Read Only<br>    name: read-only<br>    policies:<br>    - p, proj:team-b:read-only, applications, get, team-b/*, allow<br>  - description: Read Write<br>    name: read-write<br>    policies:<br>    - p, proj:team-b:read-write, applications, get, team-b/*, allow<br>    - p, proj:team-b:read-write, applications, sync, team-b/*, allow<br>    - p, proj:team-b:read-write, applications, update, team-b/*, allow        <br>  sourceRepos:<br>  - https://github.com/kostis-codefresh/intro-argocd-rbac.git</pre><p>Notice that we define 2 roles here (sets of policies). One is the “read-write’ for team B, and one is “read-only” for team A. Notice also that neither role includes a “delete” action, This means that no developer can delete the applications of this project. Finally, we restrict the application source repository to a specific GitHub project.</p><p>After we have AppProjects in place, we deploy all 4 applications using an application set. If you’re not familiar with application sets, please <a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/">read our application sets guide</a> first.</p><p>The <a href="https://github.com/kostis-codefresh/intro-argocd-rbac/blob/main/application-sets/simple-scenario.yml">applicationset</a> will automatically assign each application to the same AppProject as its parent folder.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*MXzr41Ur8lG_RwY8.png" /></figure><p>This concludes the initial application deployment.</p><h3>Assigning users to groups/roles</h3><p>For simplicity, we use local users in our example. In a real system, you’d use SSO to assign users to groups for Argo CD.</p><p>In our case, we add 2 users (one for team A and one for team B) in argocd-cm.</p><pre>apiVersion: v1<br>kind: ConfigMap<br>metadata:<br>  name: argocd-cm<br>  namespace: argocd<br>  labels:<br>    app.kubernetes.io/name: argocd-cm<br>    app.kubernetes.io/part-of: argocd<br>data:<br>  accounts.jane: apiKey, login<br>  accounts.jane.enabled: &quot;true&quot;<br>  accounts.david: apiKey, login<br>  accounts.david.enabled: &quot;true&quot;  </pre><p>You can set the password for local users with the argocd account update-password command or via declarative setup.</p><p>Finally, we assign each user to their appropriate roles in</p><pre>apiVersion: v1<br>kind: ConfigMap<br>metadata:<br>  name: argocd-rbac-cm<br>  namespace: argocd<br>  labels:<br>    app.kubernetes.io/name: argocd-rbac-cm<br>    app.kubernetes.io/part-of: argocd<br>data:<br>  policy.csv: |<br>    g, jane, proj:team-a:read-write<br>    g, jane, proj:team-b:read-only<br>    g, david, proj:team-b:read-write  </pre><p>You can see here that Jane belongs to team A. She has read/write access to all apps of team A and read-only access to projects from team B. David is on team B and he can only see his own projects.</p><p>Neither Jane nor David see any of the applications from the “infra” projects (which was one of the original requirements).</p><p>This concludes the setup. If you now log in as Admin, Jane, or David in the Argo CD user interface, you’ll see only what each user is allowed to view/edit.</p><h3>Where to go from here</h3><p>This guide was a gentle introduction to Argo CD RBAC and how to use Argo CD in multi-user situations. There are several more things to explore, like appProject hierarchies, specific SSO configurations, complex policies, and more. For now, you should have some great ideas for your own organization and understand how all the different components work together to achieve multi-tenancy.</p><p>Security is a complex topic. Consider the present guide a starting point for your organization. You should always follow the best practices and requirements of your security team.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=39ad30893fd5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/securing-argo-cd-in-a-multi-tenant-environment-with-application-projects-39ad30893fd5">Securing Argo CD in a Multi-Tenant Environment with Application Projects</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The 4 Stages of the Argo Maturity Curve]]></title>
            <link>https://medium.com/containers-101/the-4-stages-of-the-argo-maturity-curve-b140d74400c6?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/b140d74400c6</guid>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[argo-cd]]></category>
            <category><![CDATA[argo]]></category>
            <category><![CDATA[gitops]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Wed, 09 Oct 2024 10:53:52 GMT</pubDate>
            <atom:updated>2024-10-09T11:08:02.477Z</atom:updated>
            <content:encoded><![CDATA[<p>Argo CD is the most popular GitOps tool for deploying applications to Kubernetes clusters. Many teams that move their applications to Kubernetes choose Argo CD for its powerful sync engine and intuitive dashboard.</p><p>Argo CD is also fully open source, which means teams can freely install it on their private clouds, behind-the-firewall data centers, or even in air-gapped environments without any licensing restrictions. Argo CD’s popularity is surging right now, especially since Weaveworks (the company behind Flux — the main Argo CD competitor) ceased operations in early 2024.</p><p>It’s important to understand, however, that while Argo CD is great as a GitOps sync engine, it’s missing several features that big enterprises need when deploying at scale. If you have one or two Kubernetes clusters and a small number of applications, it’s easy to start using Argo CD. For larger installations with multiple clusters and multiple applications, using plain Argo CD takes too much manual effort. In those cases, looking at an enterprise solution like the Codefresh platform will be more beneficial as your organization scales its Kubernetes use.</p><p>In this guide, we look at the 4 stages of the Argo maturity curve. We explain how Codefresh can help you at each stage beyond what Argo CD offers out-of-the-box.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*CXVwydu1HyyIPHcD.png" /></figure><p>The central theme here is that enterprises have several other requirements not offered by the sync engine of Argo CD, while Codefresh is built from the ground up as an enterprise platform.</p><h3>Stage 1 — Initial Argo CD installation versus using a control plane</h3><p>The OSS version of Argo CD allows for 2 installation modes out of the box:</p><ol><li>Installing an Argo CD instance per Kubernetes cluster.</li><li>Installing a central Argo CD instance that manages several other clusters.</li></ol><p>Each method has advantages and disadvantages. We cover them in detail <a href="https://codefresh.io/blog/a-comprehensive-overview-of-argo-cd-architectures-2024/">in our Argo CD architecture article</a>. The takeaway is that no option is perfect, each has shortcomings.</p><p>The individual Argo CD instances are very difficult to manage on a large scale.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*LO2IMn93dUff1unv.png" /></figure><p>The biggest problem is daily cluster management. You now need to manage multiple Argo CD instances and their respective dashboards. If you have for example 6 Kubernetes clusters, you’ll also have 6 different dashboards. When searching for an application you need to remember to use the appropriate Argo CD dashboard. Upgrading all 6 Argo CD instances to the same version is also a non-trivial job.</p><p>On the other hand, you can have the central Argo CD instance that manages other clusters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*_HCmTFDaUGOkoF6J.png" /></figure><p>This also comes with significant challenges.</p><ol><li>The central Argo CD instance is a single point of failure.</li><li>Communication to other clusters is outgoing. This means you need to open holes in your firewall if the other clusters aren’t in the same networking domain as the central instance.</li><li>The centralized instance will quickly become a bottleneck when you have a large number of applications and clusters.</li></ol><p>Teams that choose either of these installation options will have to deal with the disadvantages on a day-to-day basis and either accept them or try to find workarounds.<br>With Codefresh there is a third solution — using a <em>Control Plane</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*cwEriqG6XjntSxuH.png" /></figure><p>The Codefresh Control Plane unifies multiple Argo CD instances under a single management dashboard.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UqG_dsJ0Oo6qWDkE.png" /></figure><p>The Control Plane approach offers the best of both worlds without the disadvantages</p><ol><li>Communication with the Control Plane is inbound. This means you can manage Argo CD instances behind the firewall and there isn’t any need for open ports</li><li>There isn’t a single point of failure. Even if the Control Plane has issues, the individual Argo CD instances will continue syncing on their own.</li><li>There are no performance bottlenecks as you can easily mix and match multiple Argo CD instances and clusters.</li><li>There’s still a single application dashboard for Argo CD instances.</li></ol><p>The last point is especially important for end users of Argo CD and, more specifically, developers. They can now view their applications easily without juggling between different dashboards.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*xfXR7tTu991gvO8Z.png" /></figure><p>Even at this early stage (installation of Argo CD), choosing Codefresh brings extra value to both Argo CD administrators and users.</p><h3>Stage 2 — Managing multiple applications and tracing back code changes</h3><p>With the initial installation out of the way, let’s examine what happens with day-2 operations. Creating Argo CD applications is a straightforward process. Out of the box, Argo CD supports your Helm charts, Kustomize overlays, and any other custom manifests you already have.</p><p>The main problem here is that Argo CD has very limited information about the applications that your developers create. The only thing that Argo CD knows about an application is:</p><ol><li>The Git repository that has the Kubernetes resources</li><li>The container image that gets deployed</li><li>The target cluster where the application gets deployed</li><li>The Git hash of the manifests that were applied</li></ol><p>And that’s all! Argo CD has no visibility into the things that matter for developers. Developers want to know what source code changes went into a release, plus information like unit test results, security vulnerabilities, and pull request information. Argo CD doesn’t know what happened before a container image was built.</p><p>This means that developers have to switch between multiple systems like GitHub, Jira, the CI server, and Argo CD to understand exactly what Argo CD deployed.</p><p>Several teams adopt Argo CD hoping that developers will use the Argo CD UI as a one-stop solution for all their deployment needs. Unfortunately, it quickly becomes apparent that developers can’t work with just the Argo CD UI. At this point organizations start looking at developer portals and internal developer platforms to address the daily needs of the development team.</p><p>With Codefresh you get full traceability from source code committed to deployment.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*gNeSOEsYKnBPqjat.png" /></figure><p>Codefresh aggregates information from several other systems, like the CI server or the ticket server (i.e. JIRA), and provides a complete timeline of what happened with an application deployment including:</p><ul><li>Who initiated a change</li><li>What was the Git commit</li><li>What features (i.e. Jira tickets) were included</li><li>Which pull request took part in the change</li><li>Which pipeline was used for artifact creation</li><li>Results of security scans, unit tests, lint checks, etc</li><li>Which container images were produced and pushed</li><li>Which Kubernetes applications were affected</li><li>Which individual components were updated</li></ul><p>The timeline also gives you the overall health status of a service/application across the complete history of deployments.</p><p>Using Codefresh, developers can answer in seconds the burning questions they face every day. Some examples are:</p><ol><li>What JIRA features are now deployed in Staging?</li><li>Who deployed release 0.3.0 last Thursday and what source code features did it contain?</li><li>Where is JIRA feature 4524 deployed right now? Is it in Production just yet?</li><li>What is the CI pipeline that created container image billing:v0.4.0 that’s been sitting in the QA environment for the last 5 weeks?</li><li>Do we have any critical vulnerabilities known in the artifact deployed in the Load Testing environment?</li><li>How many developers have just pushed their feature to QA?</li><li>Who approved pull request 2345 that just went into production?</li></ol><p>If you have only Argo CD, answering these kinds of questions can take significantly longer (even hours in some cases). With Codefresh, developers can instead reason about any deployments and quickly discover the full picture for any active application.</p><h3>Stage 3 — Promoting apps between environments</h3><p>After you have the initial deployment for every application in place, the next step for developers is promoting applications from one environment to another.</p><p>Unfortunately, the open-source version of Argo CD can’t help with promotions. Argo CD has no concept of an “environment” and doesn’t have any information about promotions. This forces people who’ve adopted Argo CD to try implementing their own promotion mechanisms with several workarounds.</p><p>The classic pattern here is the “special” naming of applications and the hardcoding of an “environment” in the application name:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*BTlsAsbFIjbhPejS.png" /></figure><p>Here these 3 Argo CD applications have been named with a special pattern to try accommodating “environments”. Even though it’s clear to a human user that this is the same application deployed in 3 environments, for Argo CD <strong>it’s 3 different applications with no connection between them whatsoever</strong>.</p><p>Codefresh has full support for both grouping multiple applications (<a href="https://codefresh.io/blog/argocd-promotions-with-products/">in products</a>) as well as <a href="https://codefresh.io/blog/introducing-the-worlds-first-dashboard-for-gitops-environments/">modeling environments</a> the proper way:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*hb80ixUb2EZ8ofiA.png" /></figure><p>A Codefresh environment can be a different namespace, a different cluster, or different namespaces in a different cluster. Basically, you can mark any combination of clusters/namespaces as an environment. Then Codefresh can correlate applications and their environments in the backend and reason about them. In the example above, even though the underlying applications are still named “accounts-qa, account-staging, accounts-production”, Codefresh <strong>knows </strong>that it’s the <strong>same </strong>application — <em>accounts — </em>deployed to 3 different environments.</p><p>In addition to environment support, Codefresh can aggregate information from the CI phase into environments (as explained in the previous section). This means that, unlike Argo CD, which only knows a container image version and nothing more, Codefresh can match other metadata into an environment, like source code commits, pull requests, security scans, and more.</p><p>Here’s another view of the environment dashboard which now has JIRA information as well:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*yB0-5mfrUi_rKmpK.png" /></figure><p>Using this dashboard, developers can reason in seconds about their GitOps environments and answer questions about <em>features </em>which isn’t possible with just Argo CD.</p><p>The highlight feature, of course, is the promotion between environments. Since Codefresh has all the context about which application is deployed into which environment, promotions are a core, built-in construct in the Codefresh GitOps platform.</p><p>In the most simple scenario, you can simply let developers promote applications by drag-and-drop. Codefresh automatically creates a commit with all the required information to perform the promotion.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/935/0*Y3bDwKGGth-gPki_.png" /></figure><p>In a more complex scenario, you can define a complete promotion workflow with specific restrictions and pre/post checks that again isn’t possible with plain Argo CD.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZbD7NW75EYbRyOqj.png" /></figure><p>In the screenshot above, we defined a basic promotion policy between 4 environments. New applications can only be deployed in “dev”. After they pass the checks in “dev”, they can move in parallel to “staging” and “qa”. Only if all checks pass in both “staging” and “qa” can the application go to production.</p><p>The beauty of the promotion policy is that you can define any pre/post checks and enforce them. Your checks can be security scans, smoke tests, database checks, etc.</p><p>If a check fails, promotion gets blocked:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*KpNRQm-OA_AFQNU7.png" /></figure><p>In this example, checks were successful in “staging”, but something went wrong in “qa”. This means developers can’t push to production until “qa” gets fixed. You can use the friendly Codefresh UI to understand what went wrong and drill down in the logs.</p><p>Essentially, when it comes to environments and promotions, plain Argo CD can’t help you at all.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/0*qehgoeWq9Rb6xTTo.png" /></figure><p>The functionality of Codefresh is essential as developers always think in terms of environments and promotions.</p><h3>Stage 4 — Enterprise features</h3><p>With the basic functionality out of the way, we can finally look at all the features that enterprises need as they scale.</p><p>Often, organizations start with just Argo CD and soon realize they need other features like built-in pipelines and progressive delivery (blue/green/canary). At this point, they choose to manually install Argo Workflows and Argo Rollouts. Then they spend extra time integrating those products.</p><p>Even though the Argo projects are made to work together, integrating them in a single cluster is a non-trivial process. For example, Argo Workflows have a different SSO mechanism than Argo CD, while Argo Rollouts has no SSO support at all.</p><p>Codefresh comes with this integration out-of-the-box. The Codefresh runtime is a unified installation package that contains all 4 Argo projects out-of-the-box and with security patches applied.</p><p>The unified integration dashboard we mentioned in the first section also applies to the other Argo projects. For example, by adopting the Codefresh runtime, you get free SSO support for all 4 projects with no extra effort.</p><p>Here’s an example of using progressive delivery in an application from the same Codefresh interface.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jPwzWO5APOZm-K7I.png" /></figure><p>At the same time, Codefresh offers several enterprise features that would require significant effort to replicate with just Argo CD. Because Codefresh has full visibility of what happened in both the CI and CD phases, DORA metrics are always available from the Codefresh UI with zero configuration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*IIWHjjYtXXyC23lO.png" /></figure><p>Finally, you can find several statistics and insights that reveal bottlenecks in your deployment process. Again, you’d need to manually create them if you only use Argo CD</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*L1pxRDTbNK3lKOoD.png" /></figure><p>This was just an overview of the extra enterprise features offered by Codefresh. There are several other features, like annotating container images with extra metadata:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*J_BHkUDxuQYUTJNo.png" /></figure><p>Correlating images with deployments also happens out-of-the-box with Codefresh:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*pOKxG2pRCXxP5fTR.png" /></figure><p>Often, developers need to know where a specific image is deployed. Answering this question with Codefresh literally takes seconds. Answering with just Argo CD can take hours depending on the complexity of the application.</p><h3>Conclusion</h3><p>We’ve seen the 4 stages for GitOps maturity and explained at every stage what you get with Argo CD and what extra features Codefresh offers.</p><p>It should now be clear that Argo CD is great as a sync engine but misses several features that enterprises need for effective deployments. With Codefresh you get a full enterprise platform that has all the missing functionality, while still using Argo CD under the hood.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/0*7UTg8rlO7sKnT84b.png" /></figure><p>The Codefresh platform is actually a superset of all 4 Argo projects and contains extra features in regards to:</p><ol><li>Initial installation</li><li>Application deployment and correlation with the CI process</li><li>Full traceability for a source code change from the initial commit until it reaches production</li><li>Environment promotion and creation of developer guardrails</li><li>Enterprise security and support</li><li>Deployment statistics, DORA metrics, and application insights</li></ol><p>Get started with the Codefresh GitOps platform, <a href="https://codefresh.io/codefresh-signup/">sign up now</a> or <a href="https://codefresh.io/request-a-demo/">schedule a demo</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b140d74400c6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/the-4-stages-of-the-argo-maturity-curve-b140d74400c6">The 4 Stages of the Argo Maturity Curve</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GitOps Secrets with Argo CD, Hashicorp Vault and the External Secret Operator]]></title>
            <link>https://medium.com/containers-101/gitops-secrets-with-argo-cd-hashicorp-vault-and-the-external-secret-operator-eb1eec1dab0d?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/eb1eec1dab0d</guid>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[argo-cd]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[secrets]]></category>
            <category><![CDATA[external-secrets-operator]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Thu, 01 Aug 2024 15:30:13 GMT</pubDate>
            <atom:updated>2024-08-02T11:15:49.126Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uM34_ipjBwDENmU811qexQ.png" /></figure><p>Teams adopting GitOps often ask how to use secrets with Argo CD. The official Argo CD page about secrets <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/secret-management/">is unopinionated by design</a> and simply lists a set of projects that can help you with secrets.</p><p>We’ve seen several approaches to secret management. These include <a href="https://codefresh.io/blog/handle-secrets-like-pro-using-gitops/">sealed secrets</a>, the <a href="https://codefresh.io/blog/aws-secret-manager-argocd/">Argo CD Vault plugin</a>, and the <a href="https://codefresh.io/blog/aws-external-secret-operator-argocd/">External Secret Operator</a>.</p><p>In this post, we showcase the <a href="https://external-secrets.io/latest/">External Secret Operator</a> and <a href="https://www.vaultproject.io/">Hashicorp Vault</a> and focus on 2 important aspects.</p><ul><li>How to avoid saving ANY secrets in Git, including tokens for fetching the application secrets</li><li>How to refresh secrets automatically without pod restarts and application deployments</li></ul><p>Several existing tutorials show how you don’t need to store any application secrets in Git, but never actually explain where to store the token for fetching the secrets from Vault or other secret providers. So you still have the same problem but for the token itself instead of the application secrets — where to store it?</p><p>Refreshing secrets automatically is also fundamental, as passing secrets to an application is only half the battle. You also need an easy way to rotate and revoke compromised secret information.</p><h3>How to pass secrets with the External Secret Operator</h3><p>The External Secret Operator (ESO) is a Kubernetes controller that supports retrieving secrets from several providers (AWS, Azure, GCP, Vault, Gitlab, etc) and converting them to plain Kubernetes secrets so any Kubernetes application can consume them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ZeSarjfK5WeBXhQo.png" /></figure><p>The Controller is designed for Kubernetes installations and although it works great with Argo CD, it doesn’t need Argo CD or even follow <a href="https://codefresh.io/learn/gitops/">GitOps principles</a>. It introduces a set of custom resources (CRDs) for representing a secret source, SecretStore, and a secret from that store, ExternalSecret.</p><p>One of the best characteristics of ESO is that all secrets it controls get converted into plain <a href="https://kubernetes.io/docs/concepts/configuration/secret/">Kubernetes secrets</a> in the end (regardless of their actual source). This makes it a very flexible choice because the application code can be as minimal as possible.</p><p>In practice, your application code:</p><ul><li>Doesn’t know anything about the ESO controller</li><li>Doesn’t need a special API to access secrets</li><li>Doesn’t even need to know it’s running inside Kubernetes</li><li>Can read secret values from files (recommended) or environment variables</li></ul><p>This is one of the controller’s highlight features, especially in legacy applications where you can’t change the source code at all. ESO is also a great choice when you want to move secrets from one provider to another, as the application is completely oblivious to where the secrets come from.</p><h3>The example secret application</h3><p>Our demo application is at <a href="https://github.com/kostis-codefresh/external-secrets-gitops-example/tree/main/src">https://github.com/kostis-codefresh/external-secrets-gitops-example/tree/main/src</a>.</p><p>It’s a very simple application that reads secrets from files mounted at /secrets.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9-LCf1rUHEJmUVeQ.png" /></figure><p>We recommend reading secrets from files and not environment variables, as you can monitor files for changes. When you need to rotate or revoke secrets, it would be great to make your application consume the new secrets without any restarts or redeployments. Of course, you can also use Kubernetes <a href="https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#define-container-environment-variables-using-secret-data">secrets as environment variables</a>.</p><p>The application needs 3 secrets:</p><ol><li>Database URL</li><li>Database username</li><li>Database password</li></ol><p>Just for demo purposes, there’s no real database. The application simply prints the secrets so you can verify visually if the values in Hashicorp Vault have passed successfully to the pod.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/722/0*BagTv9dDMBSkdV51.png" /></figure><p>Notice the application clearly says where these secrets get loaded from (/secrets directory). This is a great practice that lets you know the source of your secrets, ensuring you don’t miss any during rotation. You can expose this using an API instead, or even have an organization-wide convention that specifies the source of secrets. We recommend you do this regardless of your adoption of Argo CD or GitOps.</p><p>When trying to understand how an application loads configuration and secrets, having the application tell you itself saves time spent debugging and looking at logs. This is especially helpful during an incident where time is of the essence.</p><h3>Installing Hashicorp Vault and ESO in your Kubernetes cluster</h3><p>Let’s start first by deploying Hashicorp Vault and ESO in your cluster. You can do it with Argo CD and simply use the public Helm charts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sC4qRO8GcymjBuRH.png" /></figure><p>In a real company, Hashicorp Vault should be already installed somewhere else by your administrator. You’d need to ask your security team for access.<br>For this demo, we installed Hashicorp Vault with the <strong>server.dev.enabled</strong> option as <strong>true</strong>, so that Vault runs in development mode and doesn’t need <a href="https://developer.hashicorp.com/vault/docs/concepts/seal">sealing/unsealing</a>. <strong>This is only for demo purposes</strong>. The default root token is also set as “root” and you can use it to log in to the UI as well.</p><h3>Fetching external secrets from Hashicorp Vault</h3><p>Before we pass secrets to our application we need to explain to ESO where to get them. In our example, we use the <a href="https://external-secrets.io/latest/provider/hashicorp-vault/">built-in integration for Hashicorp Vault</a>. For the integration to work, we need to create a SecretStore or ClusterSecretStore resource that defines how to authenticate against Vault to get secrets.</p><p>This is the crucial point of the whole process. Several existing tutorials about external secrets either don’t explain the integration process, or use a predefined token for accessing Vault (or any other secret provider).</p><p>If you use an authentication token, you’re introducing the chicken-and-egg question about how to store the token itself. Using external secrets avoids storing the secrets in Git. But if we need a token for accessing secrets, where should we store that token? If we choose Git, we’re back to square one. If we hard-code in a manifest and push it on the cluster, we’ve introduced a single point of failure for reliability and security reasons.</p><p>The answer to this question is provider-specific, but essentially you need to use an authentication method based on trust and not static tokens. Several ESO providers support authentication methods with this characteristic. You need to contact your security team and discuss your options with them. Make sure to check the support of the Open ID Connect protocol (OIDC) for your secret provider.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sJi6KGceWtXBCl4V.png" /></figure><p>This guide isn’t about Hashicorp Vault, but since we’re using it as an example, we can easily follow this best practice and use a trust method instead of a predefined token.</p><p>For our demo, we can instruct Hashicorp Vault to <a href="https://external-secrets.io/latest/provider/hashicorp-vault/#kubernetes-authentication">use Kubernetes authentication</a> by trusting the same Kubernetes cluster that ESO is running on. This way we don’t need to store any token at all. ESO will use a Kubernetes service account from the pod it’s running on and pass that to Vault. Vault has many more authentication options, like trusting outside Kubernetes clusters or even other Vault instances, but explaining them is outside this guide’s scope.</p><p>The result is that we didn’t hardcode any secrets or tokens anywhere, in the cluster or in Git.<br>To enable the <a href="https://developer.hashicorp.com/vault/docs/auth/kubernetes">Kubernetes authentication read the Vault documentation</a>. You can use the UI or the CLI to enable it. In our example cluster, you can expose the Vault UI with:</p><pre>kubectl port-forward -n vault vault-0 8200:8200</pre><p>The default token is “root” because we used the “dev” installation mode.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*lRcHVyjZUBmSAanE.png" /></figure><p>A nice part of the default installation of Vault in Kubernetes, is that you get the Vault CLI preinstalled as well in the same pod. So enabling Kubernetes auth for a quick demo is as simple as doing the following:</p><pre>kubectl exec --stdin=true --tty=true -n vault vault-0 -- /bin/sh<br>vault auth enable kubernetes<br>vault write auth/kubernetes/config \<br>  kubernetes_host=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT<br><br>vault write auth/kubernetes/role/demo \<br>  bound_service_account_names=* \<br>  bound_service_account_namespaces=* \<br>  policies=default \<br>  ttl=1h</pre><p>You also need to change the default policy to allow reading of secrets by adding the following snippet to the default policy:</p><pre> path &quot;secret/*&quot; {<br>  capabilities = [ &quot;read&quot;, &quot;list&quot; ]<br>}</pre><p>The rest of the policy should stay as-is.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*T8kRseW1doPb3-5n.png" /></figure><p>Your dedicated security team should handle these settings for you in Vault or your preferred secret providers. The settings above are just for demo purposes to show you how external secrets work.<br>The last step is creating a <a href="https://github.com/kostis-codefresh/external-secrets-gitops-example/blob/main/manifests/vault-integration/secretstore.yml">ClusterSecretStore </a>that tells ESO that Vault will be used for secret storage.</p><pre>apiVersion: external-secrets.io/v1beta1<br>kind: ClusterSecretStore<br>metadata:<br>  name: vault-backend<br>spec:<br>  provider:<br>    vault:<br>      server: &quot;http://vault.vault:8200&quot;<br>      path: &quot;secret&quot;<br>      # Version is the Vault KV secret engine version.<br>      # This can be either &quot;v1&quot; or &quot;v2&quot;, defaults to &quot;v2&quot;<br>      version: &quot;v2&quot;<br>      auth:<br>        # points to a secret that contains a vault token<br>        # https://www.vaultproject.io/docs/auth/token<br>        kubernetes:<br>          mountPath: &quot;kubernetes&quot;<br>          role: &quot;demo&quot;</pre><p>Notice there are no credentials in this file, so it can be safely stored in Git as-is.</p><h3>Passing secrets from Vault to your application</h3><p>With the setup done, we’re now ready to use secrets in our application. First, create a set of secrets in Vault. You can do this with the CLI, the UI, Terraform, etc.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ooTDKZ9NNHUFREXW.png" /></figure><p>Next, create an <a href="https://github.com/kostis-codefresh/external-secrets-gitops-example/blob/main/manifests/app/external-secret-db.yml">External Secret YAML</a>.</p><pre>apiVersion: external-secrets.io/v1beta1<br>kind: ExternalSecret<br>metadata:<br>  name: my-db-credentials<br>spec:<br>  refreshInterval: &quot;15s&quot;<br>  secretStoreRef:<br>    name: vault-backend<br>    kind: ClusterSecretStore<br>  target:<br>    name: mysql-credentials<br>    template:<br>      engineVersion: v2<br>      data:<br>        credentials: |<br>          db_con=&quot;{{ .db_url }}&quot;<br>          db_user=&quot;{{ .db_username }}&quot;<br>          db_password=&quot;{{ .db_password }}&quot;<br>       <br>  data:<br>  - secretKey: db_url<br>    remoteRef:<br>      key: mysql_credentials<br>      property: url<br>  - secretKey: db_username<br>    remoteRef:<br>      key: mysql_credentials<br>      property: username<br>  - secretKey: db_password<br>    remoteRef:<br>      key: mysql_credentials<br>      property: password</pre><p>In this file, we’re using <a href="https://external-secrets.io/latest/guides/templating/">secret templating</a> to match the file format that the application expects (a simple <a href="https://github.com/kostis-codefresh/external-secrets-gitops-example/blob/main/src/credentials">property file</a>). Again, notice this file doesn’t have any confidential information. It’s simply a pointer to Hashicorp Vault. This means you can safely store it in Git (and apply it with Argo CD).<br>Finally, deploy the application with Argo CD. Here’s the<a href="https://github.com/kostis-codefresh/external-secrets-gitops-example/blob/main/manifests/app/deployment.yml"> deployment file</a>.</p><pre>---<br>apiVersion: apps/v1<br>kind: Deployment<br>metadata:<br>  name: gitops-secrets-deploy<br>spec:<br>  replicas: 1<br>  selector:<br>    matchLabels:<br>      app: gitops-secrets-app<br>  template:<br>    metadata:<br>      labels:<br>        app: gitops-secrets-app<br>    spec:<br>      containers:<br>      - name: gitops-secrets-app<br>        image: docker.io/kostiscodefresh/simple-secret-app:latest<br>        imagePullPolicy: Always  <br>        ports:<br>        - containerPort: 8080<br>        volumeMounts:<br>        - name: mysql<br>          mountPath: &quot;/secrets&quot;<br>          readOnly: true      <br>        livenessProbe:<br>          httpGet:<br>            path: /health/live<br>            port: 8080<br>        readinessProbe:<br>          httpGet:<br>            path: /health/ready<br>            port: 8080<br>      volumes:<br>      - name: mysql<br>        secret:<br>          secretName: mysql-credentials</pre><p>Notice that it mounts the mysql-credentials secret as a normal file in the container filesystem at the /secrets folder.</p><p>Now launch the application and you can see it has the correct secret information. You can do that with:</p><pre>kubectl port-forward svc/gitops-secrets-service 8080:8080</pre><p>Success! Our secrets from Vault are accessible to our application without saving anything in Git.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/751/0*Ag_Qv6iUg9gpivcp.png" /></figure><p>If we look at the Resource Overview in Argo CD we can see that ESO automatically generated a standard secret for us with Vault information. That secret was mounted on the container.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QpfDW5tkZxuS15rh.png" /></figure><p>Here’s what happened behind the scenes:</p><ol><li>We applied our <a href="https://github.com/kostis-codefresh/external-secrets-gitops-example/tree/main/manifests/app">application manifests</a> with Argo CD.</li><li>The service and deployment are standard Kubernetes resources and were applied directly by Argo CD.</li><li>The external secret is a CRD. It was passed to the external secret controller.</li><li>ESO sees that this is an external secret that mentions our Vault secret store.</li><li>ESO contacts Vault and asks for secret details.</li><li>Vault trusts ESO because Kubernetes authentication is active and they are running on the same Kubernetes cluster.</li><li>Vault gives the secret information to ESO.</li><li>ESO creates a standard Kubernetes secret using the template defined in the ExternalSecret CRD.</li><li>The job of ESO is over.</li><li>A standard Kubernetes deployment mounts the Kubernetes secret at /secrets/credentials in the filesystem of the container.</li><li>The application starts up. It reads /secrets/credentials like any other file without any knowledge of how this file was created.</li><li>The secrets appear on the web page.</li></ol><h3>Refreshing secrets without application restarts</h3><p>In the introduction, we mentioned that using secrets is just one part of the equation. We also need a way to easily rotate and revoke secrets.</p><p>One of the highlight features of ESO is that it can automatically refresh our secrets when they change in the original secret storage.</p><p>Our application takes advantage of this automatically.</p><ol><li>It loads the secret from a file and not an environment variable. Environment variables are fixed once a process starts and the only way to refresh them is to restart the app.</li><li>The source code <a href="https://github.com/kostis-codefresh/external-secrets-gitops-example/blob/main/src/simple-web-server.go#L85">automatically monitors the /secrets/credentials</a> file for changes and auto-reloads it if it changes.</li></ol><p>This means that we can change our secrets on the fly and see the application update right away. Let’s say that our database password became compromised and we had to update it. We’d make the change in Vault:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*iG-b1u3y9FcSqIMS.png" /></figure><p>In the next refresh period (defined in the External Secret CRD with the refreshInterval property), ESO will pass the new change to the application.</p><p>The application will update the secret on its own. How nice is that? 🙂</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/741/0*d6lhmZNDNphOOcwz.png" /></figure><p>We achieved one of the holy grails of good security practices. We can rotate and revoke secrets without restarting the application and without recreating any pods.</p><h3>Conclusion</h3><p>In this guide, you saw an alternative method of using secrets with GitOps, where an external source keeps the secret values. In Git, we only stored their configuration and how they’re mounted on their application. If your organization already has an external source for secrets, check if the External Secret Operator supports it.</p><p>You also saw how to prepare an application to automatically reload secrets when they change, making secret rotation simple.</p><p>We hope that now you have several choices for managing secrets with GitOps, you can choose your own level of risk or ease of use based on how closely you want to follow GitOps principles.</p><p>Happy deployments!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eb1eec1dab0d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/gitops-secrets-with-argo-cd-hashicorp-vault-and-the-external-secret-operator-eb1eec1dab0d">GitOps Secrets with Argo CD, Hashicorp Vault and the External Secret Operator</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating Temporary Preview Environments Based On Pull Requests With Argo CD And Codefresh]]></title>
            <link>https://medium.com/containers-101/creating-temporary-preview-environments-based-on-pull-requests-with-argo-cd-and-codefresh-86d36faa2010?source=rss----2f4a04e0f5fe---4</link>
            <guid isPermaLink="false">https://medium.com/p/86d36faa2010</guid>
            <category><![CDATA[argo-cd]]></category>
            <category><![CDATA[pull-request]]></category>
            <category><![CDATA[kubernetes]]></category>
            <category><![CDATA[applicationset]]></category>
            <category><![CDATA[devops]]></category>
            <dc:creator><![CDATA[Kostis Kapelonis]]></dc:creator>
            <pubDate>Thu, 30 May 2024 13:41:02 GMT</pubDate>
            <atom:updated>2024-05-30T13:41:02.324Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7nHsIHuIUHSdAMpa2RXUcA.png" /></figure><p>In our <a href="https://codefresh.io/blog/kubernetes-antipatterns-2/">big guide for Kubernetes deployments</a>, we explained the benefits of using dynamic environments for testing.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*jpaX8pKnE6wzNrFL.png" /></figure><p>The general idea is that each developer gets a preview environment instead of having a fixed number of testing/QA environments. The environment gets created on the fly when you open a pull request. Typically, it gets destroyed when you merge the pull request (or after a specific amount of time).</p><p>Previously, we explained how to implement this pattern using just <a href="https://codefresh.io/blog/unlimited-preview-environments/">Helm applications and an Ingress</a> on a Kubernetes cluster with a traditional deployment pipeline.</p><p>If your organization has moved to <a href="https://codefresh.io/learn/gitops/">GitOps, </a>you might wonder if you can replicate this setup with Argo CD (or the Codefresh runtime). The answer is yes!</p><p>In this guide, you learn how to create preview environments with Argo CD by using <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Generators-Pull-Request/">the Pull Request Generator</a> designed for this scenario.</p><h3>Keeping developers happy</h3><p>There are several approaches for creating preview environments with Argo CD. One major decision is whether you want a preview environment created for a pull request in the application source code, or a pull request on the Kubernetes manifests. As a reminder, <a href="https://codefresh.io/blog/argo-cd-best-practices/">it’s always a good idea to separate source code</a> from manifests in two separate Git repositories.</p><p>Both approaches are valid, but in this guide, we focus on pull requests created on the source code. Developers open these pull requests when they’ve implemented a feature and want to test or share it with their team.</p><p>We don’t want to force developers to learn how Argo CD or Kubernetes works. So, as far as they’re concerned, they open a pull request as usual, and after some time, a new preview environment gets created with the contents of the pull request.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/840/0*LXSJaW-03yd-j94f.png" /></figure><p>There are many ways to create preview environments for Kubernetes. For simplicity, we’ll follow the same assumptions <a href="https://codefresh.io/blog/unlimited-preview-environments/">as in the Helm article</a>.</p><ol><li>We’ll use a single cluster for all preview environments.</li><li>Argo CD will run inside the same cluster.</li><li>We’ll deploy each pull request to its own namespace.</li><li>The name of the namespace will match the name of the pull request.</li></ol><p>If two developers create two pull requests named “my-billing-feature” and “fix-queue-size”, two new namespaces with the same name get created with the respective deployments. Each namespace contains the container from the respective Continuous Integration build. So both developers can test their own features in isolation.<br>In a future article, we’ll cover more advanced scenarios, like <a href="https://www.vcluster.com/">creating a virtual cluster </a>per pull request.</p><h3>Generating Argo CD applications automatically</h3><p>The basic concept of an Argo CD installation <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#applications">is the application</a>, which is a link between a Git repository and a destination cluster. We explained how to create Argo CD applications using generators in our<a href="https://codefresh.io/blog/how-to-structure-your-argo-cd-repositories-using-application-sets/"> article about Argo CD application sets</a>.</p><p>Things are a bit different in preview environments, as we don’t really have a folder in Git that represents the application. If we did, we could use the Git generator and create preview environments as more folders get created/destroyed.</p><p>In theory, we could instruct our Continuous Integration (CI) system to commit new folders on the manifest repo for each preview application. But this process is too cumbersome and there’s no need to go down that route. The <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/Generators-Pull-Request/">Pull Request (PR) Generator</a> solves this problem by creating applications directly from pull requests instead of folders.</p><p>Unlike other Argo CD generators, the PR Generator is special because it can monitor <strong>any </strong>Git repository for pull requests. It doesn’t have to be the same one that has manifests. This lets you monitor the Git repository for source code while still deploying manifests from a <strong>different </strong>Git repository.</p><p>Here’s our updated architecture where we’ve expanded the “magic” section from the previous picture.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*6L_ie33_dU7b_01O.png" /></figure><p>The whole process goes like this:</p><ol><li>There’s one Git repository with just the source code of the application. There’s another Git repository with Kubernetes manifests (Helm, Kustomize, or plain manifests).</li><li>A developer creates a branch on the source Git repository and starts working on a feature.</li><li>When the developer finishes, they create a pull request on the branch (usually pending tests and feedback from their team).</li><li>Argo CD notices the pull request on the source code Git repository and creates a new application for it.</li><li>The Pull Request Generator takes the manifests from the infrastructure repository and templates/modifies them according to the pull request in the source code repo.</li><li>A new application gets created and deployed in the Kubernetes cluster.</li><li>The developer accesses the new application, reviews it, runs unit tests, shares it, etc.</li><li>When the pull request gets merged/closed, Argo CD automatically discards the preview environment.</li><li>The process works in parallel for all developers working on features.</li></ol><p>This is the heart of the process. There are also several supporting factors we must consider to create a rock-solid experience for developers.</p><h3>Coordinating with the Continuous Integration system</h3><p>The main goal of the preview environment is to let developers test their new features in isolation. Before dealing with Argo CD deployments, we need a Continuous Integration (CI) system that covers two basic requirements:</p><ol><li>It automatically builds a container every time you open a pull request.</li><li>It pushes the container using the Git hash of the source code as a tag.</li></ol><p>We use Codefresh CI in our example application, but any CI system should be able to cover these requirements.</p><p>You can find the source code of the example application at <a href="https://github.com/kostis-codefresh/preview-env-source-code">https://github.com/kostis-codefresh/preview-env-source-code</a>. All the manifests are at <a href="https://github.com/kostis-codefresh/preview-env-manifests">https://github.com/kostis-codefresh/preview-env-manifests</a>. Note that we follow the<a href="https://codefresh.io/blog/argo-cd-best-practices/"> Argo CD best practices</a> for splitting source code from Kubernetes manifests.</p><p>This example uses Kustomize. For an example with Helm, see <a href="https://piotrminkowski.com/2023/06/19/preview-environments-on-kubernetes-with-argocd/">Piot’s guide on the same subject</a>.</p><p>Here’s a minimal Codefresh pipeline that builds a container image with the same tag as the source code.</p><pre>version: &quot;1.0&quot;<br>stages:<br>  - &quot;clone&quot;<br>  - &quot;build&quot;<br>steps:<br>  clone:<br>    title: &quot;Cloning repository&quot;<br>    type: &quot;git-clone&quot;<br>    repo: &quot;kostis-codefresh/preview-env-source-code&quot;<br>    revision: &quot;${{CF_REVISION}}&quot;<br>    git: &quot;github-1&quot;<br>    stage: &quot;clone&quot;<br><br><br>  build:<br>    title: &quot;Building Docker image&quot;<br>    type: &quot;build&quot;<br>    image_name: &quot;kostiscodefresh/my-preview-app&quot;<br>    working_directory: &quot;${{clone}}&quot;<br>    tag: &quot;${{CF_SHORT_REVISION}}&quot;<br>    dockerfile: &quot;Dockerfile&quot;<br>    stage: &quot;build&quot;</pre><p>The important part here is the “tag” line that instructs Codefresh to tag the Docker image with the same hash as the source code. The short revision variable represents the Git hash and gets automatically injected by Codefresh with the commit that we checked out.</p><p>The second requirement is to make this pipeline run only for pull request events. This happens out of the box with <a href="https://codefresh.io/docs/docs/pipelines/triggers/git-triggers/">Codefresh Git triggers</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/589/0*qTi-V7om-uIkb7Ut.png" /></figure><p>We now have a pipeline that satisfies our requirements. If you create pull requests on the source code repository, Codefresh automatically builds them. Here’s an example build.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*NxkX9CaWkWDzk8zh.png" /></figure><p>When the pipeline has finished, <a href="https://hub.docker.com/r/kostiscodefresh/my-preview-app/tags">you can see in Docker Hub</a> (or whichever container registry you’re using) the resulting images tagged with the same Git hash as the source code branch:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*apXjkiiYy2MXUO9q.png" /></figure><p>This concludes the setup for the Continuous Integration part. Let’s move on to the deployment process.</p><h3>Performing the initial deployment</h3><p>With the CI part in place, we can create our Pull Request Generator. <a href="https://github.com/kostis-codefresh/preview-env-manifests/blob/main/argocd/pr-generator.yml">Here’s the initial version</a>:</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: ApplicationSet<br>metadata:<br>  name: myapps<br>  namespace: argocd  <br>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - pullRequest:<br>      github:<br>        owner: kostis-codefresh<br>        repo: preview-env-source-code<br>      requeueAfterSeconds: 180<br>  template:<br>    metadata:<br>      name: &#39;myapp-{{.branch}}&#39;<br>    spec:<br>      source:<br>        repoURL: &#39;https://github.com/kostis-codefresh/preview-env-manifests.git&#39;<br>        targetRevision: HEAD<br>        path: kustomize-preview-app/<br>        kustomize:<br>          images:<br>            - &#39;docker.io/kostiscodefresh/my-preview-app:{{.head_short_sha_7}}&#39;<br>      project: &quot;default&quot;<br>      destination:<br>        server: https://kubernetes.default.svc<br>        namespace: &#39;preview-{{.branch_slug}}&#39;<br>      syncPolicy:<br>        automated:<br>          prune: true  <br>        syncOptions:<br>        - CreateNamespace=true</pre><p>This Pull Request Generator does the following:</p><ol><li>Monitors the <a href="https://github.com/kostis-codefresh/preview-env-source-code">kostis-codefresh/preview-env-source code</a> repository for pull requests.</li><li>If it finds a pull request, it creates an Argo CD application named myapp-&lt;name of branch&gt;.</li><li>The Argo CD application syncs manifests found at preview-env-manifests folder “<a href="https://github.com/kostis-codefresh/preview-env-manifests/tree/main/kustomize-preview-app">kustomize-preview-app</a>”.</li><li>It deploys the manifests to the local cluster (the same cluster where Argo CD is installed) at a namespace with the same name as the branch of the pull request prepended with “preview”.</li><li>It sets the container tag with the same hash as the source code, so that it matches the CI pipelines of Codefresh described in the previous section.</li></ol><p>You can test this Pull Request Generator by applying it to your local Argo CD instance.</p><pre>kubectl apply -f pr-generator.yml -n argocd</pre><p>Then, you can start creating pull requests on your source code repository and see applications created automatically. Isn’t this great?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*Uf-WKMZbb9oHWklg.png" /></figure><p>Note that in our example, we instructed Argo CD to check for new pull requests every 3 minutes via the <strong>requeueAfterSeconds </strong>property. You need to fine-tune this property according to your organization’s needs.</p><p>As always, you can click on any application to see its Kubernetes resources.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*aFoCpYAoaHCJxDt6.png" /></figure><p>That’s it!</p><p>Now every time a developer creates a new pull request, their container image gets deployed automatically in the same namespace as the branch name. You can also inspect the applications manually in the cluster with the CLI.</p><pre>➜  argocd git:(main) kubectl get ns<br>NAME                       STATUS   AGE<br>argocd                     Active   26d<br>default                    Active   26d<br>kube-node-lease            Active   26d<br>kube-public                Active   26d<br>kube-system                Active   26d<br>preview-fix-queue-size     Active   2m14s<br>preview-my-billing-feature   Active   2m14s</pre><p>To make things easy, we use the “preview” prefix for all namespaces. But you can choose any naming convention for your organization.</p><h3>Handling subsequent commits for the same pull request</h3><p>Often, a developer notices something in a preview environment, fixes it, and wants to perform a redeployment. With our setup, this happens automatically. If the developer commits again, the Pull Request Generator detects the commit and redeploys the application.</p><p>Note that in our example, we set the refresh period to 3 minutes. Our application is very simple, so usually when the Pull Request Generator is redeploying an environment, the container image is already built and pushed to our registry.</p><p>There’s also the scenario where the image build is not yet complete. Argo CD will mark the deployment with an ImagePullBackOff error. Kubernetes will automatically retry the pull later and the application will eventually succeed. But note that this will work only if your application takes less than 5 minutes to build as this is the default timeout for image pull operations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*32GjqPktbO2djjo8.png" /></figure><p>If this is an issue for your developers, you can simply instruct the Pull Request Generator to only deploy pull requests with a special label, for example “preview-ready”. Then you have your CI system or developers add that label on the pull request after the CI build has finished. This way you guarantee the image is always there when Argo CD creates the temporary environment.</p><h3>Destroying the temporary Argo CD application</h3><p>Destroying an environment is straightforward. After you merge or reject/close a pull request, Argo CD detects it and automatically discards the respective environment.</p><p>Note that in our simple example, the preview namespace stays behind. To delete the namespace as well, you need to add a namespace resource in your Helm chart or Kustomize folder.</p><p>Alternatively, you can set up a job that periodically clears unused namespaces.</p><h3>Passing the name of the branch to a preview environment</h3><p>The setup we have works great for several common scenarios. One key point is that the application itself doesn’t know if it’s running in a preview environment.</p><p>Sometimes, however, you want to pass to the application, information about the pull request environment, and most probably give it as a parameter with the name of the branch or the Git hash. These can be used to make the application adapt in some way to the preview environment. An example scenario would be to set up an Ingress with the URL that matches the pull request. So in our example, we’d have URLs like this:</p><ul><li><a href="http://example.com/fix-queue-size">example.com/fix-queue-size</a></li><li><a href="http://example.com/my-billing-app">example.com/my-billing-app</a></li></ul><p>Our example application source code already <a href="https://github.com/kostis-codefresh/preview-env-source-code/blob/main/main.go#L21">accepts the Git hash and the branch name as environment variables</a>.</p><p>We can modify our Pull Request Generator to pass these parameters in the created application.</p><pre>apiVersion: argoproj.io/v1alpha1<br>kind: ApplicationSet<br>metadata:<br>  name: myapps<br>  namespace: argocd  <br>spec:<br>  goTemplate: true<br>  goTemplateOptions: [&quot;missingkey=error&quot;]<br>  generators:<br>  - pullRequest:<br>      github:<br>        owner: kostis-codefresh<br>        repo: preview-env-source-code<br>      requeueAfterSeconds: 180<br>  template:<br>    metadata:<br>      name: &#39;myapp-{{.branch}}&#39;<br>    spec:<br>      source:<br>        repoURL: &#39;https://github.com/kostis-codefresh/preview-env-manifests.git&#39;<br>        targetRevision: HEAD<br>        path: kustomize-preview-app/<br>        kustomize:<br>          images:<br>            - &#39;docker.io/kostiscodefresh/my-preview-app:{{.head_short_sha_7}}&#39;        <br>          patches:<br>            - target:<br>                kind: Deployment<br>                name: simple-deployment<br>              patch: |-<br>                - op: replace<br>                  path: /spec/template/spec/containers/0/env/0<br>                  value:<br>                    name: GIT_BRANCH<br>                    value: &#39;{{.branch_slug}}&#39;<br>                - op: replace<br>                  path: /spec/template/spec/containers/0/env/1<br>                  value:<br>                    name: GIT_HASH<br>                    value: &#39;{{.head_short_sha_7}}&#39;<br>      project: &quot;default&quot;<br>      destination:<br>        server: https://kubernetes.default.svc<br>        namespace: &#39;preview-{{.branch_slug}}&#39;<br>      syncPolicy:<br>        automated:<br>          prune: true  <br>        syncOptions:<br>        - CreateNamespace=true</pre><p>Now, when you create a preview application, if you visit the application, you see that it knows which branch and hash it was created from.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/661/0*Mmn8ED6_tiEwOA-p.png" /></figure><p>Notice that this override syntax is something we DO NOT recommend for static and/or production environments. It mixes Argo CD resources with information that belongs to the Kubernetes manifests. We recognize, however, that some developers like this capability, so we include it in this guide for completeness.</p><h3>Limitations and Future Considerations</h3><p>The approach we’ve seen — monitoring the source code repository for pull requests — is very simple to set up, but there are some limitations:</p><ol><li>By default, it deploys the new manifests without actually waiting for the container image to be ready. Kubernetes will automatically retry to pull it for the first 5 minutes only. So if your CI system needs more than 5 minutes to build (and test) the application, this approach won’t work for you.</li><li>Developers can only create a preview environment for a single application (the one that has the pull request). If your organization has adopted microservices, a developer might want to create a common preview environment with different applications from different Git repositories. This scenario is not possible with the approach in this guide.</li><li>There are some cases where the preview environment is not just a new container tag, but the developer wants to preview a change in configuration as well, for example, introducing a new variable or configuration file. Again, this scenario is not possible with the approach in this guide</li></ol><p>You can solve all these limitations by changing the behavior of the Pull Request Generator to monitor the Kubernetes manifest repository for pull requests. This process is more complex, though, so we’ll cover it in a future article.</p><h3>Summary</h3><p>In this guide, you’ve seen how you can use Argo CD even for preview environments. Using the <a href="https://codefresh.io/learn/argo-cd/argocd-applicationset-multi-cluster-deployment-made-easy-with-code-examples/">ApplicationSet</a> Pull Request Generator, you can create temporary/ephemeral deployments so developers can test a feature in isolation. Updating the environment with new code when the developer performs additional commits in the pull request is also very easy to set up.</p><p><a href="https://codefresh.io/blog/gitops-benefits-and-considerations/">The benefits of GitOps</a> can now apply to preview environments as well. For example, it’s now possible to quickly find configuration differences between environments or even revert back to a preview version of the environment using just a Git action.</p><p>With the power of the ApplicationSet Generator, you can also automatically delete all inactive temporary environments. This helps with cloud costs and long-term maintenance of temporary environments.</p><p>The process is fully transparent for developers. They can keep creating pull requests in the source code repository like they already do. After they merge/reject the pull request, Argo CD discards the temporary environment without any human intervention.</p><p>We hope you now have a good starting point for your preview environment strategy.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=86d36faa2010" width="1" height="1" alt=""><hr><p><a href="https://medium.com/containers-101/creating-temporary-preview-environments-based-on-pull-requests-with-argo-cd-and-codefresh-86d36faa2010">Creating Temporary Preview Environments Based On Pull Requests With Argo CD And Codefresh</a> was originally published in <a href="https://medium.com/containers-101">Container Hub</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>