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?
- Edit in Git: Change
replicas: 2toreplicas: 3in deployment.yaml - Commit and push
- ArgoCD detects the change (within 3 minutes by default, or immediately with webhooks)
- 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:
- Sealed Secrets
- External Secrets Operator with Vault
- SOPS encrypted secrets
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:
- App-of-Apps pattern: Manage applications with applications
- Drift detection: Monitor for unauthorized changes
- Disaster recovery: Rebuild clusters from Git
- ApplicationSets: Generate applications dynamically
- Notifications: Slack/email on sync events
My Recommendation
- Start with one application. Get comfortable with the workflow.
- Enable automated sync + selfHeal. Experience the full GitOps benefit.
- Use a dedicated gitops repo. Separate infrastructure from application code.
- Set up webhooks. Faster sync than polling.
- 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.
