0x55aa
← Back to Blog

ArgoCD & GitOps: Put Your Kubernetes Deployments on Autopilot šŸš€šŸ¤–

•8 min read

ArgoCD & GitOps: Put Your Kubernetes Deployments on Autopilot šŸš€šŸ¤–

True story: I once ran kubectl apply -f prod/ from my laptop, accidentally pointed at the wrong cluster config, and deployed an untested feature branch to production at 4 PM on a Friday.

My Slack DMs lit up like a Christmas tree. šŸŽ„ (Not the fun kind.)

The fix was easy. The trust damage took weeks. And the root cause? There was no single source of truth for what was supposed to be running in production.

Enter GitOps — and specifically ArgoCD — the thing that would have saved my Friday and my sanity.

What's GitOps, and Why Should You Care? šŸ¤”

GitOps is a simple but powerful idea:

Git is the only source of truth for what runs in your cluster.

No manual kubectl apply. No "I pushed it from my machine". No mystery configs that exist only in prod. If it's not in Git, it doesn't exist.

The old way (chaotic neutral):

Developer → kubectl apply → Cluster
CI/CD pipeline → kubectl apply → Cluster
Teammate → kubectl apply (oops, wrong context) → Cluster

The GitOps way (lawful good):

Developer → Git commit → Pull Request → Merge → ArgoCD sees the diff → Cluster updated āœ…

ArgoCD is an open-source continuous delivery tool that watches your Git repo and automatically syncs your cluster to match whatever's declared there. It's like having a very attentive sysadmin who never sleeps, never makes typos, and never deploys to the wrong cluster.

Installing ArgoCD (5 Minutes Flat) ⚔

# Create the argocd namespace
kubectl create namespace argocd

# Install ArgoCD
kubectl apply -n argocd -f \
  https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for pods to be ready
kubectl wait --for=condition=available deployment -l app.kubernetes.io/name=argocd-server \
  -n argocd --timeout=120s

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

# Port-forward the UI (or expose via LoadBalancer/Ingress)
kubectl port-forward svc/argocd-server -n argocd 8080:443

Open https://localhost:8080, log in with admin and your generated password. You've got a UI. You're already winning. šŸ†

Your First ArgoCD Application šŸ“¦

Here's where the magic happens. You define an Application object that tells ArgoCD: "Watch this Git repo, and make the cluster look like it."

# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-api
  namespace: argocd
spec:
  project: default

  # Where to get the desired state
  source:
    repoURL: https://github.com/yourname/your-k8s-configs
    targetRevision: main
    path: apps/my-api         # Folder with your K8s manifests

  # Where to apply it
  destination:
    server: https://kubernetes.default.svc
    namespace: production

  # Sync policy: do it automatically!
  syncPolicy:
    automated:
      prune: true             # Delete resources removed from Git
      selfHeal: true          # Fix manual changes back to desired state
    syncOptions:
      - CreateNamespace=true  # Create namespace if it doesn't exist

Apply it once:

kubectl apply -f argocd-app.yaml

Now every time someone merges to main in your-k8s-configs, ArgoCD will automatically apply the changes. No more kubectl apply. No more "did you deploy the config too?"

The deployment flow becomes:

  1. Developer opens a PR to update apps/my-api/deployment.yaml (e.g., bump image tag)
  2. Team reviews, approves, merges
  3. ArgoCD detects the change within ~3 minutes
  4. ArgoCD applies the update with a full audit trail
  5. You can see the sync status in the UI — green checkmark or a clear error message

The Feature That Made Me a True Believer: Self-Healing šŸ›”ļø

This is where I went from "interesting tool" to "I will evangelize this at every standup."

Someone on my team — bless their heart — kubectl exec'd into a production pod and edited a ConfigMap directly to "quickly test something." They forgot to update Git. A week later, nobody could explain why prod was behaving differently from staging.

With ArgoCD's selfHeal: true:

# Someone edits a resource manually in the cluster
kubectl patch deployment my-api -n production \
  -p '{"spec":{"replicas":1}}'    # Changed replicas from 3 to 1!

# ArgoCD detects the drift within minutes
# Status: OutOfSync āŒ

# ArgoCD reverts it back to what Git says (3 replicas)
# Status: Synced āœ…

# Audit log shows: "Auto-synced: reverted manual change"

Git is now the unbeatable authority. Manual changes bounce off it like ping-pong balls. No more mystery configs. No more "who changed this?!" conversations.

Real-World Repo Structure That Actually Works šŸ“

After experimenting with several layouts, here's what I've found works well for small-to-medium teams:

