GitOps with ArgoCD
In this series (14 parts)
- Why Kubernetes exists
- Kubernetes architecture
- Core Kubernetes objects
- Kubernetes networking
- Storage in Kubernetes
- Kubernetes configuration and secrets
- Resource management and autoscaling
- Kubernetes workload types
- Kubernetes observability
- Kubernetes security
- Helm and package management
- GitOps with ArgoCD
- Kubernetes cluster operations
- Service mesh concepts
GitOps defines a single principle: Git is the source of truth for infrastructure. ArgoCD enforces it. It watches a Git repository, compares desired state against live cluster state, and reconciles any drift. No more kubectl apply from a laptop.
ArgoCD architecture
ArgoCD runs inside your cluster as three core components.
The API server exposes a gRPC and REST API for authentication, RBAC, and the web UI. The repo server clones Git repositories, renders Helm charts or Kustomize overlays, and produces plain manifests. The application controller watches the Kubernetes API for live state, compares it against desired manifests, and triggers syncs on drift.
graph LR Git["Git Repository"] -->|poll / webhook| RS["Repo Server"] RS -->|rendered manifests| AC["Application Controller"] AC -->|apply| K8s["Kubernetes Cluster"] K8s -->|live state| AC AC -->|drift detected| AC AC -->|status| API["API Server"] API -->|dashboard| User["Developer / UI"]
ArgoCD sync loop: the repo server fetches manifests from Git, the application controller compares them against live cluster state, and reconciles drift.
All components are stateless except for a backing Redis cache. Configuration is stored as custom resources.
The Application resource
An Application maps a path in a Git repository to a namespace in a Kubernetes cluster.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frontend
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: apps/frontend
destination:
server: https://kubernetes.default.svc
namespace: frontend
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
The source block points at a Git repo and path. The destination targets a cluster and namespace.
Sync policies
By default ArgoCD only detects drift. You must configure sync policies to act on it.
Manual sync is the default. ArgoCD shows the diff and waits for approval.
Automated sync removes the manual step. The controller applies changes on detection.
Prune deletes resources from the cluster when they disappear from Git. Without prune: true, removed manifests leave orphaned resources.
Self-heal reverts manual kubectl changes back to the Git-defined state within seconds.
syncPolicy:
automated:
prune: true
selfHeal: true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Self-healing in practice
With self-heal enabled, every manual kubectl edit or kubectl scale is overwritten. ArgoCD reverts to whatever the manifest says within the reconciliation cycle (default 3 minutes, or immediate on webhook). Disable self-heal only during active incidents.
App-of-apps pattern
The app-of-apps pattern uses one root Application that points to a directory of other Application manifests.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: argocd-apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
The argocd-apps/ directory contains individual Application YAMLs, one per service. Adding a new YAML file triggers the root app to sync, and ArgoCD picks up the child application. Deleting the file removes the child and its resources.
ApplicationSets
ApplicationSets generate Application resources dynamically using generators.
Git directory generator
Scan a directory structure and create one Application per subdirectory:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: cluster-apps
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/myorg/k8s-manifests.git
revision: main
directories:
- path: apps/*
template:
metadata:
name: "{{path.basename}}"
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: "{{path}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{path.basename}}"
Adding a new folder under apps/ creates a new Application automatically.
Cluster generator
Deploy the same app across multiple clusters:
generators:
- clusters:
selector:
matchLabels:
env: production
template:
metadata:
name: "monitoring-{{name}}"
spec:
project: default
source:
repoURL: https://github.com/myorg/k8s-manifests.git
targetRevision: main
path: monitoring
destination:
server: "{{server}}"
namespace: monitoring
Every cluster labeled env: production gets a monitoring stack automatically.
List generator
For explicit control over parameters per target:
generators:
- list:
elements:
- cluster: staging
url: https://staging.k8s.example.com
- cluster: production
url: https://prod.k8s.example.com
Secrets management
Git stores everything in plain text. Secrets cannot go into Git unencrypted. ArgoCD does not solve this alone, so you need a companion tool.
Sealed Secrets
Bitnami Sealed Secrets uses asymmetric encryption. You encrypt secrets client-side with kubeseal, commit the SealedSecret to Git, and the in-cluster controller decrypts it into a regular Secret.
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-credentials
namespace: backend
spec:
encryptedData:
password: AgBy8h...encrypted-base64...
username: AgCx3k...encrypted-base64...
The sealed secret is safe to commit. Only the cluster’s private key can decrypt it.
External Secrets Operator
External Secrets Operator pulls secrets from AWS Secrets Manager, HashiCorp Vault, or Google Secret Manager.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: backend
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: prod/db/password
This approach keeps secrets outside Git entirely. Choose Sealed Secrets for self-contained clusters. Choose External Secrets Operator when you already have a centralized secret store.
What comes next
ArgoCD handles deployments. But a production cluster needs more. The next article covers cluster operations: monitoring with Prometheus, log aggregation, node management, and disaster recovery.