GitOps for Small Teams: ArgoCD, Kustomize, and Automated Drift Detection

17 min read
gitopsargocdfluxkubernetesdeployment automationdrift detection

GitOps treats your Git repository as the single source of truth for infrastructure and application state. Every change goes through a pull request, every deployment is auditable, and drift from the desired state is detected and corrected automatically. For small teams of 2-8 engineers, GitOps eliminates the "who deployed what and when" problem entirely.

ArgoCD is the most widely adopted GitOps controller for Kubernetes, used by 62% of GitOps practitioners according to the 2025 CNCF survey. Combined with Kustomize for environment overlays, it provides a complete deployment system that requires zero custom scripting. This guide covers the full setup from repository structure through production rollback.

---

Repository Structure with Kustomize

Kustomize uses a base-plus-overlays pattern. The base directory contains resources common to all environments. Each overlay directory patches the base for a specific environment (staging, production). This eliminates the copy-paste problem of maintaining separate YAML files per environment.

k8s/

base/

deployment.yaml

service.yaml

ingress.yaml

kustomization.yaml

overlays/

staging/

kustomization.yaml

replica-patch.yaml

production/

kustomization.yaml

replica-patch.yaml

ingress-patch.yaml

---

Base Resources

The base `kustomization.yaml` declares all shared resources:

<h1>k8s/base/kustomization.yaml</h1>

apiVersion: kustomize.config.k8s.io/v1beta1

kind: Kustomization

resources:

- deployment.yaml

- service.yaml

- ingress.yaml

commonLabels:

app: my-app

managed-by: kustomize

<h1>k8s/base/deployment.yaml</h1>

apiVersion: apps/v1

kind: Deployment

metadata:

name: my-app

spec:

replicas: 1

selector:

matchLabels:

app: my-app

template:

metadata:

labels:

app: my-app

spec:

containers:

- name: app

image: ghcr.io/my-org/my-app:latest

ports:

- containerPort: 3000

resources:

requests:

cpu: 100m

memory: 128Mi

limits:

cpu: 500m

memory: 512Mi

readinessProbe:

httpGet:

path: /health

port: 3000

initialDelaySeconds: 5

periodSeconds: 10

livenessProbe:

httpGet:

path: /health

port: 3000

initialDelaySeconds: 15

periodSeconds: 20

<h1>k8s/base/service.yaml</h1>

apiVersion: v1

kind: Service

metadata:

name: my-app

spec:

selector:

app: my-app

ports:

- port: 80

targetPort: 3000

---

Environment Overlays

Overlays patch the base resources for each environment. Staging uses fewer replicas and lower resource limits. Production uses more replicas, custom ingress rules, and tighter resource constraints.

<h1>k8s/overlays/staging/kustomization.yaml</h1>

apiVersion: kustomize.config.k8s.io/v1beta1

kind: Kustomization

namespace: staging

resources:

- ../../base

patches:

- path: replica-patch.yaml

images:

- name: ghcr.io/my-org/my-app

newTag: staging-latest

<h1>k8s/overlays/staging/replica-patch.yaml</h1>

apiVersion: apps/v1

kind: Deployment

metadata:

name: my-app

spec:

replicas: 1

<h1>k8s/overlays/production/kustomization.yaml</h1>

apiVersion: kustomize.config.k8s.io/v1beta1

kind: Kustomization

namespace: production

resources:

- ../../base

patches:

- path: replica-patch.yaml

- path: ingress-patch.yaml

images:

- name: ghcr.io/my-org/my-app

newTag: v1.2.0

<h1>k8s/overlays/production/replica-patch.yaml</h1>

apiVersion: apps/v1

kind: Deployment

metadata:

name: my-app

spec:

replicas: 3

---

ArgoCD Application Configuration

ArgoCD watches your Git repository and syncs the desired state to the cluster. The Application resource tells ArgoCD which repo, path, and branch to monitor.

<h1>argocd/staging-app.yaml</h1>

apiVersion: argoproj.io/v1alpha1

kind: Application

metadata:

name: my-app-staging

namespace: argocd

spec:

project: default

source:

repoURL: https://github.com/my-org/my-app.git

targetRevision: main

path: k8s/overlays/staging

destination:

server: https://kubernetes.default.svc

namespace: staging

syncPolicy:

automated:

prune: true

selfHeal: true

syncOptions:

- CreateNamespace=true

retry:

limit: 5

backoff:

duration: 5s

factor: 2

maxDuration: 3m

<h1>argocd/production-app.yaml</h1>

apiVersion: argoproj.io/v1alpha1

kind: Application

metadata:

name: my-app-production

namespace: argocd

spec:

project: default

source:

repoURL: https://github.com/my-org/my-app.git

targetRevision: main

path: k8s/overlays/production

destination:

server: https://kubernetes.default.svc

namespace: production

syncPolicy:

automated:

prune: true

selfHeal: false

syncOptions:

- CreateNamespace=true

The key difference between environments: staging has `selfHeal: true` (auto-corrects drift), while production has `selfHeal: false` (alerts on drift but requires manual sync). This prevents accidental production changes from auto-reverting during an active incident response.

---

PR-Based Promotion Workflow

