<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged argocd</title>
<link>https://coderlegion.com/tag/argocd</link>
<description>Powered by Question2Answer</description>
<item>
<title>How to manage releases and rollbacks with Argo CD in Octopus Deploy</title>
<link>https://coderlegion.com/21207/how-to-manage-releases-and-rollbacks-with-argo-cd-in-octopus-deploy</link>
<description>&lt;p&gt;Most teams say they can roll back, but far fewer have actually tried it.&lt;/p&gt;
&lt;p&gt;When they actually do, the plan is typically a runbook in a wiki, or the fact that, in theory, we can always revert to the last working commit. When you run Kubernetes, Argo CD is instrumental in keeping your resources in sync.&lt;/p&gt;
&lt;p&gt;Argo CD is good at reconciling whatever's in your Git repo to your cluster. When a deploy goes sideways, &quot;roll back&quot; turns into &quot;revert the right commit, in the right repo, without breaking the other applications syncing alongside it&quot;, which is fine on paper and miserable in practice.&lt;/p&gt;
&lt;p&gt;In this post, we'll explore how to manage releases in Octopus Deploy, how it integrates with Argo CD, and discuss, as well as implement, a rollback.&lt;/p&gt;
&lt;h3 id=&quot;what-argo-cd-doesn-t-do-and-why-it-matters-&quot;&gt;What Argo CD doesn't do (and why it matters)&lt;/h3&gt;
&lt;p&gt;Argo CD is great at one job: take whatever's in your Git repo and reconcile a cluster to match it. It'll show you what's drifted, when it last synced, and which resources are unhealthy.&lt;/p&gt;
&lt;p&gt;However, what it deliberately doesn't do is anything &lt;em&gt;above&lt;/em&gt; the cluster.&lt;/p&gt;
&lt;p&gt;There's no concept of a release. Each &lt;code&gt;Application&lt;/code&gt; is an independent entity, and its current state is whatever commit &lt;code&gt;HEAD&lt;/code&gt; happens to be on. If Tuesday's image tag turns out to be a regression, your options are: revert the commit in Git and wait for the next reconcile, or open the Argo CD UI and sync to a previous revision by hand.&lt;/p&gt;
&lt;p&gt;More importantly, the processes around what constitutes a release that can be synced are something Argo simply wasn't built for.&lt;/p&gt;
&lt;h4 id=&quot;how-octopus-manages-releases-with-argo-cd&quot;&gt;How Octopus manages releases with Argo CD&lt;/h4&gt;
&lt;p&gt;A release in Octopus is a versioned snapshot of three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the deployment process (the steps it runs)&lt;/li&gt;
&lt;li&gt;the variables those steps use, and&lt;/li&gt;
&lt;li&gt;the package or image versions they reference.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once a release is created, it's frozen. You can promote &lt;code&gt;0.0.2&lt;/code&gt; through &lt;code&gt;Development&lt;/code&gt;, then &lt;code&gt;Staging&lt;/code&gt;, then &lt;code&gt;Production&lt;/code&gt;, and each environment receives the &lt;em&gt;same&lt;/em&gt; release, not whatever happens to sit at &lt;code&gt;HEAD&lt;/code&gt; when you trigger the deployment.&lt;/p&gt;
&lt;p&gt;With the Argo CD integration, that release model integrates into your existing GitOps setup. When you deploy a release in Octopus, you can have an Argo CD step that rewrites the image tags in your repo, commits the change with the credentials you've attached, then asks Argo CD to sync. Argo CD keeps doing the reconciliation while Octopus keeps the lifecycle.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;This tutorial assumes you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A working Kubernetes cluster (any flavour)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kubernetes.io/docs/tasks/tools/&quot;&gt;&lt;code&gt;kubectl&lt;/code&gt;&lt;/a&gt; installed and pointed at the cluster&lt;/li&gt;
&lt;li&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://cli.github.com/&quot;&gt;GitHub CLI (&lt;code&gt;gh&lt;/code&gt;)&lt;/a&gt; installed and authenticated (&lt;code&gt;gh auth login&lt;/code&gt;). We'll use it to create the demo repo in one command&lt;/li&gt;
&lt;li&gt;An Octopus Cloud (or self-hosted) instance, with the &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/docs/argo-cd&quot;&gt;Argo CD Gateway&lt;/a&gt; installed in the cluster and an Argo CD instance already registered against Octopus. If you haven't done that yet, follow the &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/blog/connecting-aws-eks-argo-cd-to-octopus-cloud&quot;&gt;Connecting AWS EKS Argo CD to Octopus Cloud&lt;/a&gt; guide first; the same flow works for any cluster.&lt;/li&gt;
&lt;li&gt;A Git &lt;strong&gt;Personal Access Token (PAT)&lt;/strong&gt; scoped to your demo repository, Octopus will use it to push image-tag commits back&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-1-create-the-octopus-project&quot;&gt;Step 1: Create the Octopus project&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Sign in to Octopus and head to &lt;strong&gt;Projects&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add Project&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give it a name (we'll use &lt;code&gt;argo-demo&lt;/code&gt;), pick &lt;strong&gt;Kubernetes&lt;/strong&gt; as the deployment target, and answer &lt;strong&gt;Yes&lt;/strong&gt; to &lt;em&gt;Are you using Argo CD?&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Leave the lifecycle on &lt;code&gt;Default Lifecycle&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-add-new-project.png&quot; alt=&quot;The new project dialog for a new argo-demo project&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Creating the argo-demo project.&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Hit &lt;strong&gt;Create Project&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;step-2-add-the-argo-cd-step&quot;&gt;Step 2: Add the Argo CD step&lt;/h4&gt;
&lt;p&gt;You'll land on the Process editor.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click &lt;strong&gt;Add Step&lt;/strong&gt; and filter by &lt;strong&gt;Containers and Orchestration&lt;/strong&gt; &amp;gt; &lt;strong&gt;Argo CD&lt;/strong&gt; in the left sidebar. You'll see two installed step templates:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Update Argo CD Application Image Tags&lt;/strong&gt;, which updates an image tag in your manifests and commits the change for Argo CD to sync&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update Argo CD Application Manifests&lt;/strong&gt;, which generate manifests from templates using Octopus variables, and commit those.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We'll use the first one for this walkthrough.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-add-step-argo-cd.png&quot; alt=&quot;The add step dialog&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Argo CD category in the step picker. Both templates ship with Octopus once an Argo CD instance is registered.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;step-3-set-up-the-manifests-repository&quot;&gt;Step 3: Set up the manifests repository&lt;/h4&gt;
&lt;p&gt;Argo CD syncs from Git, and Octopus pushes image-tag updates back to that same repo, so before either side has anything to do, we need a repository with a workload manifest in it.&lt;/p&gt;
&lt;p&gt;We'll keep the layout small: one Deployment, one Service, and the Argo CD &lt;code&gt;Application&lt;/code&gt; that wires the two systems together.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;argocd-octopus-demo/
├── application.yaml      # the Argo CD Application (apply once to the cluster)
└── manifests/
    └── app.yaml          # workload Argo CD reconciles from this repo
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Create the directory locally:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p argocd-octopus-demo/manifests
cd argocd-octopus-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Write the workload manifest. We're using &lt;a rel=&quot;nofollow&quot; href=&quot;https://hub.docker.com/r/nginxdemos/hello&quot;&gt;&lt;code&gt;nginxdemos/hello:plain-text&lt;/code&gt;&lt;/a&gt;, a tiny nginx that responds with a text page naming the pod that served the request. It's a single-container app with no state, perfect for proving reconciliation worked end-to-end.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat &amp;gt; manifests/app.yaml &amp;lt;&amp;lt;'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello
  labels:
    app: hello
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello
  template:
    metadata:
      labels:
        app: hello
    spec:
      containers:
        - name: hello
          image: nginxdemos/hello:plain-text
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 25m
              memory: 32Mi
---
apiVersion: v1
kind: Service
metadata:
  name: hello
spec:
  type: ClusterIP
  selector:
    app: hello
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Initialize the repo, create a public GitHub repo from it, and push the first commit:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init -b main
git add .
git commit -m &quot;chore: init&quot;
gh repo create &amp;lt;your-org&amp;gt;/argocd-octopus-demo \
  --public \
  --description &quot;Argo CD + Octopus Deploy demo manifests&quot; \
  --source=. \
  --push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the repo live, we can write the Argo CD &lt;code&gt;Application&lt;/code&gt; that points at it.&lt;/p&gt;
&lt;h4 id=&quot;step-4-scope-the-argo-cd-application-with-annotations&quot;&gt;Step 4: Scope the Argo CD Application with annotations&lt;/h4&gt;
&lt;p&gt;The Octopus gateway pulls the full list of Argo CD Applications from your cluster, but it doesn't yet know &lt;em&gt;which&lt;/em&gt; of them belong to &lt;em&gt;this&lt;/em&gt; Octopus project. You can tell it with three annotations on the Argo Application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;argo.octopus.com/project[.&amp;lt;source-name&amp;gt;]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;argo.octopus.com/environment[.&amp;lt;source-name&amp;gt;]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;argo.octopus.com/tenant[.&amp;lt;source-name&amp;gt;]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;.&amp;lt;source-name&amp;gt;&lt;/code&gt; suffix matches &lt;code&gt;spec.source.name&lt;/code&gt; on the Application, which lets you scope different sources of a multi-source Application to different Octopus projects.&lt;/p&gt;
&lt;p&gt;For our &lt;code&gt;argo-demo&lt;/code&gt; project and &lt;code&gt;Development&lt;/code&gt; environment, an annotated &lt;code&gt;Application&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: argo-demo
  namespace: argocd
  annotations:
    argo.octopus.com/project.argo-demo-source: argo-demo
    argo.octopus.com/environment.argo-demo-source: development
spec:
  project: default
  source:
    repoURL: https://github.com/&amp;lt;your-org&amp;gt;/argocd-octopus-demo.git
    targetRevision: HEAD
    path: manifests
    name: argo-demo-source
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two annotations there tie this Application to the &lt;code&gt;argo-demo&lt;/code&gt; Octopus project, scoped to the &lt;code&gt;development&lt;/code&gt; environment. The suffix &lt;code&gt;.argo-demo-source&lt;/code&gt; matches the &lt;code&gt;spec.source.name&lt;/code&gt; field below it.&lt;/p&gt;
&lt;p&gt;Apply it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl apply -f application.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Within a few seconds, the Octopus gateway picks up the new annotations, and the Application appears in the project's deployment preview.&lt;/p&gt;
&lt;h4 id=&quot;step-5-preview-the-deployment&quot;&gt;Step 5: Preview the deployment&lt;/h4&gt;
&lt;p&gt;Back in Octopus, in the Process tab, click on the step you just added.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-deployment-preview.png&quot; alt=&quot;Deployment preview screen&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Octopus has discovered the argo-demo Application via the annotations&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Annotations have been discovered, but flags that the source repo is missing Git credentials. Click View Applications to see what was matched.&lt;/p&gt;
&lt;p&gt;The deployment preview opens. You should see the &lt;code&gt;argo-demo&lt;/code&gt; application listed under the &lt;code&gt;Rollback&lt;/code&gt; Argo CD instance, scoped to the &lt;code&gt;Development&lt;/code&gt; environment, with the source repo URL underneath.&lt;/p&gt;
&lt;h4 id=&quot;step-6-connect-git-credentials&quot;&gt;Step 6: Connect Git credentials&lt;/h4&gt;
&lt;p&gt;Octopus needs Git credentials so the step can push the new image-tag commit.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click the &lt;strong&gt;Connect Git Credential&lt;/strong&gt; dropdown next to the repo URL.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-git-credential-dropdown.png&quot; alt=&quot;The connect Git credential dropdown&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Two paths: spin up a new credential in Octopus from a PAT, or reuse one you've already attached to this space&lt;/em&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Pick &lt;strong&gt;Create a new Git credential&lt;/strong&gt; (or &lt;strong&gt;Connect an existing Git credential&lt;/strong&gt; if you've already added one to the space).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once the credential is attached, the warning on the deployment preview clears and the source shows up as ready to update.&lt;/p&gt;
&lt;h4 id=&quot;step-7-configure-the-step&quot;&gt;Step 7: Configure the step&lt;/h4&gt;
&lt;p&gt;Back on the step, fill in the rest:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-step-config.png&quot; alt=&quot;The process editor screen with the changes&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Confirm your step settings look good&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The key fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step Name&lt;/strong&gt;, leave as the default unless you have multiple Argo CD steps in one process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container Images&lt;/strong&gt;, click &lt;strong&gt;Add&lt;/strong&gt; to declare which container image(s) this step should bump. You'll point it at a &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/docs/projects/steps/package-references&quot;&gt;package/container reference&lt;/a&gt; so Octopus knows which image and which feed to pull tags from.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git Commit Settings&lt;/strong&gt;, the commit author/email Octopus uses when it pushes the tag update.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit Message&lt;/strong&gt;, supports Octopus variables, so something like &lt;code&gt;chore: bump image to #{Octopus.Action.Package[hello].PackageVersion}&lt;/code&gt; is a good default.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Save the step.&lt;/p&gt;
&lt;h4 id=&quot;step-8-create-a-release&quot;&gt;Step 8: Create a release&lt;/h4&gt;
&lt;p&gt;Head to the project dashboard, and you should now see the &lt;strong&gt;Create Release&lt;/strong&gt; prompt.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-project-dashboard.png&quot; alt=&quot;The project dashboard&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The project dashboard once the deployment process has at least one step. Hit Create Release.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On the release form, give it a version (Octopus auto-suggests 0.0.1) and click Save.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-create-release.png&quot; alt=&quot;Create release page&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Create release page&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Release notes are optional but recommended for anything you'd want a changelog entry for later.&lt;/p&gt;
&lt;h4 id=&quot;step-9-deploy-to-development&quot;&gt;Step 9: Deploy to Development&lt;/h4&gt;
&lt;p&gt;Pick &lt;strong&gt;Deploy to Development&lt;/strong&gt; on the release page. Octopus kicks off the deployment, and the task log streams the three sub-steps in real time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Update Argo CD Application Image Tags&lt;/strong&gt;, Octopus rewrites the image tag in the manifest and pushes the commit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Triggering Argo CD application syncs&lt;/strong&gt;, Octopus asks Argo CD to sync the Application&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wait for Argo CD Application Health&lt;/strong&gt;, Octopus polls the Application until it reports Healthy&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When all three turn green, you'll see a completion line in the task log, and a &lt;strong&gt;Deploy to Staging…&lt;/strong&gt; button appears at the top right, ready for the next environment in the lifecycle.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-deploy-complete.png&quot; alt=&quot;Deployment task screen&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Deployment completes in just over 90 seconds&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can verify the rollout landed on the cluster side too:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl -n default get deploy hello -o \
jsonpath='{.spec.template.spec.containers[0].image}{&quot;\n&quot;}'
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-10-rolling-back-redeploying-a-previous-release&quot;&gt;Step 10: Rolling back: redeploying a previous release&lt;/h4&gt;
&lt;p&gt;The deploy half of the demo is done. The other half is the one that actually earns its keep: a release that turns out to be bad, and how Octopus takes you back to the previous one without anyone opening a terminal.&lt;/p&gt;
&lt;p&gt;To simulate it, hit &lt;strong&gt;Create Release&lt;/strong&gt; again, save as &lt;code&gt;0.0.3&lt;/code&gt;, and deploy it to &lt;code&gt;Development&lt;/code&gt;. Imagine the new image is broken (regression, missing env var, whatever). To roll back, head to &lt;strong&gt;Releases&lt;/strong&gt; in the project's left sidebar.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-all-releases.png&quot; alt=&quot;Releases screen&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Every release Octopus has ever cut for this project, newest first. Each one is a versioned artifact you can revisit and redeploy. To roll back, open the last known-good release, 0.0.2&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Click &lt;code&gt;0.0.2&lt;/code&gt; to open it, then look at the lifecycle table. The &lt;code&gt;Development&lt;/code&gt; row will show its previous deployment with a &lt;strong&gt;Redeploy…&lt;/strong&gt; button.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-redeploy-002.png&quot; alt=&quot;The redeploy button is shown next to the release&quot;&gt;&lt;/p&gt;
&lt;p&gt;Click &lt;strong&gt;Redeploy…&lt;/strong&gt;, confirm, and the same three sub-steps run again.&lt;/p&gt;
&lt;p&gt;Octopus rewrites the image tag in Git back to the one &lt;code&gt;0.0.2&lt;/code&gt; used, commits it, asks Argo CD to sync, then waits until the Application reports Healthy.&lt;/p&gt;
&lt;p&gt;When the task completes, the project dashboard shows &lt;code&gt;0.0.2&lt;/code&gt; back in Development.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://octopus.com/blog/img/manage-releases-rollbacks-argo-cd/octopus-rollback-healthy.png&quot; alt=&quot;Project dashboard screen&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The dashboard after the rollback shows a healthy application.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;when-to-roll-back-vs-when-to-roll-forward&quot;&gt;When to roll back vs when to roll forward&lt;/h3&gt;
&lt;p&gt;For this demo, rolling back was a single click. That doesn't mean rolling back is always the right call. Rollbacks have a way of failing that isn't always talked about.&lt;/p&gt;
&lt;p&gt;They assume the rest of the system is in a state that the old version can still talk to. Most of the time it is. Sometimes it very much isn't.&lt;/p&gt;
&lt;p&gt;Take, for instance, a database. If a bad release adds a new column and your users have been generating data through that path for an hour, rolling the application code back doesn't unwind the schema or the data. The old app meets the new schema and either ignores fields it doesn't know about (best case) or crashes on them (worst case)&lt;/p&gt;
&lt;p&gt;Refactoring databases buys you flexibility. If schema changes go in through additive, backward-compatible migrations (add columns nullable, write to both old and new fields for a release cycle, then drop the old in a later release), rollbacks stay safe much longer.&lt;/p&gt;
&lt;h3 id=&quot;make-rollbacks-boring&quot;&gt;Make rollbacks boring&lt;/h3&gt;
&lt;p&gt;If you made it this far, you've got the full loop: an Argo CD &lt;code&gt;Application&lt;/code&gt; scoped to an Octopus project with annotations, a deploy that flows from a click down through Git into your cluster, and a redeploy that takes you back to a known-good release the same way.&lt;/p&gt;
&lt;p&gt;Argo CD keeps reconciling, your manifests stay the source of truth, and Octopus wraps versioning, approvals, audit, and that redeploy button around them. The rollback nobody had tested becomes a button two engineers know how to press.&lt;/p&gt;
&lt;p&gt;Beyond manual rollbacks, Octopus enables you to build out full lifecycles on your deployments, so this can all be automated conditionally.&lt;/p&gt;
&lt;p&gt;Extend the pipeline to your real environments using the &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/docs/argo-cd&quot;&gt;Argo CD integration docs&lt;/a&gt;, or if you don't have an Octopus instance yet, &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/start&quot;&gt;start a free trial&lt;/a&gt; and run this end-to-end on your own cluster.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21207/how-to-manage-releases-and-rollbacks-with-argo-cd-in-octopus-deploy</guid>
<pubDate>Tue, 23 Jun 2026 12:52:41 +0000</pubDate>
</item>
<item>
<title>ArgoCD Guide: GitOps Continuous Delivery for Kubernetes</title>
<link>https://coderlegion.com/15641/argocd-guide-gitops-continuous-delivery-for-kubernetes</link>
<description>
&lt;a href=&quot;https://alexandre-vazquez.com/argocd-guide/&quot; title=&quot;ArgoCD Guide: GitOps Continuous Delivery for Kubernetes&quot; rel=&quot;nofollow&quot;&gt;&lt;img width=&quot;1672&quot; height=&quot;941&quot; src=&quot;https://mlk5tkvgfeoa.i.optimole.com/cb:s8WW.68/w:1672/h:941/q:mauto/f:best/https://alexandre-vazquez.com/wp-content/uploads/2026/04/ArgoCD.avif&quot; class=&quot;webfeedsFeaturedVisual wp-post-image&quot; alt=&quot;ArgoCD Guide: GitOps Continuous Delivery for Kubernetes&quot; style=&quot;display: block; margin: auto; margin-bottom: 5px;max-width: 100%;&quot; title=&quot;ArgoCD Guide: GitOps Continuous Delivery for Kubernetes 4&quot;&gt;&lt;/a&gt;
&lt;p class=&quot;lead&quot;&gt;ArgoCD has become the de facto standard for GitOps-based continuous delivery in Kubernetes. If you are running production workloads on Kubernetes and still deploying with raw &lt;code&gt;kubectl apply&lt;/code&gt; or untracked Helm releases, ArgoCD solves a class of problems you may not even know you have yet. This guide covers everything from core concepts to production-grade configuration.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;The Problem ArgoCD Solves&lt;/h2&gt;



