Section 1 ยท Overview

Why ArgoCD makes your GitHub Actions setup dramatically more secure

You're already using GitHub Actions. It works. So why bother adding another tool? This guide answers that question concretely โ€” with your security concern (Jenkins-style blast radius) at the center โ€” and walks you step-by-step through a realistic workflow. No marketing fluff.

0
Server credentials in CI
After ArgoCD, GitHub Actions never holds kubeconfigs or SSH keys.
3 min
Avg rollback time
Rollback = git revert. ArgoCD notices and rolls back.
100%
Cluster state auditable
Everything that runs in your cluster is in a Git commit. Full history.
๐Ÿ’ก

The core insight. You don't replace GitHub Actions with ArgoCD. You split the job: GitHub Actions builds container images and pushes them (which is what it's best at). ArgoCD watches Git for changes to your Kubernetes manifests and applies them from inside the cluster (which is what it's best at). The CI never needs to talk to your cluster directly again.

Section 2 ยท Your current pain

What's actually wrong with "just use GitHub Actions"?

GitHub Actions is great for building, testing, and publishing. But when people use it as the deployment tool, they recreate the same architectural problem that made Jenkins dangerous โ€” just with a nicer UI.

Today's typical GitHub Actions deploy flow

๐Ÿ‘จโ€๐Ÿ’ป
Developer
git push
โ†’
โš™๏ธ
GitHub Actions
builds + deploys
โš 
๐Ÿ–ฅ๏ธ
Kubernetes
cluster

GitHub Actions stores a KUBE_CONFIG secret and runs kubectl apply. This is a push pattern.

The hidden cost of this pattern

# .github/workflows/deploy.yml โ€” a typical setup
name: Deploy to production
on: { push: { branches: [main] } }

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up kubectl
        run: echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > ~/.kube/config  # ๐Ÿšฉ long-lived cluster admin creds
      - name: Build and push image
        run: docker build -t myapp:${{ github.sha }} . && docker push ...
      - name: Deploy
        run: kubectl set image deploy/myapp myapp=myapp:${{ github.sha }}
โœ— Security problems
  • GitHub holds a KUBE_CONFIG secret โ€” often cluster-admin
  • Any compromised action (tj-actions/changed-files style supply-chain attack) can steal it
  • Any maintainer with repo access can exfiltrate it via a PR workflow
  • Credential rotation means updating every workflow file
  • Audit = reading GitHub Actions logs one by one
โœ— Operational problems
  • What's actually running in the cluster? Hard to tell without kubectl
  • Someone ran kubectl edit deploy at 3am. Now what?
  • Rollback = re-run an old workflow (hope the image still exists)
  • Multi-cluster = same secret copy-pasted N times
  • No single source of truth for cluster state
๐Ÿ”ฅ

The blast radius. If a malicious dependency gets into one workflow, the attacker now has a kubeconfig with full cluster access. They can deploy a cryptominer, exfiltrate secrets from every namespace, or drop a persistent backdoor โ€” all without ever touching your servers directly. This is the exact problem you had with Jenkins, just wearing a GitHub hat.

Section 3 ยท The mental model

What "GitOps" actually means (without the buzzword soup)

GitOps is not a product. It's a pattern with four rules. If you follow all four, you are doing GitOps. ArgoCD is simply a tool that makes following those four rules easy on Kubernetes.

Rule 1 ยท Declarative

Your entire system is described as data files (YAML), not as scripts. You say "I want 3 replicas of nginx", not "run kubectl scale deploy nginx --replicas=3".

Rule 2 ยท Versioned in Git

Those declarative files live in a Git repo. Git is the single source of truth. If it's not in Git, it doesn't exist. Every change is a commit, reviewable, revertible.

Rule 3 ยท Pulled automatically

An agent running inside the target system pulls from Git and applies changes. Nothing outside the cluster needs cluster credentials. This is the security superpower.

Rule 4 ยท Continuously reconciled

The agent constantly compares "what Git says" vs "what's actually running". Any drift is detected and auto-corrected (or alerted). The cluster cannot silently diverge from Git.

Push vs Pull โ€” visualized

๐Ÿ“ฆ
Git repo
โ†’
โš™๏ธ
CI / Jenkins
HAS credentials
โš  push creds
๐Ÿ–ฅ๏ธ
Cluster

CI reaches into the cluster using a stored credential. If CI is compromised, everything downstream is too.

๐Ÿ“ฆ
Git repo
source of truth
โ† pull
๐Ÿ™
ArgoCD
INSIDE cluster
applies
๐Ÿ–ฅ๏ธ
Cluster

Cluster reaches out to Git (read-only). No inbound connections. No external service holds cluster credentials.

๐Ÿ”’

Why this is genuinely more secure. A firewall rule on your cluster can now say: "no external service may initiate a connection to the Kubernetes API". You don't even need to trust GitHub Actions, any third-party action, or any CI vendor โ€” because none of them are in the path to your cluster anymore. The cluster pulls from Git, which it verifies via commit signatures and HTTPS.

Section 4 ยท Mechanics

How ArgoCD actually works under the hood

ArgoCD is a Kubernetes controller. That's it. Once you understand what a controller is, ArgoCD becomes obvious.

The reconciliation loop

Every ArgoCD action is a variation of this same loop, which runs forever:

1๏ธโƒฃ
Fetch Git
every 3 min
โ†’
2๏ธโƒฃ
Render manifests
Helm/Kustomize
โ†’
3๏ธโƒฃ
Compare
desired vs live
โ†’
4๏ธโƒฃ
Sync
apply diff
โ†ป

In plain English

Every ~3 minutes (configurable), ArgoCD:

  1. Fetches the latest commit from your Git repo.
  2. Renders the manifests (plain YAML, or Helm charts, or Kustomize overlays).
  3. Compares the rendered output to what's actually running in the cluster. Any difference = "drift".
  4. Syncs by applying the diff โ€” creating, updating, or deleting K8s resources as needed.

The same loop also runs instantly when you push a commit (via webhook) or click "Sync" in the UI.

The two things ArgoCD gives you that kubectl alone can't

โ‘  Drift detection

Someone runs kubectl edit deploy/payments --replicas=10 directly in production. ArgoCD immediately sees this doesn't match Git and marks the app OutOfSync. It can either alert you or auto-revert.

โ‘ก Continuous guarantees

Someone deletes a ConfigMap by accident. ArgoCD notices at next sync and recreates it from Git โ€” automatically. Your cluster self-heals back to the Git-declared state.

The Application CRD โ€” ArgoCD's main concept

An Application is a Kubernetes resource ArgoCD gives you. It's a declarative pointer that says "sync this Git path into this namespace".

Application.yamlapiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: snip-link
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/vattanac/infra.git    # where to find manifests
    path: apps/snip-link/production                # which folder
    targetRevision: main                          # which branch
  destination:
    server: https://kubernetes.default.svc         # which cluster
    namespace: snip-link-prod                         # which namespace
  syncPolicy:
    automated:
      prune: true      # delete resources removed from Git
      selfHeal: true   # revert manual changes to the cluster
๐ŸŽฏ

Mental model. An Application is a subscription. It says "my cluster subscribes to this Git folder. Whatever appears there, put it in me." That's 90% of ArgoCD.

Section 5 ยท What's actually inside ArgoCD

Architecture โ€” the five components

ArgoCD runs as a handful of pods inside your cluster. Understanding which pod does what makes debugging vastly easier.

External
๐Ÿ“ฆ Git repository
GitHub / GitLab / Bitbucket. Holds YAML manifests, Helm charts, Kustomize.
๐Ÿณ Container registry
GHCR / ECR / Docker Hub. Holds built images. Never holds cluster creds.
๐ŸŒ Webhook (optional)
Git notifies ArgoCD of pushes so it syncs instantly instead of polling.
Inside your cluster (argocd namespace)
๐Ÿง  argocd-application-controller
The brain. Runs the reconciliation loop. Compares Git state to cluster state and applies changes.
๐Ÿ“‚ argocd-repo-server
The Git worker. Clones repos, renders Helm/Kustomize, caches results. Stateless.
๐Ÿ”Œ argocd-server
The API + web UI. SSO, RBAC, gRPC API. This is what you click on.
๐Ÿ’พ Redis
Shared cache between the components. Makes repeated renders fast.
๐Ÿ” dex (optional)
SSO connector. Connects to GitHub/Google/Okta/SAML for user auth.
Application targets
๐Ÿ–ฅ๏ธ Same cluster
ArgoCD deploys into the cluster it runs in. Most common setup.
๐ŸŒ Other clusters
One ArgoCD can manage many clusters. Each connected via kubeconfig stored in ArgoCD.

Where the security actually lives

๐Ÿ”‘

Direction of trust. The cluster trusts Git (read-only). Git does not trust the cluster. Nothing outside the cluster holds a cluster credential. This is the architectural property that makes ArgoCD fundamentally different from Jenkins or "GitHub Actions with kubeconfig".

The only credential ArgoCD itself needs is a read-only Git deploy key (or GitHub App with read access). Even if someone steals that key, they can only read your infra Git repo โ€” they cannot write to your cluster.

Section 6 ยท The combined workflow

GitHub Actions + ArgoCD โ€” who does what?

This is the part people get confused about. You don't throw away GitHub Actions. You give it a clearer, smaller job.

Division of labor

Responsibility GitHub Actions ArgoCD
Run tests on PRโœ“ Yesโœ— No
Build container imageโœ“ Yesโœ— No
Push image to registryโœ“ Yesโœ— No
Scan image for vulnsโœ“ Yesโœ— No
Update manifest with new image tagโœ“ YesOptional via Image Updater
Apply to Kubernetesโœ— Noโœ“ Yes
Detect/fix manual driftโœ— Noโœ“ Yes
Rollback on failureโœ— Noโœ“ Yes
Show cluster healthโœ— Noโœ“ Yes

The recommended Git structure

You need two repositories (or two folders in a monorepo). This separation is the key to making GitOps clean.

Repo 1 ยท Application code

๐Ÿ“ snip-link/ โ”œโ”€โ”€ src/ โ”œโ”€โ”€ Dockerfile โ”œโ”€โ”€ package.json โ””โ”€โ”€ .github/workflows/ โ””โ”€โ”€ build.yml # only builds image

Repo 2 ยท Infrastructure (watched by ArgoCD)

๐Ÿ“ infra/ โ”œโ”€โ”€ apps/ โ”‚ โ””โ”€โ”€ snip-link/ โ”‚ โ”œโ”€โ”€ base/ โ”‚ โ”‚ โ”œโ”€โ”€ deployment.yaml โ”‚ โ”‚ โ””โ”€โ”€ service.yaml โ”‚ โ””โ”€โ”€ production/ โ”‚ โ””โ”€โ”€ kustomization.yaml # ๐Ÿ‘ˆ image tag lives here โ””โ”€โ”€ argocd/ โ””โ”€โ”€ applications.yaml

The full flow, end to end

๐Ÿ‘จโ€๐Ÿ’ป
Developer
push to app repo
โ†’
โš™๏ธ
GH Actions
builds image
โ†’
๐Ÿ“ฆ
Registry
image pushed
โš™๏ธ
GH Actions
bump tag in infra repo
โ†’
๐Ÿ“
Infra Git repo
new commit
โ† pulls
๐Ÿ™
ArgoCD
detects change
โ†’
๐Ÿ–ฅ๏ธ
Cluster
new pods rolling

The new GitHub Actions workflow โ€” no more kubeconfig

.github/workflows/build.ymlname: Build and update manifest
on: { push: { branches: [main] } }

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write                 # push to GHCR only โ€” no cluster access!
    steps:
      - uses: actions/checkout@v4

      - name: Log in to registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push
        run: |
          docker build -t ghcr.io/vattanac/snip-link:${{ github.sha }} .
          docker push ghcr.io/vattanac/snip-link:${{ github.sha }}

      - name: Bump image tag in infra repo
        run: |
          git clone https://x-access-token:${{ secrets.INFRA_REPO_TOKEN }}@github.com/vattanac/infra.git
          cd infra/apps/snip-link/production
          sed -i "s/newTag:.*/newTag: ${{ github.sha }}/" kustomization.yaml
          git config user.email "ci@bot" && git config user.name "ci"
          git commit -am "deploy snip-link ${{ github.sha }}"
          git push
๐ŸŽ‰

Notice what's missing. No KUBE_CONFIG. No kubectl. No cluster URL. The only secret GitHub Actions holds is a scoped PAT that can commit to the infra repo โ€” which even if stolen only lets an attacker open a bogus PR (auditable, reversible). ArgoCD picks up the infra commit and handles the actual cluster work.

Section 7 ยท Hands-on installation

Install ArgoCD in 5 minutes

You need a Kubernetes cluster (k3s, kind, EKS, DigitalOcean Kubernetes โ€” anything works) and kubectl. That's it.

Step 1 โ€” Install ArgoCD

# Create the namespace and apply the official install manifest
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait until pods are ready
kubectl -n argocd wait --for=condition=ready pod -l app.kubernetes.io/name=argocd-server --timeout=300s

Step 2 โ€” Access the UI

# Port-forward the server pod to your laptop
kubectl port-forward -n argocd svc/argocd-server 8080:443

# Get the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

# Open https://localhost:8080 โ†’ login as admin

Step 3 โ€” Install the CLI (optional but nice)

brew install argocd
# or
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd && sudo mv argocd /usr/local/bin/

argocd login localhost:8080 --username admin --password <password>

Step 4 โ€” Point ArgoCD at your infra repo

# Register the repo (once per cluster)
argocd repo add https://github.com/vattanac/infra.git \
  --username vattanac --password <github-pat-with-read>

# Create your first Application
argocd app create snip-link \
  --repo https://github.com/vattanac/infra.git \
  --path apps/snip-link/production \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace snip-link-prod \
  --sync-policy automated \
  --self-heal \
  --auto-prune
โšก

That's it. ArgoCD will now pull from your Git repo, deploy everything in apps/snip-link/production, and keep the namespace in sync with Git forever. Every git push to infra = automatic deploy.

Section 8 ยท Step-by-step walkthrough

Live demo โ€” deploying "Snip.link" end-to-end

Click through the 8 steps below. This is the exact workflow you'll use every day. No placeholder content โ€” every step shows real files, real commands, real UI.

1
Write code
2
Push to app repo
3
GH Actions builds
4
Bump infra repo
5
ArgoCD detects
6
Sync to cluster
7
Drift detection
8
Rollback

Step 1 ยท You write code and commit it locally

Normal developer life. You edit src/server.js in your app repo, add a feature, and commit.

# In your app repo (snip-link)
vim src/server.js                      # add /api/v2/shorten endpoint
git add .
git commit -m "add v2 shorten endpoint"
git push origin main
๐Ÿ’ญ

Nothing changed in your developer experience. You didn't install a new tool. You didn't learn new commands. You still git push.

Step 2 ยท GitHub receives the push and fires the workflow

Your GitHub Actions workflow is triggered by the push to main. It has exactly two jobs: build and bump.

๐Ÿ”’ github.com / vattanac / snip-link / actions
โœ“ Build and update manifest
triggered by push to main ยท commit a3f29b1
โ–ธ checkout
โ–ธ docker build -t ghcr.io/vattanac/snip-link:a3f29b1 . โœ“ (42s)
โ–ธ docker push ghcr.io/vattanac/snip-link:a3f29b1 โœ“ (8s)
โ–ธ bump image tag in infra repo โœ“ (3s)

Step 3 ยท The built image lands in the registry

Now ghcr.io/vattanac/snip-link:a3f29b1 exists. That's it โ€” just an image sitting in a registry. Nothing is running yet. Nothing talks to the cluster.

# Verify the image is there
docker pull ghcr.io/vattanac/snip-link:a3f29b1
โœ“ a3f29b1: Pulling from vattanac/snip-link
โœ“ Digest: sha256:9f7ac2ed61...
๐Ÿ”

Security note. Up to this point, nothing has connected to your Kubernetes cluster. GitHub Actions held only registry push permission + an infra repo write token. No kubeconfig exists anywhere in CI.

Step 4 ยท The bump step commits to the infra repo

This is the "handoff". GH Actions edits one line in the infra repo's kustomization.yaml โ€” changing the image tag from the old SHA to a3f29b1 โ€” and pushes.

infra/apps/snip-link/production/kustomization.yamlapiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../base

images:
  - name: ghcr.io/vattanac/snip-link
    newTag: a3f29b1          # ๐Ÿ‘ˆ this line got updated

namespace: snip-link-prod
replicas:
  - name: snip-link
    count: 3

A git commit on the infra repo now exists saying "deploy snip-link a3f29b1". This commit is your audit trail โ€” forever.

Step 5 ยท ArgoCD detects the change

If you configured a webhook (recommended), this happens in ~2 seconds. Otherwise ArgoCD polls every ~3 minutes and catches it on the next cycle.

๐Ÿ”’ argocd.yourco.dev / applications / snip-link
๐Ÿ™ snip-link
github.com/vattanac/infra ยท apps/snip-link/production
โ— OutOfSync โ— Healthy
[diff] Deployment/snip-link
- image: ghcr.io/vattanac/snip-link:f81b2a4
+ image: ghcr.io/vattanac/snip-link:a3f29b1
ArgoCD sees the diff. Because syncPolicy.automated is true, it syncs immediately.

Step 6 ยท ArgoCD applies the change inside the cluster

The argocd-application-controller pod calls the Kubernetes API (from inside the cluster, using its own ServiceAccount โ€” no external credentials). It does a rolling update.

kubectl -n snip-link-prod get pods -w

NAME                          READY   STATUS              RESTARTS   AGE
snip-link-5d8c6f7b9c-a1b2c    1/1     Running             0          12m
snip-link-5d8c6f7b9c-d3e4f    1/1     Running             0          12m
snip-link-5d8c6f7b9c-g5h6i    1/1     Running             0          12m
snip-link-7f9a3c2d1e-x7y8z    0/1     ContainerCreating   0          2s   โ† new
snip-link-7f9a3c2d1e-x7y8z    1/1     Running             0          8s
snip-link-5d8c6f7b9c-a1b2c    1/1     Terminating         0          12m  โ† old
...
โœจ

New version is live. ArgoCD marks the app Synced + Healthy. Total elapsed time from git push on the app repo to pods running: usually 1โ€“3 minutes. If anything fails (image pull error, readiness probe fails), ArgoCD marks it Degraded and alerts.

Step 7 ยท Someone makes a "quick fix" directly in the cluster

Imagine a teammate runs this at 3 AM during an incident:

kubectl -n snip-link-prod scale deploy snip-link --replicas=10  # ๐Ÿšฉ not in Git!

With a plain GitHub Actions setup, this change just... stays. Until someone notices. Until the next deploy overwrites it. Until the cluster blows up.

With ArgoCD + selfHeal: true, the controller sees the drift on its next sync:

๐Ÿ”’ argocd.yourco.dev / applications / snip-link
โš  Out of sync detected. Deployment.spec.replicas drifted: live=10, desired=3
โœ“ Self-heal triggered. Scaled deployment back to 3 replicas.
๐ŸŽฏ

The cluster cannot silently drift. This alone is worth the whole setup. You can even set selfHeal: false if you want ArgoCD to just alert instead of auto-revert โ€” great for emergency scenarios.

Step 8 ยท A bad deploy โ€” rollback via git revert

Commit a3f29b1 turned out to be broken. No problem. The fix is a git revert:

# In the INFRA repo (not the app repo)
cd infra
git revert <bump-commit-sha>
git push
# Done. ArgoCD sees the new commit, rolls forward (back to the old image)

Alternative: in the ArgoCD UI, click History and rollback on the app, pick the last-good revision, click Rollback. Same effect.

โช

Why this is better than re-running a workflow. The old image is pinned in a git commit โ€” it cannot disappear. Your rollback path is deterministic, reversible, and auditable. Try rolling back a Jenkins or GH Actions deploy from six months ago: you'll need to hope the image, the workflow file, and the kubeconfig still exist.

Section 9 ยท Concrete wins

The five benefits you'll actually feel

Forget marketing pages. These are the five things that make real engineers go "oh, that's nice" in the first month of using ArgoCD properly.

โ‘  No cluster credentials in any CI system โ€” ever

Your GitHub Actions can be compromised by a supply-chain attack tomorrow and your production cluster is not affected. The worst an attacker can do is push a bad image tag to the infra repo โ€” which you'd see immediately in a diff and revert. This is a categorical security improvement, not a marginal one.

โ‘ก What's running in prod = git log

A month from now, someone asks "when did we enable the V2 API?" โ€” you answer in 5 seconds by grepping the infra repo. Six months from now you're audited. You hand over a git history. Done.

โ‘ข Self-healing cluster

Stop worrying about sleep-deprived 3 AM kubectl changes. ArgoCD treats unauthorized changes like any other drift and reverts them. Your cluster stays aligned with Git 24/7, with zero human effort.

โ‘ฃ Multi-cluster for free

Got a staging cluster? A DR cluster? A region cluster for EU compliance? You just add them to ArgoCD's cluster list. Each becomes a destination in your Application specs. Same infra repo, different destinations. No duplicated CI pipelines.

โ‘ค The UI is genuinely useful

Unlike Jenkins or plain kubectl, ArgoCD's UI shows you a live topological diagram of every resource in your app โ€” Deployments, Pods, Services, Ingresses โ€” with real-time health status. When something breaks, you see where in 3 seconds, not after 20 minutes of kubectl describe.

Section 10 ยท Avoid these

Common mistakes that kill ArgoCD adoption

Most "ArgoCD isn't giving me much" feelings come from one of these five misconfigurations. Avoid them and the value becomes obvious.

Mistake 1 ยท Keeping manifests in the same repo as your app code

Tempting โ€” "it's all in one place!" โ€” but it creates a chicken-and-egg problem. Every code change triggers a deploy, and you can't bump image tags cleanly. Fix: use a separate infra repo (or at minimum a separate top-level folder that CI is forbidden to trigger on).

Mistake 2 ยท Not enabling automated sync

People install ArgoCD and then require manual "Sync" clicks in the UI. Now it's just a slower kubectl. Fix: set syncPolicy.automated.selfHeal: true and prune: true. Let ArgoCD actually do its job.

Mistake 3 ยท Secrets in Git

Don't commit plain secrets. Use Sealed Secrets, External Secrets Operator (with Vault / AWS Secrets Manager / GCP SM), or SOPS. All of these integrate cleanly with ArgoCD. Pick one before you roll out to production.

Mistake 4 ยท Using ArgoCD as a CI runner

ArgoCD does not build code. It does not run tests. If you find yourself wanting it to โ€” you want Argo Workflows or Tekton. Keep ArgoCD focused on deploy; keep CI (GitHub Actions) focused on build.

Mistake 5 ยท Not using the App-of-Apps pattern at scale

Managing Applications one-by-one is fine for 5 services. At 50 services you want the "App of Apps" pattern or ApplicationSets โ€” a single Application that templates out N others. Lets you onboard a new microservice by adding one folder to Git.

Section 11 ยท Final answer

When is ArgoCD worth it?

โœ“ Strong yes if you haveโ€ฆ
  • A Kubernetes cluster (any flavor) you deploy to regularly
  • Security team asking about CI blast radius
  • More than one environment (staging + prod) or cluster
  • More than two people deploying
  • Any compliance requirement (SOC 2, ISO, HIPAA, etc.)
  • Production incidents where "who changed what" matters
โœ— Probably overkill ifโ€ฆ
  • You deploy to a single VPS with docker-compose (use Coolify/Dokploy instead)
  • You're a solo dev with one small app and no Kubernetes
  • You deploy once a month and nothing is security-sensitive
  • You use a managed PaaS (Railway, Fly, Render) โ€” they handle this internally

The 60-second recommendation for your situation

You already use Kubernetes and GitHub Actions. You have a security team worried about CI-to-cluster credentials. You've partially set up ArgoCD. The setup you haven't fully felt the benefit of yet is:

  1. Move your Kubernetes manifests into a separate infra repo.
  2. Remove the KUBE_CONFIG secret from every GitHub Actions workflow โ€” have GH Actions commit to the infra repo instead of running kubectl.
  3. Create one ArgoCD Application per service with automated + selfHeal + prune enabled.
  4. Do the first git revert rollback. That's when it clicks.

After those four steps, your infrastructure will be meaningfully more secure โ€” not "according to a blog post," but in a way your security team can verify by checking that no external service holds cluster credentials anymore.

ArgoCD ยท argo-cd.readthedocs.io ยท github.com/argoproj/argo-cd
Compiled April 2026