ArgoCD and GitOps for Kubernetes
ArgoCD makes Git the source of truth for your Kubernetes clusters. Learn the GitOps pattern, the App-of-Apps model, and sync strategies that work.
What you'll learn
- ✓What GitOps is and why it beats push-based deploys
- ✓How ArgoCD watches Git and reconciles clusters
- ✓How Applications and Projects organize your deploys
- ✓How sync waves and hooks order rollouts
- ✓How the App-of-Apps pattern scales to many services
Prerequisites
- •Comfortable with Kubernetes basics — see What is Kubernetes
- •Familiar with CI/CD pipelines — see What is CI/CD
GitOps is the idea that the desired state of your infrastructure lives in Git and a controller in the cluster makes the cluster match. ArgoCD is the most widely used controller for that pattern on Kubernetes. Instead of CI pipelines pushing manifests with kubectl apply, ArgoCD pulls from your repo and reconciles continuously. This post walks through the parts and the patterns.
Push vs pull deploys
A push pipeline runs in CI, holds cluster credentials, and applies manifests over the network. It works, and it is how most teams start. Problems show up at scale:
- Every cluster needs CI credentials.
- Drift (someone runs
kubectl editin production) goes unnoticed. - Rollbacks require re-running the pipeline with the old commit.
- The cluster has no idea what it is supposed to look like.
A pull model flips it. The cluster runs a controller that knows the Git URL and the path. It clones, compares, and applies. Drift is auto-corrected. Rollbacks are git reverts. The cluster’s desired state is exactly the contents of a branch.
Install ArgoCD
The standard install is a Kubernetes manifest in a dedicated namespace.
kubectl create namespace argocd
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
Port-forward the UI and log in with the initial admin password.
kubectl -n argocd port-forward svc/argocd-server 8080:443
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
Visit https://localhost:8080. The UI is one of ArgoCD’s strongest features — every sync, diff, and error is visible.
The Application resource
The unit of work in ArgoCD is an Application. It points at a Git repo and path, names a destination namespace, and declares sync behavior.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: web
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/codeloom/infra.git
targetRevision: main
path: apps/web
destination:
server: https://kubernetes.default.svc
namespace: web
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
What this says: watch apps/web on the main branch, apply it to the web namespace in this cluster, automatically delete resources that have been removed from Git (prune), and revert manual changes (selfHeal).
Apply this manifest once. From now on, every commit to apps/web/ triggers a sync within minutes.
Projects for boundaries
AppProject resources group Applications and restrict what they can do.
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: payments
namespace: argocd
spec:
sourceRepos:
- https://github.com/codeloom/payments-infra.git
destinations:
- namespace: payments-*
server: https://kubernetes.default.svc
clusterResourceWhitelist: []
namespaceResourceWhitelist:
- group: ""
kind: "*"
The payments team can only deploy from their repo into namespaces starting with payments-. They cannot touch cluster-scoped resources. This is how you give teams self-service without giving them the keys to the cluster.
Sync waves and hooks
When an Application contains many manifests, order matters. ArgoCD sync waves let you express that.
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1"
Lower numbers go first. CRDs in wave -1, namespaces in wave 0, deployments in wave 1, jobs that depend on them in wave 2. ArgoCD waits for each wave to be healthy before starting the next.
Hooks run extra resources at specific phases (PreSync, Sync, PostSync, SyncFail). A common use is a database migration job:
metadata:
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
Migrations run before the new pods start. If they fail, the sync fails and the old version stays live.
The App-of-Apps pattern
Hand-rolling Applications in the UI for every service does not scale. The App-of-Apps pattern uses one parent Application whose Git path contains child Application manifests.
Repo layout:
clusters/
prod/
apps/
web.yaml # Application
api.yaml # Application
worker.yaml # Application
root.yaml # Application that points at apps/
Apply root.yaml once. The parent Application syncs the directory, which creates the child Applications, which sync the actual services. To add a new service, open a PR adding a child manifest. Merging deploys it.
This is GitOps applied to GitOps itself. The list of deployed apps becomes a git-versioned file.
Promoting between environments
A common arrangement:
- One repo with environment overlays (
overlays/dev,overlays/staging,overlays/prod). - One ArgoCD Application per environment, pointing at the matching overlay.
- CI builds images and updates the image tag in a values file via a PR.
- Promotion is a PR that copies the dev tag to staging, then staging to prod.
Tools like Argo CD Image Updater can automate the tag bump. The promotion review still happens in pull requests.
Drift detection and self-heal
selfHeal: true is powerful and divisive. It means anyone running kubectl edit on a managed resource will see their change reverted within minutes. That is the point — there is one source of truth and it is Git. For exploratory environments, leave self-heal off. For production, leave it on.
The UI highlights drift even when self-heal is off, which is useful on its own. Drift you can see is drift you can fix.
Secrets
ArgoCD does not manage secrets directly. Common patterns:
- Sealed Secrets — encrypted Secrets that decrypt only in-cluster.
- External Secrets Operator — pulls from a secret manager and creates Kubernetes Secrets.
- SOPS-encrypted files with a plugin.
Whatever you pick, the cleartext never lives in Git.
Observability and alerts
ArgoCD exposes Prometheus metrics for sync status, durations, and reconciliation health. Hook these into the dashboards you already have. Alert when an Application is OutOfSync for more than a few minutes or when a sync fails in production.
Pitfalls
- Letting
masterandproductiondrift apart. Use protected branches and PR-based promotion. - Mixing application code and manifests in the same repo without discipline. A separate
infrarepo per team is often clearer. - Hand-editing the live cluster “just this once.” With self-heal on, you will be confused when your change disappears.
- Forgetting
CreateNamespace=truefor new apps. The first sync will fail with a missing namespace.
Wrap up
ArgoCD turns Git into the control plane for Kubernetes. You declare what should exist, the controller makes it so, and the audit log is your commit history. Start with one Application pointing at one path. Add Projects to scope team access. Adopt the App-of-Apps pattern when you have more than a handful of services. With sync waves, hooks, and selfHeal in place, deploys stop being a thing you do and become a thing the cluster does on its own.