· CI/CD · 3 min read
GitOps on Kubernetes with ArgoCD
ArgoCD changed how I think about deployments. Here's how to set up GitOps for your Kubernetes workloads — and why you won't go back to manual kubectl applies.
What Is GitOps?
GitOps is a deployment model where your Git repository is the single source of truth for infrastructure and application state. Instead of running kubectl apply manually or triggering imperative scripts, a GitOps operator continuously reconciles your cluster state with what’s declared in Git.
The key properties:
- Declarative — desired state is defined in Git
- Automated — changes in Git automatically sync to the cluster
- Observable — drift between Git and cluster is detected and reported
- Auditable — every change has a Git commit, author, and timestamp
Why ArgoCD?
I’ve used ArgoCD across multiple production EKS environments. It’s become my default for Kubernetes deployments because it:
- Provides a clean UI to visualize application state
- Detects and alerts on configuration drift
- Supports Helm, Kustomize, and plain YAML
- Integrates cleanly with GitHub Actions pipelines
- Handles multi-cluster and multi-tenant setups
Installation
kubectl create namespace argocd
kubectl apply -n argocd -f \
https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Install ArgoCD CLI
brew install argocd
# Port-forward to access the UI
kubectl port-forward svc/argocd-server -n argocd 8080:443For production, use Helm and expose via your Ingress:
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
--set server.ingress.enabled=true \
--set server.ingress.hosts[0]=argocd.internal.yourdomain.comYour First Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/your-helm-charts
targetRevision: HEAD
path: charts/my-app
helm:
values: |
image:
tag: latest
replicaCount: 2
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Re-sync if cluster drifts from Git
syncOptions:
- CreateNamespace=trueApply it:
kubectl apply -f my-app.yamlArgoCD will detect the application and start syncing immediately.
The CI/CD + GitOps Flow
My standard pipeline pattern with GitHub Actions:
Code push → GitHub Actions (build + test + push image) → Update Helm values in Git → ArgoCD detects change → Syncs to clusterThe GitHub Actions step that updates the image tag:
- name: Update image tag in Helm values
run: |
git clone https://github.com/your-org/your-helm-charts.git
cd your-helm-charts
# Update the image tag
sed -i "s/tag: .*/tag: ${{ github.sha }}/" charts/my-app/values.yaml
git config user.email "ci@jakops.dev"
git config user.name "GitHub Actions"
git add .
git commit -m "ci: deploy my-app@${{ github.sha }}"
git pushArgoCD polls Git every 3 minutes by default, or you can use webhooks for instant sync.
App of Apps Pattern
For managing many applications, use the App of Apps pattern — one ArgoCD application that manages other ArgoCD applications:
# root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
spec:
source:
repoURL: https://github.com/your-org/gitops-repo
path: apps/
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: truegitops-repo/
apps/
my-app.yaml
another-service.yaml
monitoring.yaml
ingress-nginx.yamlEach file in apps/ is an ArgoCD Application manifest. Add a new file, ArgoCD picks it up automatically. Remove a file, ArgoCD prunes the application.
Rollbacks
One of the best features: rollbacks are a Git revert away.
# Via CLI
argocd app rollback my-app <revision-id>
# Or just revert the Git commit — ArgoCD will sync automatically
git revert HEAD
git pushThe cluster state follows Git. Always.
Conclusion
ArgoCD + GitOps fundamentally changes your relationship with deployments. Instead of worrying about what’s running in the cluster, you trust Git. Drift is detected automatically, rollbacks are instant, and every change is auditable. Once you operate this way, going back to imperative deployments feels reckless.