Argo CD doesn’t actually deploy your applications; it enforces the desired state of your applications as defined in Git.

Let’s see it in action. Imagine we have a simple Nginx deployment defined in Kubernetes manifests:

# nginx-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer

We’ll commit this nginx-app.yaml file to a Git repository. Now, we’ll tell Argo CD to watch this repository and apply these manifests to our Kubernetes cluster.

First, install Argo CD itself. A common way is using its manifest:

kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

This deploys the Argo CD control plane, including the API server, repo server, and application controller, all within the argocd namespace.

Next, we need to create an Application custom resource in Argo CD to define what we want it to manage. This Application resource points to our Git repository and specifies the path within that repository containing our Kubernetes manifests.

# argocd-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx-app
  namespace: argocd # Must be in the argocd namespace
spec:
  project: default
  source:
    repoURL: https://github.com/your-username/your-gitops-repo.git # Replace with your Git repo URL
    path: nginx/ # Replace with the path to your manifests in the repo
    targetRevision: HEAD # Or a specific branch/tag
  destination:
    server: https://kubernetes.default.svc # The cluster Argo CD is running in
    namespace: default # The namespace to deploy the app into
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Apply this Application resource to your cluster:

kubectl apply -n argocd -f argocd-app.yaml

Argo CD’s application controller, upon detecting this new Application resource, will:

  1. Fetch manifests: It clones the specified Git repository (repoURL) and checks out the targetRevision. It then reads the Kubernetes manifests from the path specified.
  2. Compare state: It compares the desired state defined in Git with the current state of resources in the destination Kubernetes cluster and namespace.
  3. Synchronize: If there’s a drift (i.e., the cluster state doesn’t match Git), Argo CD will automatically create, update, or delete Kubernetes resources to match the Git definition. This is the "GitOps" part – Git is the single source of truth.

You can then access the Argo CD UI (usually via argocd-server service, often exposed via port-forwarding initially: kubectl port-forward svc/argocd-server -n argocd 8080:443) to visualize this. You’ll see your nginx-app with a status indicating it’s "Healthy" and "Synced" if everything matches.

If you were to change replicas: 2 to replicas: 3 in your nginx-app.yaml in Git and commit it, Argo CD would detect the change and automatically update the Deployment in your cluster to have 3 replicas. The automated: true sync policy handles this without manual intervention. prune: true ensures that if you delete a resource from Git, Argo CD will delete it from the cluster. selfHeal: true means if someone manually changes a resource in the cluster, Argo CD will revert it back to the state defined in Git.

The core problem Argo CD solves is the lack of a declarative, auditable, and automated way to manage application deployments in Kubernetes. Traditional CI/CD pipelines often involve imperative commands (kubectl apply, kubectl delete), making it hard to track what’s running where and why. Argo CD brings Git as the source of truth, enabling rollbacks by simply reverting commits, providing an audit trail of all changes, and automating the reconciliation process.

The Application resource is not just a pointer to Git. It has a rich set of fields allowing you to define complex deployment strategies. For instance, you can specify different targetRevisions for different environments (e.g., main for staging, production for production) or use Helm charts and Kustomize overlays directly from Git. The syncPolicy is where you control the automation: automated: true enables continuous synchronization, syncOptions allow further customization like CreateNamespace: true to automatically create the destination namespace if it doesn’t exist.

When Argo CD synchronizes, it doesn’t just run kubectl apply. It uses a sophisticated diffing algorithm to determine the precise changes required. It can ignore certain fields that are managed by Kubernetes itself (like resourceVersion or uid) to avoid spurious diffs. This diffing process is what you see in the Argo CD UI when it highlights added, modified, and deleted resources. The application controller then translates these differences into specific Kubernetes API calls.

Most people understand Argo CD synchronizes from Git to Kubernetes. What’s less obvious is how it handles drift detection and reconciliation when the cluster state deviates from Git without a new Git commit. The syncPolicy.selfHeal: true setting means the Argo CD application controller continuously watches the resources it manages. If it detects that a resource’s live state in the cluster does not match the desired state from Git (e.g., someone deleted a pod, or changed a label manually), it will automatically re-apply the desired state from Git to correct the drift. This reconciliation loop is the heart of its GitOps enforcement.

The next concept to explore is managing multiple applications and environments with Argo CD Projects and ApplicationSets.

Want structured learning?

Take the full DevOps & Platform Engineering course →