GitOps with Argo CD
Introduction¶
GitOps: the desired state of your infrastructure lives in Git, and an agent
in the cluster continuously reconciles the cluster to match. CI builds and
pushes images and bumps a manifest; it never kubectl applys. Argo CD is
the common agent.
Why pull, not push¶
- No cluster credentials in CI. CI only needs Git write. Argo CD, inside the cluster, has the deploy permissions.
- Git is the audit log and the rollback mechanism —
git reverta bad change. - Drift is visible. Someone
kubectl edits a Deployment → Argo CD shows it OutOfSync (and can revert it).
The Application¶
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/deploy.git
targetRevision: main
path: apps/api/overlays/prod # kustomize / helm / plain manifests
destination:
server: https://kubernetes.default.svc
namespace: api
syncPolicy:
automated:
prune: true # delete resources removed from Git
selfHeal: true # revert manual changes to match Git
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff: { duration: 10s, factor: 2, maxDuration: 3m }
automated.selfHeal: true— Argo CD reverts out-of-band changes. Powerful; make sure nothing legitimately needs to mutate those objects (HPA changesreplicas— exclude that field withignoreDifferences).prune: true— removing a file from Git deletes the resource. Without it, orphans accumulate.
Scaling to many apps¶
- App-of-apps — one Application whose
pathcontains more Application manifests. A single root app manages dozens. - ApplicationSet — a generator (list, Git directory, cluster, PR) templates
Applications. "One app per directory under
apps/", or "deploy this to every cluster labelledenv=prod".
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata: { name: all-apps, namespace: argocd }
spec:
generators:
- git:
repoURL: https://github.com/org/deploy.git
revision: main
directories: [{ path: "apps/*" }]
template:
metadata: { name: '{{path.basename}}' }
spec:
project: default
source: { repoURL: '...', targetRevision: main, path: '{{path}}' }
destination: { server: 'https://kubernetes.default.svc', namespace: '{{path.basename}}' }
syncPolicy: { automated: { prune: true, selfHeal: true } }
Secrets¶
Argo CD renders manifests from Git — you can't commit plaintext Secrets. Options:
- Sealed Secrets — encrypt to a cluster-specific key; the ciphertext is safe in Git, the controller decrypts in-cluster.
- External Secrets Operator — commit an
ExternalSecretthat references Vault/AWS SM; the operator materialises the real Secret. - SOPS + KSOPS/argocd-vault-plugin — Argo decrypts at render time.
Image updates¶
- Argo CD Image Updater — watches the registry, commits a new tag/digest to Git when a new image appears.
- Or CI bumps the digest in the kustomize/helm values and pushes — simplest, keeps the Git history meaningful.
Verification and troubleshooting¶
argocd app list
argocd app get api
argocd app diff api # Git vs live
argocd app sync api
argocd app history api # revisions; argocd app rollback api <id>
kubectl -n argocd logs deploy/argocd-application-controller
- App stuck
OutOfSyncafter sync — a resource Argo can't fully reconcile: a mutating webhook/controller changes it right back (HPA on replicas, a service mesh injecting sidecars). UseignoreDifferencesfor those fields, orReplace=false. ComparisonError/ "rpc error: repo not accessible" — repo credentials (SSH key / token) missing or expired in Argo CD's repo config.- Sync fails "another operation is already in progress" — a previous sync
hung; terminate it (
argocd app terminate-op api). SharedResourceWarning/ resource owned by another app — two Applications manage the same object. Split the paths or useargocd.argoproj.io/tracking-id.- selfHeal fighting a legitimate change — someone/something needs to mutate a
managed field. Either bring that change into Git, or
ignoreDifferences, or drop selfHeal for that app. - Prune deleted something important — a manifest was removed from Git
(a bad rebase, a moved file).
git revert; considerPruneLast=trueandpruneguarded by a sync window for critical apps. - Helm/Kustomize render differs from
helm templatelocally — Argo's build options (--include-crds, value files, kustomize version). Match them in the Application'ssource.
Related tools and reading¶
- On-site: YAML ↔ JSON converter.
- Related posts: Trunk-based development, Kubernetes version upgrades without drama.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in DevOps & CI/CD
OpenTelemetry for Distributed Tracing
A trace follows one request across services. OpenTelemetry is the vendor-neutral way to produce them — here's the model, the Collector, and sampling that won't bankrupt you.
October 24, 2026pre-commit Hooks That Actually Help
pre-commit runs checks before a commit lands, catching formatting, secrets, and syntax errors locally. Here's a config that helps without being annoying.
October 24, 2026Chaos Engineering Basics
Chaos engineering is controlled experiments that inject failure to verify your system tolerates it. Here's the method, safe blast radius, starter experiments, and game days.
October 24, 2026