&lt;p&gt;Traditional CI/CD pushes deployments into a cluster. A CI system runs tests, builds an image, and then executes &lt;code&gt;kubectl apply&lt;/code&gt; or &lt;code&gt;helm upgrade&lt;/code&gt; against the cluster. This model has several structural problems:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Drift goes undetected.&lt;/strong&gt; Someone applies a hotfix directly to the cluster. Now your Git repository no longer reflects reality, and nobody knows it.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;No single source of truth.&lt;/strong&gt; The cluster state is authoritative, not Git. Your desired state and actual state can diverge silently.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Rollback is painful.&lt;/strong&gt; Rolling back a bad deployment means re-running old CI pipelines or manually reversing changes, neither of which is fast.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Multi-cluster management compounds the problem.&lt;/strong&gt; Each cluster becomes a snowflake with its own history of undocumented changes.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;GitOps inverts this model. Git is the source of truth. The cluster pulls its desired state from Git and continuously reconciles toward it. ArgoCD is the most mature GitOps operator for Kubernetes, implementing this pull-based model with a production-ready feature set.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;How ArgoCD Works: Core Architecture&lt;/h2&gt;



&lt;p&gt;ArgoCD runs as a set of controllers inside your Kubernetes cluster. The core components are:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Application Controller&lt;/strong&gt; &amp;mdash; Watches both the Git repository and the live cluster state. Computes the diff and drives reconciliation.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;API Server&lt;/strong&gt; &amp;mdash; Exposes the gRPC/REST API consumed by the CLI, UI, and external systems.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Repository Server&lt;/strong&gt; &amp;mdash; Generates Kubernetes manifests from source (Helm, Kustomize, plain YAML, Jsonnet).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt; &amp;mdash; Caches cluster state and repository data to reduce API server load.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Dex&lt;/strong&gt; (optional) &amp;mdash; Provides OIDC authentication for SSO integration.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;The fundamental unit in ArgoCD is an &lt;strong&gt;Application&lt;/strong&gt; &amp;mdash; a CRD that maps a source (a path in a Git repo at a specific revision) to a destination (a namespace in a cluster). ArgoCD continuously compares the desired state from Git with the live state in the cluster and reports on the sync status.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Sync Status vs Health Status&lt;/h3&gt;



