GitOps changed how I think about deployments. Instead of running commands against a cluster, I push to Git and watch the cluster converge to the desired state. It sounds simple, but the implications are profound.

ArgoCD is my tool of choice for GitOps. Let me show you why, and how to get started.

Why GitOps? The Philosophy First

Before diving into ArgoCD, let’s understand why GitOps matters.

Traditional deployment:

Developer → kubectl apply → Cluster

The problem: What is deployed? You have to query the cluster. Configuration drift happens silently. Rollbacks are manual and error-prone. There’s no audit trail beyond “someone ran kubectl.”

GitOps deployment:

Developer → Git Push → ArgoCD → Cluster

The difference: Git is the source of truth. What’s in Git is what’s in the cluster. Always. If someone manually changes something, ArgoCD reverts it. If you want to know what’s deployed, check Git. If you want to rollback, revert the commit.

This aligns with my core philosophy: systems should be explicit and understandable. Imperative commands are ephemeral. Declarative state in Git is permanent and auditable.

Installing ArgoCD

Let’s get ArgoCD running. I’ll use a simple installation first, then explain the pieces.

# Create 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=Ready pods --all -n argocd --timeout=300s

That’s it. ArgoCD is now running. Let’s access it:

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

# Port-forward the UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

# Open https://localhost:8080, login as 'admin' with the password above

The Core Concepts

ArgoCD has a few key concepts to understand:

Application

An Application is the fundamental unit in ArgoCD. It defines:

  • Source: Where your manifests live (Git repo, Helm chart, Kustomize)
  • Destination: Which cluster and namespace to deploy to
  • Sync Policy: How to handle differences between Git and cluster
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/yourorg/yourrepo.git
    targetRevision: main
    path: manifests/my-app
  destination:
    server: https://kubernetes.default.svc
    namespace: my-app
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Project

A Project groups applications and defines access controls. The default project allows everything, but in production you’ll want restrictions.

Sync

Sync is the process of making the cluster match Git. It can be:

  • Manual: You click a button
  • Automated: ArgoCD does it when Git changes
  • Self-healing: ArgoCD reverts manual cluster changes

Your First Deployment

Let’s deploy something real. I’ll create a simple nginx deployment managed by ArgoCD.

Step 1: Create a Git Repository

Create a new repo (or use an existing one) with this structure:

my-gitops-repo/
└── apps/
    └── nginx/
        ├── deployment.yaml
        ├── service.yaml
        └── kustomization.yaml

deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80

service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    targetPort: 80

kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml

Commit and push this to your repo.

Step 2: Create the ArgoCD Application

# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/YOURUSER/my-gitops-repo.git
    targetRevision: main
    path: apps/nginx
  destination:
    server: https://kubernetes.default.svc
    namespace: nginx
  syncPolicy:
    syncOptions:
      - CreateNamespace=true
    automated:
      prune: true
      selfHeal: true

Apply it:

kubectl apply -f argocd-application.yaml

Step 3: Watch the Magic

Open the ArgoCD UI. You’ll see your application appear. If you enabled automated sync, it will immediately deploy. Otherwise, click “Sync.”

flowchart LR
    subgraph dashboard["ArgoCD Dashboard - nginx"]
        direction LR
        DEP["Deployment<br/>nginx"] --> RS["ReplicaSet<br/>nginx"]
        RS --> POD["Pod x2<br/>nginx"]
        DEP --> SVC["Service<br/>nginx"]
    end

ArgoCD shows the resource tree, health status, and sync status. It’s immediately clear what’s deployed.

The GitOps Workflow

Now comes the beautiful part. Want to change something?

  1. Edit in Git: Change replicas: 2 to replicas: 3 in deployment.yaml
  2. Commit and push
  3. ArgoCD detects the change (within 3 minutes by default, or immediately with webhooks)
  4. Cluster updates automatically

No kubectl. No scripts. No “did I run that command?” Git is the truth.

Rollback? Just Revert