k8s-configs/               # Your GitOps repo (separate from app code!)
ā”œā”€ā”€ apps/
│   ā”œā”€ā”€ api/
│   │   ā”œā”€ā”€ deployment.yaml
│   │   ā”œā”€ā”€ service.yaml
│   │   ā”œā”€ā”€ configmap.yaml
│   │   └── hpa.yaml
│   └── worker/
│       ā”œā”€ā”€ deployment.yaml
│       └── configmap.yaml
ā”œā”€ā”€ infrastructure/
│   ā”œā”€ā”€ ingress-nginx/
│   └── cert-manager/
└── argocd/
    ā”œā”€ā”€ api-app.yaml        # The ArgoCD Application manifests
    └── worker-app.yaml

Why a separate repo?

Your application code repo (git push to deploy a new feature) and your infrastructure config repo (update image tag to deploy) have different change rates, different reviewers, and different risk profiles. Keep them apart. Your future self will thank you.

The CI pipeline for your app repo does one thing: build, test, and update the image tag in the k8s-configs repo. That's the handoff. ArgoCD takes it from there.

# .github/workflows/deploy.yml (in your app repo)
- name: Update image tag in k8s-configs
  run: |
    git clone https://github.com/yourname/k8s-configs
    cd k8s-configs
    # Update the image tag in deployment.yaml
    sed -i "s|image: myapp:.*|image: myapp:${{ github.sha }}|" apps/api/deployment.yaml
    git commit -am "ci: deploy api@${{ github.sha }}"
    git push
  # ArgoCD picks up the push and deploys!

Lessons Learned the Hard Way 🪤

1. Pruning is powerful — but review before you enable it.

prune: true means if you delete a file from Git, ArgoCD deletes the resource from the cluster. That's great until you accidentally git rm the wrong YAML on a Friday. Start with pruning disabled, understand your configs, then enable it.

2. Separate your environment configs.

Don't have a single main branch control both staging and production. Use either:

  • Branch-based: staging branch → staging cluster, production branch → prod cluster
  • Directory-based: envs/staging/ and envs/production/ folders, one ArgoCD app per env

I prefer directory-based with Kustomize overlays — but either beats "pray the right context is set in kubectl."

3. Use App of Apps for scale.

Once you have 10+ services, managing individual Application manifests gets tedious. ArgoCD has an "App of Apps" pattern: one ArgoCD Application points to a folder full of other Application manifests. One repo to rule them all.

# The "root" app that manages all other apps
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: all-apps
  namespace: argocd
spec:
  source:
    repoURL: https://github.com/yourname/k8s-configs
    targetRevision: main
    path: argocd/apps       # Folder containing all Application YAMLs
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      selfHeal: true

One merge to add a new Application YAML and your new service is managed. Zero manual ArgoCD UI clicks needed.

The Audit Trail That Saved My Bacon šŸ„“

Three months into using ArgoCD, a weird configuration appeared in production. In the old world, this becomes a detective story with no clear ending.

In the ArgoCD world:

  • Every sync operation is logged with a timestamp
  • Every sync references a specific Git commit SHA
  • Every Git commit has an author, a PR number, and a review trail

I opened the ArgoCD UI, clicked "History & Rollback," saw exactly which commit introduced the change, clicked through to the GitHub PR, found the author, and had a productive (non-accusatory!) conversation. Done in 4 minutes.

And rollbacks? Two clicks in the UI, or one command:

# Roll back to the previous sync
argocd app rollback my-api

# Or roll back to a specific history ID
argocd app rollback my-api 42

ArgoCD re-applies the old Git state. No panic. No git revert and hope for the best. Just a clean, verified rollback to a known-good state.

Your 30-Minute GitOps Starter Checklist āœ…

Right now:

  1. Install ArgoCD in your cluster (it's 5 minutes)
  2. Create a k8s-configs repo with your existing manifests
  3. Create one Application pointing at it — manual sync only to start
  4. Verify ArgoCD shows your cluster is "Synced"

This week:

  1. Enable automated sync for staging
  2. Wire your CI pipeline to update image tags via Git commit
  3. Enable selfHeal and watch drift disappear
  4. Enable prune after you're confident in your configs

This month:

  1. Migrate production to GitOps
  2. Implement App of Apps pattern
  3. Set up notifications (Slack/email) for sync failures
  4. Celebrate never running kubectl apply from a laptop again šŸŽ‰

The Bottom Line šŸ’”

GitOps with ArgoCD doesn't just make deployments faster — it makes them auditable, reversible, and boring. Boring is good. Boring means no 3 AM pages. Boring means no mystery configs. Boring means you can sleep on Friday nights.

The best deployment is the one you didn't have to think about.

Stop deploying with your fingers crossed and a kubectl command in your terminal history. Let Git be the authority, let ArgoCD do the work, and go build features instead.


Still deploying manually? Find me on LinkedIn — I will personally help you set up ArgoCD in 30 minutes.

Want to see a real GitOps repo structure? Check out my GitHub for examples.

Now go commit your configs, merge that PR, and let ArgoCD do the rest! šŸ¤–āœØ


P.S. The first time ArgoCD auto-reverted a manual change someone made to production, they were annoyed for about 10 minutes — then admitted it was the right call. GitOps wins hearts slowly but surely. šŸ’š