&lt;p&gt;Two orthogonal concepts you need to understand from day one:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Sync Status&lt;/strong&gt; &amp;mdash; Does the live state match what Git says it should be? Values: Synced, OutOfSync, Unknown.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Health Status&lt;/strong&gt; &amp;mdash; Is the application actually working? Values: Healthy, Progressing, Degraded, Suspended, Missing, Unknown.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;An application can be &lt;em&gt;Synced&lt;/em&gt; but &lt;em&gt;Degraded&lt;/em&gt; &amp;mdash; the manifests were applied correctly, but a pod is crash-looping. Conversely, it can be &lt;em&gt;OutOfSync&lt;/em&gt; but &lt;em&gt;Healthy&lt;/em&gt; &amp;mdash; someone applied a change directly to the cluster outside of Git.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Installing ArgoCD&lt;/h2&gt;



&lt;p&gt;The official installation method uses a single manifest. For production, always pin to a specific version:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply -n argocd -f &lt;a href=&quot;https://raw.githubusercontent.com/argoproj/argo-cd/v2.11.0/manifests/install.yaml&quot; rel=&quot;nofollow&quot;&gt;https://raw.githubusercontent.com/argoproj/argo-cd/v2.11.0/manifests/install.yaml&lt;/a&gt;&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This deploys ArgoCD in the &lt;code&gt;argocd&lt;/code&gt; namespace with full cluster-admin access. For a production HA setup, use the &lt;code&gt;manifests/ha/install.yaml&lt;/code&gt; variant, which deploys multiple replicas of the API server and application controller.&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Accessing the UI and CLI&lt;/h3&gt;