Promotion from staging to production follows a strict PR workflow:

  • CI pipeline builds and pushes an image tagged with the commit SHA
  • A bot or developer updates the staging overlay image tag
  • ArgoCD auto-syncs the staging environment
  • After validation, a PR updates the production overlay image tag
  • Team reviews the PR (diff shows exactly what changes reach production)
  • Merge triggers ArgoCD production sync
  • <h1>Automated image tag update script</h1>
    

    #!/bin/bash

    NEW_TAG=$1

    ENVIRONMENT=$2

    cd k8s/overlays/$ENVIRONMENT

    <h1>Update image tag in kustomization.yaml</h1>

    kustomize edit set image ghcr.io/my-org/my-app:$NEW_TAG

    <h1>Commit and push</h1>

    git add kustomization.yaml

    git commit -m "deploy($ENVIRONMENT): update image to $NEW_TAG"

    git push origin main

    This script can run as the final step of your CI pipeline for staging, and as a manual step (or PR) for production. The Git history becomes a complete deployment log.

    ---

    Drift Detection and Self-Healing

    Drift occurs when someone applies a manual `kubectl` change that differs from the Git repository state. ArgoCD detects drift in two ways:

    • Periodic reconciliation: ArgoCD compares the live cluster state to the Git repository every 3 minutes (configurable)
    • Webhook-based: ArgoCD receives webhook notifications on Git push and immediately reconciles

    Drift TypeDetectionAuto-HealAction Required

    Replica count changed manually3 minutesYes (staging)None
    ConfigMap modified via kubectl3 minutesYes (staging)None
    Image tag changed manually3 minutesNo (production)Manual sync
    New resource added manually3 minutesPrunedNone

    To configure webhook-based drift detection for faster response:

    <h1>Add webhook to your GitHub repository</h1>
    

    <h1>Settings > Webhooks > Add webhook</h1>

    <h1>Payload URL: https://argocd.yourdomain.com/api/webhook</h1>

    <h1>Content type: application/json</h1>

    <h1>Events: Push events only</h1>

    ---

    Rollback Procedures

    GitOps makes rollback trivial because every deployment is a Git commit. Rolling back means reverting to a previous commit.

    <h1>Option 1: Git revert (creates a new forward commit)</h1>
    

    git revert HEAD

    git push origin main

    <h1>Option 2: ArgoCD CLI rollback to previous sync</h1>

    argocd app rollback my-app-production

    <h1>Option 3: ArgoCD UI</h1>

    <h1>Applications > my-app-production > History > Select revision > Rollback</h1>

    Git revert is preferred because it maintains a clean audit trail. ArgoCD rollback is faster (immediate) but requires manual Git cleanup afterward to prevent the next sync from re-deploying the broken version.

    ---

    Monitoring ArgoCD Health

    <h1>Check application sync status</h1>
    

    argocd app list

    <h1>Get detailed sync status and health</h1>

    argocd app get my-app-production

    <h1>View sync history</h1>

    argocd app history my-app-production

    <h1>Check for out-of-sync resources</h1>

    argocd app diff my-app-production

    Set up Prometheus alerts for ArgoCD sync failures. ArgoCD exposes metrics at `/metrics` that Prometheus can scrape. Key metrics to alert on: `argocd_app_info` with `sync_status="OutOfSync"` and `health_status="Degraded"`.

    For setting up the complete monitoring stack, see our VPS monitoring and alerting guide. If you need canary deployments alongside GitOps, check our canary deployment best practices. Teams building their first CI pipeline should start with our CI/CD pipeline guide for startups.

    ---

    Frequently Asked Questions

    How many repositories should a small team use for GitOps?

    Start with a monorepo containing both application code and Kubernetes manifests in separate directories. A 2-5 person team gains no benefit from separate app and config repositories. Split into separate repos only when you have dedicated platform engineers managing infrastructure independently from application developers.

    Should I use Kustomize or Helm with ArgoCD?

    Kustomize for teams that want simplicity and readability. Helm for teams that need templating for many similar services. Kustomize patches are pure YAML with no templating language to learn. Helm requires understanding Go templates, values files, and chart lifecycle. For most small teams deploying 1-10 services, Kustomize is sufficient and easier to debug.

    How do I handle secrets in GitOps?

    Use Sealed Secrets (Bitnami) or External Secrets Operator. Sealed Secrets encrypts secrets with a cluster-specific key so encrypted values can be committed to Git safely. External Secrets Operator fetches secrets from AWS Secrets Manager, HashiCorp Vault, or similar services at runtime. Never commit plain Kubernetes Secret manifests to Git.

    What if ArgoCD goes down?

    Your running applications are unaffected because they run independently of ArgoCD. ArgoCD is a control plane component, not a data plane component. When ArgoCD recovers, it reconciles the current state against the Git repository and applies any pending changes. For high availability, run ArgoCD with 3+ replicas across availability zones.

    How do I handle database migrations with GitOps?

    Run migrations as Kubernetes Jobs triggered before the main Deployment rolls out. Use ArgoCD sync waves to control ordering: wave 0 for migrations, wave 1 for the application Deployment. The migration Job runs to completion, then ArgoCD proceeds to update the application.

    Can I use GitOps without Kubernetes?

    GitOps principles (declarative config, Git as source of truth, automated reconciliation) apply to any infrastructure. Tools like Terraform with Atlantis provide GitOps workflows for cloud resources. For VPS deployments, you can implement a lightweight GitOps pattern using webhooks and deploy scripts as described in our one-click GitHub deploy guide.