Search…

GitOps with ArgoCD

In this series (14 parts)
  1. Why Kubernetes exists
  2. Kubernetes architecture
  3. Core Kubernetes objects
  4. Kubernetes networking
  5. Storage in Kubernetes
  6. Kubernetes configuration and secrets
  7. Resource management and autoscaling
  8. Kubernetes workload types
  9. Kubernetes observability
  10. Kubernetes security
  11. Helm and package management
  12. GitOps with ArgoCD
  13. Kubernetes cluster operations
  14. 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.

Start typing to search across all content
navigate Enter open Esc close