Something broke? Revert the commit in Git. ArgoCD syncs. Done.

git revert HEAD
git push

# ArgoCD automatically rolls back the cluster

The cluster state is now auditable through git log:

git log --oneline apps/nginx/
# a1b2c3d Scale nginx to 3 replicas
# d4e5f6g Initial nginx deployment

Self-Healing in Action

Enable selfHeal: true and try this:

# Manually scale the deployment
kubectl scale deployment nginx -n nginx --replicas=5

# Wait a moment...
kubectl get deployment nginx -n nginx
# NAME    READY   UP-TO-DATE   AVAILABLE
# nginx   3/3     3            3

ArgoCD noticed the drift and reverted it. The cluster always matches Git.

This prevents configuration drift — one of the most insidious problems in infrastructure. No more “someone changed that setting last year and nobody remembers why.”

Sync Policies Explained

The sync policy controls how ArgoCD behaves:

syncPolicy:
  automated:
    prune: true      # Delete resources removed from Git
    selfHeal: true   # Revert manual changes
    allowEmpty: false # Don't sync if source is empty
  syncOptions:
    - CreateNamespace=true
    - PruneLast=true  # Prune after other syncs complete
    - ApplyOutOfSyncOnly=true  # Only apply changed resources
  retry:
    limit: 5
    backoff:
      duration: 5s
      factor: 2
      maxDuration: 3m

My recommendation: Start with automated sync, prune, and selfHeal all enabled. This gives you the full GitOps experience. You can always disable for specific applications that need special handling.

Private Repositories

Most real repos are private. ArgoCD needs credentials:

# Using the CLI
argocd repo add https://github.com/yourorg/private-repo.git \
  --username git \
  --password ghp_yourtoken

# Or as a Kubernetes secret
kubectl create secret generic private-repo \
  -n argocd \
  --from-literal=url=https://github.com/yourorg/private-repo.git \
  --from-literal=username=git \
  --from-literal=password=ghp_yourtoken

kubectl label secret private-repo -n argocd \
  argocd.argoproj.io/secret-type=repository

For SSH:

argocd repo add git@github.com:yourorg/private-repo.git \
  --ssh-private-key-path ~/.ssh/id_rsa

Helm and Kustomize

ArgoCD natively supports Helm and Kustomize.

Helm Charts

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prometheus
  namespace: argocd
spec:
  source:
    repoURL: https://prometheus-community.github.io/helm-charts
    chart: prometheus
    targetRevision: 25.0.0
    helm:
      values: |
        server:
          persistentVolume:
            enabled: true
            size: 10Gi
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring

Kustomize Overlays

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-production
spec:
  source:
    repoURL: https://github.com/yourorg/yourrepo.git
    path: apps/my-app/overlays/production
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
    namespace: production

ArgoCD detects kustomization.yaml and applies it automatically.

Common Mistakes to Avoid

Mistake 1: Not Enabling Prune

Without prune: true, ArgoCD won’t delete resources you remove from Git. You end up with orphaned resources.

Mistake 2: Putting Secrets in Git

Never commit plain secrets to Git. Use:

Mistake 3: Manual Changes “Just This Once”

The moment you manually change something, you’ve introduced drift. Either update Git or enable selfHeal to revert your change.

Mistake 4: Single Monolithic Application

One giant Application with everything is hard to manage. Split into multiple applications by service or team.

What’s Next?

This is just the beginning. ArgoCD has much more:

My Recommendation

  1. Start with one application. Get comfortable with the workflow.
  2. Enable automated sync + selfHeal. Experience the full GitOps benefit.
  3. Use a dedicated gitops repo. Separate infrastructure from application code.
  4. Set up webhooks. Faster sync than polling.
  5. Don’t fight it. If you find yourself wanting manual control, ask why.

GitOps isn’t just a deployment method. It’s a mindset shift. Once you experience “Git is truth,” you won’t want to go back to imperative commands and configuration drift.