&lt;p&gt;The initial admin password is auto-generated and stored in a secret:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;argocd admin initial-password -n argocd&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;For local access, port-forward the API server:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;kubectl port-forward svc/argocd-server -n argocd 8080:443&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Then log in via the CLI:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;argocd login localhost:8080 --username admin --password &amp;lt;password&amp;gt; --insecure&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;For production, expose the ArgoCD server via an Ingress or LoadBalancer with a proper TLS certificate. If you&amp;rsquo;re using NGINX Ingress Controller:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-server-ingress
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: &quot;true&quot;
    nginx.ingress.kubernetes.io/backend-protocol: &quot;HTTPS&quot;
spec:
  ingressClassName: nginx
  rules:
  - host: argocd.yourdomain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: argocd-server
            port:
              number: 443&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Defining Your First Application&lt;/h2&gt;



&lt;p&gt;Applications can be created via the UI, the CLI, or declaratively with a YAML manifest. The declarative approach is the recommended one &amp;mdash; it means your ArgoCD configuration itself is in Git:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: &lt;a href=&quot;https://github.com/your-org/your-app&quot; rel=&quot;nofollow&quot;&gt;https://github.com/your-org/your-app&lt;/a&gt;
    targetRevision: HEAD
    path: k8s/overlays/production
  destination:
    server: &lt;a href=&quot;https://kubernetes.default.svc&quot; rel=&quot;nofollow&quot;&gt;https://kubernetes.default.svc&lt;/a&gt;
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Key fields to understand:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;targetRevision&lt;/strong&gt; &amp;mdash; Can be a branch name, tag, or commit SHA. For production, pin to a tag rather than &lt;code&gt;HEAD&lt;/code&gt;.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;path&lt;/strong&gt; &amp;mdash; The directory within the repo containing your Kubernetes manifests.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;automated.prune&lt;/strong&gt; &amp;mdash; Automatically delete resources that are no longer in Git. Required for true GitOps but use carefully &amp;mdash; it will delete things.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;automated.selfHeal&lt;/strong&gt; &amp;mdash; Automatically revert manual changes made directly to the cluster. This is what enforces Git as the single source of truth.&lt;/li&gt;&lt;/ul&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Helm Integration&lt;/h2&gt;



&lt;p&gt;ArgoCD has native Helm support. It can deploy Helm charts directly from chart repositories or from your Git repository. You can override values per environment:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus-stack
  namespace: argocd
spec:
  project: default
  source:
    repoURL: &lt;a href=&quot;https://prometheus-community.github.io/helm-charts&quot; rel=&quot;nofollow&quot;&gt;https://prometheus-community.github.io/helm-charts&lt;/a&gt;
    chart: kube-prometheus-stack
    targetRevision: 58.4.0
    helm:
      releaseName: prometheus-stack
      valuesObject:
        grafana:
          adminPassword: &quot;${GRAFANA_PASSWORD}&quot;
        prometheus:
          prometheusSpec:
            retention: 30d
            storageSpec:
              volumeClaimTemplate:
                spec:
                  storageClassName: fast-ssd
                  resources:
                    requests:
                      storage: 50Gi
  destination:
    server: &lt;a href=&quot;https://kubernetes.default.svc&quot; rel=&quot;nofollow&quot;&gt;https://kubernetes.default.svc&lt;/a&gt;
    namespace: observability&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;One important nuance: ArgoCD renders Helm charts server-side using its own templating engine, not &lt;code&gt;helm install&lt;/code&gt;. This means Helm hooks (&lt;code&gt;pre-install&lt;/code&gt;, &lt;code&gt;post-upgrade&lt;/code&gt;, etc.) &lt;em&gt;are&lt;/em&gt; supported, but the release is not tracked in Helm&amp;rsquo;s release history. Running &lt;code&gt;helm list&lt;/code&gt; will not show ArgoCD-managed releases unless you configure ArgoCD to use the Helm secrets backend.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Projects: Multi-Tenancy and Access Control&lt;/h2&gt;



&lt;p&gt;ArgoCD Projects provide multi-tenancy within a single ArgoCD instance. They let you restrict which source repositories, destination clusters, and namespaces a team can deploy to. Every Application belongs to a Project.&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: platform-team
  namespace: argocd
spec:
  description: Platform team applications
  sourceRepos:
  - '&lt;a href=&quot;https://github.com/your-org/*&quot; rel=&quot;nofollow&quot;&gt;https://github.com/your-org/*&lt;/a&gt;'
  destinations:
  - namespace: 'platform-*'
    server: &lt;a href=&quot;https://kubernetes.default.svc&quot; rel=&quot;nofollow&quot;&gt;https://kubernetes.default.svc&lt;/a&gt;
  clusterResourceWhitelist:
  - group: ''
    kind: Namespace
  namespaceResourceBlacklist:
  - group: ''
    kind: ResourceQuota&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Projects are where you define the boundaries of what each team can do. The &lt;code&gt;default&lt;/code&gt; project has no restrictions &amp;mdash; never use it for production workloads. Create dedicated projects per team or per environment.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;RBAC Configuration&lt;/h2&gt;



&lt;p&gt;ArgoCD has its own RBAC system layered on top of Kubernetes RBAC. It is configured via the &lt;code&gt;argocd-rbac-cm&lt;/code&gt; ConfigMap. Roles are defined per project or globally:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
    # Platform team has full access to platform-team project
    p, role:platform-admin, applications, *, platform-team/*, allow
    p, role:platform-admin, projects, get, platform-team, allow
    p, role:platform-admin, repositories, *, *, allow

    # Dev team can sync but not delete
    p, role:developer, applications, get, */*, allow
    p, role:developer, applications, sync, */*, allow
    p, role:developer, applications, action/*, */*, allow

    # Bind SSO groups to roles
    g, your-org:platform-team, role:platform-admin
    g, your-org:developers, role:developer&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The &lt;code&gt;policy.default: role:readonly&lt;/code&gt; ensures that any authenticated user who has no explicit role assignment gets read-only access &amp;mdash; a safe default for production.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Multi-Cluster Management&lt;/h2&gt;



&lt;p&gt;ArgoCD can manage multiple Kubernetes clusters from a single control plane. Register external clusters with the CLI:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;# First, ensure the target cluster context is in your kubeconfig
argocd cluster add production-eu-west --name production-eu-west

# Verify registration
argocd cluster list&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;ArgoCD will create a ServiceAccount in the target cluster and store its credentials as a Kubernetes secret in the ArgoCD namespace. Applications can then target this cluster by name in their &lt;code&gt;destination.server&lt;/code&gt; field.&lt;/p&gt;



&lt;p&gt;For large-scale multi-cluster setups, consider the &lt;strong&gt;App of Apps&lt;/strong&gt; pattern or &lt;strong&gt;ApplicationSets&lt;/strong&gt;. ApplicationSets are a controller that generates Applications dynamically based on generators &amp;mdash; cluster lists, Git directory structures, or matrix combinations:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: cluster-addons
  namespace: argocd
spec:
  generators:
  - clusters:
      selector:
        matchLabels:
          environment: production
  template:
    metadata:
      name: '{{name}}-addons'
    spec:
      project: platform
      source:
        repoURL: &lt;a href=&quot;https://github.com/your-org/cluster-addons&quot; rel=&quot;nofollow&quot;&gt;https://github.com/your-org/cluster-addons&lt;/a&gt;
        targetRevision: HEAD
        path: 'addons/{{metadata.labels.region}}'
      destination:
        server: '{{server}}'
        namespace: kube-system&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This single ApplicationSet deploys the appropriate addons to every cluster labeled &lt;code&gt;environment: production&lt;/code&gt;, using each cluster&amp;rsquo;s region label to select the correct path in the repository.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Sync Strategies and Waves&lt;/h2&gt;



&lt;p&gt;When deploying complex applications with dependencies between resources, you need to control the order of deployment. ArgoCD provides two mechanisms:&lt;/p&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Sync Phases&lt;/h3&gt;



&lt;p&gt;Resources are deployed in three phases: PreSync, Sync, and PostSync. Use Sync Hooks for resources that must complete before the main sync proceeds (database migrations, certificate issuance, etc.):&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
  annotations:
    argocd.argoproj.io/hook: PreSync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: your-app:v1.2.3
        command: [&quot;./migrate.sh&quot;]
      restartPolicy: Never&lt;/code&gt;&lt;/pre&gt;



&lt;h3 class=&quot;wp-block-heading&quot;&gt;Sync Waves&lt;/h3&gt;



&lt;p&gt;Within the Sync phase, waves control ordering. Resources with a lower wave number are applied and must become healthy before resources with higher wave numbers are applied:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;# Applied first
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: &quot;1&quot;

# Applied after wave 1 is healthy
metadata:
  annotations:
    argocd.argoproj.io/sync-wave: &quot;2&quot;&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Notifications and Alerting&lt;/h2&gt;



&lt;p&gt;ArgoCD Notifications is a standalone controller that sends alerts when Application state changes. It supports Slack, PagerDuty, GitHub commit status, email, and a dozen other providers. Configure it via the &lt;code&gt;argocd-notifications-cm&lt;/code&gt; ConfigMap:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code&quot;&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token
  template.app-sync-failed: |
    slack:
      attachments: |
        [{
          &quot;title&quot;: &quot;{{.app.metadata.name}}&quot;,
          &quot;color&quot;: &quot;#E96D76&quot;,
          &quot;fields&quot;: [{
            &quot;title&quot;: &quot;Sync Status&quot;,
            &quot;value&quot;: &quot;{{.app.status.sync.status}}&quot;,
            &quot;short&quot;: true
          },{
            &quot;title&quot;: &quot;Message&quot;,
            &quot;value&quot;: &quot;{{range .app.status.conditions}}{{.message}}{{end}}&quot;,
            &quot;short&quot;: false
          }]
        }]
  trigger.on-sync-failed: |
    - when: app.status.sync.status == 'Unknown'
      send: [app-sync-failed]
    - when: app.status.operationState.phase in ['Error', 'Failed']
      send: [app-sync-failed]&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Secret Management with ArgoCD&lt;/h2&gt;



&lt;p&gt;ArgoCD intentionally has no secret management built in &amp;mdash; storing secrets in Git as plain text is never acceptable. The common patterns are:&lt;/p&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Sealed Secrets&lt;/strong&gt; (Bitnami) &amp;mdash; Encrypts secrets with a cluster-specific key. The encrypted secret can be committed to Git; only the cluster can decrypt it.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;External Secrets Operator&lt;/strong&gt; &amp;mdash; Syncs secrets from Vault, AWS Secrets Manager, GCP Secret Manager, etc. into Kubernetes secrets. The ArgoCD Application manages the ExternalSecret CRD, not the actual secret value.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;argocd-vault-plugin&lt;/strong&gt; &amp;mdash; A plugin that replaces placeholder values in manifests with secrets retrieved from Vault at sync time.&lt;/li&gt;&lt;/ul&gt;



&lt;p&gt;The External Secrets Operator approach is the most flexible for teams already using a centralized secrets backend. The Application in ArgoCD deploys &lt;code&gt;ExternalSecret&lt;/code&gt; objects, which the ESO controller resolves at runtime without ever touching Git.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;Production Best Practices&lt;/h2&gt;



&lt;ul class=&quot;wp-block-list&quot;&gt;&lt;li&gt;&lt;strong&gt;Run ArgoCD in HA mode.&lt;/strong&gt; Use &lt;code&gt;manifests/ha/install.yaml&lt;/code&gt; with 3 replicas of the API server and multiple application controller shards for large clusters (100+ applications).&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Pin image versions.&lt;/strong&gt; Never use &lt;code&gt;latest&lt;/code&gt; for the ArgoCD image itself. Pin to a specific version and upgrade deliberately.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Use the App of Apps pattern for bootstrapping.&lt;/strong&gt; A single root Application deploys all other Applications. This makes cluster bootstrapping idempotent and reproducible.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Separate ArgoCD config from application config.&lt;/strong&gt; Store ArgoCD Application manifests in a dedicated &lt;code&gt;gitops&lt;/code&gt; repository, separate from application source code.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Enable resource tracking via annotations.&lt;/strong&gt; Use &lt;code&gt;application.resourceTrackingMethod: annotation&lt;/code&gt; in &lt;code&gt;argocd-cm&lt;/code&gt; instead of the default label-based tracking, which can conflict with Helm&amp;rsquo;s own labels.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Set resource limits on ArgoCD controllers.&lt;/strong&gt; Application controller CPU and memory scale with the number of resources tracked. Monitor and tune accordingly.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Restrict auto-sync in production.&lt;/strong&gt; Consider requiring manual sync approval for production environments even when using GitOps &amp;mdash; or at minimum require a PR approval gate before changes reach the target branch.&lt;/li&gt;&lt;/ul&gt;



&lt;h2 class=&quot;wp-block-heading&quot;&gt;ArgoCD vs Flux&lt;/h2&gt;



&lt;p&gt;Flux v2 is the other major GitOps operator. Both are CNCF projects. The main differences in practice:&lt;/p&gt;



&lt;figure class=&quot;wp-block-table&quot;&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Feature&lt;/th&gt;&lt;th&gt;ArgoCD&lt;/th&gt;&lt;th&gt;Flux v2&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;UI&lt;/td&gt;&lt;td&gt;Built-in web UI&lt;/td&gt;&lt;td&gt;No official UI (use Weave GitOps)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multi-cluster&lt;/td&gt;&lt;td&gt;Single control plane manages many clusters&lt;/td&gt;&lt;td&gt;Agent per cluster, pull model&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ApplicationSets&lt;/td&gt;&lt;td&gt;Native&lt;/td&gt;&lt;td&gt;Kustomization + HelmRelease&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Secret management&lt;/td&gt;&lt;td&gt;Plugin-based&lt;/td&gt;&lt;td&gt;SOPS native integration&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Learning curve&lt;/td&gt;&lt;td&gt;Steeper (more concepts)&lt;/td&gt;&lt;td&gt;Lower (Kubernetes-native CRDs)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CNCF status&lt;/td&gt;&lt;td&gt;Graduated&lt;/td&gt;&lt;td&gt;Graduated&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;



&lt;p&gt;ArgoCD wins when you need the UI, multi-cluster management from a central plane, or have a large operations team that benefits from the visual application topology view. Flux wins when you want a simpler, purely Kubernetes-native approa&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/15641/argocd-guide-gitops-continuous-delivery-for-kubernetes</guid>
<pubDate>Thu, 23 Apr 2026 12:47:43 +0000</pubDate>
</item>
<item>
<title>Stop Manually Editing GitOps Files: ArgoCD Image Updater on Kubernetes</title>
<link>https://coderlegion.com/14224/stop-manually-editing-gitops-files-argocd-image-updater-on-kubernetes</link>
<description>&lt;h2 id=&quot;stop-manually-editing-gitops-files-argocd-image-updater-on-kubernetes&quot;&gt;Stop Manually Editing GitOps Files: ArgoCD Image Updater on Kubernetes&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Every time a developer pushes new code, someone has to manually update the image tag in the GitOps repo. ArgoCD then deploys it. Sound familiar? Let’s fix that.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’re running ArgoCD with a GitOps setup, you’ve probably experienced this pain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developer pushes code&lt;/li&gt;
&lt;li&gt;CI (Jenkins/Gitlab) builds and pushes a new image to the registry&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Someone&lt;/strong&gt; (u/me/dev) &lt;strong&gt;manually edits the image tag in the GitOps repo&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;ArgoCD detects the change and deploys it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-is-argocd-image-updater-&quot;&gt;What is ArgoCD Image Updater?&lt;/h3&gt;
&lt;p&gt;ArgoCD Image Updater automatically watches your container registry for new image tags and updates your ArgoCD applications without any manual intervention. When a new image is pushed, it detects the change and triggers a deployment without manual interventions.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites-1&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;Before we begin, please ensure you have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An EKS cluster running&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt; configured&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm&lt;/code&gt; installed&lt;/li&gt;
&lt;li&gt;A container registry (DockerHub, ECR, GitLab Registry etc.)&lt;/li&gt;
&lt;li&gt;A GitHub/GitLab repo for your GitOps values&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Step 1 — Install ArgoCD on EKS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First, create the ArgoCD namespace and install it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see a CRD annotation size error, apply the server-side flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl apply --server-side -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Access the ArgoCD UI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl port-forward svc/argocd-server -n argocd 8080:443
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Retrieve the initial admin password by opening another terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath=&quot;{.data.password}&quot; | base64 -d &amp;amp;&amp;amp; echo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this password to log in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2 — Understanding the Multisource Pattern&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In real-world GitOps setups, ArgoCD applications often use &lt;strong&gt;multiple sources&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Source 1&lt;/strong&gt; — The Helm chart from a Helm registry (e.g., Bitnami, your internal registry)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Source 2&lt;/strong&gt; — The values files from your GitOps repository&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a clean separation — the chart is versioned independently from your environment configuration.&lt;/p&gt;
&lt;p&gt;Here’s what a multisource ArgoCD application looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx-sample
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  sources:
    - repoURL: https://charts.bitnami.com/bitnami
      chart: nginx
      targetRevision: 21.0.5
      helm:
        releaseName: nginx-sample
        valueFiles:
          - $values/values/values.yaml
    - repoURL: https://github.com/AlexyPulivelil/gitops-sample.git
      targetRevision: main
      ref: values
  destination:
    server: https://kubernetes.default.svc
    namespace: nginx-sample
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;values.yaml&lt;/code&gt; file typically looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;replicaCount: 1
service:
  type: ClusterIP
  port: 80
image:
  registry: docker.io
  repository: repository/nginix
  tag: &quot;10.0&quot;
  pullPolicy: Always
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are addressing the issue of manually updating these tags on every deployment.&lt;/p&gt;
&lt;h3 id=&quot;step-3-why-argocd-image-updater-old-version-won-t-work-here&quot;&gt;Step 3 — Why ArgoCD Image Updater Old Version Won’t Work Here&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(skip this if having single source.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I tried 0.12.2 initially&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/v0.12.2/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And add annotations to your ArgoCD Application:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;annotations:
  argocd-image-updater.argoproj.io/image-list: myapp=myregistry/myapp
  argocd-image-updater.argoproj.io/myapp.update-strategy: latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you check logs you could see&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;level=warning msg=&quot;skipping app 'nginx-sample' of type '' 
because it's not of supported source type&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;v0.12.2 does not support multisource ArgoCD applications.&lt;/strong&gt; It uses annotations on the Application resource and relies on detecting the source type, which fails for multisource apps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NB: If working on single source, this would be sufficient&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This limitation is why we need &lt;strong&gt;v1.1.1&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4 — Install ArgoCD Image Updater v1.1.1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;v1.1.1 introduces a CRD based approach — instead of annotations on the ArgoCD Application, you create a dedicated ImageUpdater custom resource. This completely solves the multisource problem.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/v1.1.1/config/install.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify the controller is running:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl get pods -n argocd | grep image-updater-controller
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grant the controller permissions to access ArgoCD applications:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl create clusterrolebinding image-updater-binding \
  --clusterrole=cluster-admin \
  --serviceaccount=argocd:argocd-image-updater-controller \
  -n argocd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;For production, scope down the permissions using a dedicated ClusterRole instead of cluster-admin.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 5 — Create the ImageUpdater CR&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the key difference in v1.1.1. Instead of annotating your ArgoCD Application, you create a separate &lt;code&gt;ImageUpdater&lt;/code&gt; resource:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: argocd-image-updater.argoproj.io/v1alpha1
kind: ImageUpdater
metadata:
  name: nginx-sample-updater
  namespace: argocd
spec:
  applicationRefs:
    - namePattern: &quot;nginx-sample&quot;
      images:
        - alias: nginx
          imageName: your-registry/nginx-sample-image
          commonUpdateSettings:
            updateStrategy: newest-build
            forceUpdate: true
          manifestTargets:
            helm:
              tag: image.tag
              name: image.repository
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What each field means:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;namePattern&lt;/code&gt; Which ArgoCD application to watch&lt;/p&gt;
&lt;p&gt;&lt;code&gt;imageName&lt;/code&gt; Which image to monitor in the registry&lt;/p&gt;
&lt;p&gt;&lt;code&gt;updateStrategy&lt;/code&gt; How to pick the new tag (&lt;code&gt;newest-build&lt;/code&gt;, &lt;code&gt;semver&lt;/code&gt;, &lt;code&gt;alphabetical&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;forceUpdate&lt;/code&gt; Update even if image isn't directly referenced in app status&lt;/p&gt;
&lt;p&gt;&lt;code&gt;manifestTargets.helm.tag&lt;/code&gt; Which Helm value holds the image tag&lt;/p&gt;
&lt;p&gt;&lt;code&gt;manifestTargets.helm.name&lt;/code&gt; Which Helm value holds the image repository&lt;/p&gt;
&lt;p&gt;Apply it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl apply -f imageupdater-cr.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NB: The apply order should be&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# First the ImageUpdater CR
kubectl apply -f argocd/image-updater-cr.yaml
# Then the ArgoCD Application
kubectl apply -f argocd/application.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now push a new image tag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker tag your-dockerhub/nginx:1.0 your-dockerhub/nginx:2.0
docker push your-dockerhub/nginx:2.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Watch the magic happen in the logs♂️&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl logs -f deployment/argocd-image-updater-controller -n argocd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You’ll see&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;msg=&quot;Setting new image to your-dockerhub/nginx:2.0&quot;
msg=&quot;Successfully updated image to your-dockerhub/nginx:2.0&quot;
msg=&quot;Successfully updated application spec for nginx-sample&quot;
msg=&quot;images_updated=1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:996/format:webp/1*ZAVFRAQubFN0wh-axnDXVA.gif&quot; alt=&quot;captionless image&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero manual editing. Fully automated.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;key-features&quot;&gt;Key Features&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Multiple Update Strategies:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;newest-build&lt;/code&gt; Always pick the most recently pushed tag&lt;/p&gt;
&lt;p&gt;&lt;code&gt;semver&lt;/code&gt; Follow semantic versioning constraints e.g. &lt;code&gt;~1.29&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;alphabetical&lt;/code&gt; Pick the last tag alphabetically&lt;/p&gt;
&lt;p&gt;&lt;code&gt;digest&lt;/code&gt; Track by image digest&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One CR Per Application:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Each ArgoCD Application gets its own &lt;code&gt;ImageUpdater&lt;/code&gt; CR. This gives you fine-grained control — each service can have a different update strategy, different registry credentials, and different tag filters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write-back Methods:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;argocd&lt;/code&gt; (default) Updates ArgoCD application parameters directly via API&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git&lt;/code&gt; Commits the tag change back to your GitOps repo&lt;/p&gt;
&lt;p&gt;For true GitOps, use the &lt;code&gt;git&lt;/code&gt; write-back method so every deployment change is tracked in Git.&lt;/p&gt;
&lt;h3 id=&quot;limitations&quot;&gt;Limitations&lt;/h3&gt;
&lt;p&gt;Being transparent about limitations is important before adopting any tool in production:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. One CR per Application:&lt;/strong&gt; Every ArgoCD Application needs its own &lt;code&gt;ImageUpdater&lt;/code&gt; CR. In large setups with many services these requirements can become a lot of CRs to manage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. argocd write-back overrides values.yaml:&lt;/strong&gt; When using the default &lt;code&gt;argocd&lt;/code&gt; write-back method, Image Updater sets a &lt;strong&gt;parameter override&lt;/strong&gt; directly on the ArgoCD Application. This takes priority over your values.yaml file. If you manually edit values.yaml, it won't take effect. Use &lt;code&gt;git&lt;/code&gt; write-back to avoid this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Registry rate limiting:&lt;/strong&gt; Image Updater polls the registry every 2 minutes by default. On DockerHub's free tier this can hit rate limits, especially in teams with many services. Configure credentials or use webhooks for instant updates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. newest-build picks by push timestamp&lt;/strong&gt; If you push tags out of order (e.g., push &lt;code&gt;9.0&lt;/code&gt; after &lt;code&gt;10.0&lt;/code&gt;), Image Updater will pick &lt;code&gt;9.0&lt;/code&gt; because they were pushed more recently. Use &lt;code&gt;semver&lt;/code&gt; strategy to avoid this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. No conflict resolution between multiple CRs&lt;/strong&gt; If two &lt;code&gt;ImageUpdater&lt;/code&gt; CRs accidentally target the same ArgoCD Application, they will continuously overwrite each other, causing the application to flip between versions. ensure that only one CR targets each application.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14224/stop-manually-editing-gitops-files-argocd-image-updater-on-kubernetes</guid>
<pubDate>Wed, 08 Apr 2026 10:55:08 +0000</pubDate>
</item>
</channel>
</rss>