GitOps takes “Infrastructure as Code” literally: your Git repository becomes the single source of truth for what should be running. ArgoCD watches your repo and automatically synchronizes your cluster to match. No more kubectl apply from laptops, no more “what’s actually deployed?” mysteries.

GitOps Principles

  1. Declarative: Describe the desired state, not the steps to get there
  2. Versioned: All changes go through Git (audit trail, rollback)
  3. Automated: Changes are applied automatically when Git changes
  4. Self-healing: Drift from desired state is automatically corrected

Installing ArgoCD

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Create namespace
kubectl create namespace argocd

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

# Wait for pods
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=300s

# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d

# Port forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

Access the UI at https://localhost:8080 with username admin.

Repository Structure

Organize your GitOps repo for clarity:

gitopabispavn-psefrserre/ps/awlpsannrprtpeartsaebooaibyoatmta/dg//sdgrewcuawwiawwdskdsk/ukrikruso.cpeonpeoeeueeucuenuecprytibrgibrprsprstspgsptakai..k/..klvtlvtitl/tluc-moyyeyyeoiooioooioireplnaaraarycmycmnmcmceso/mm.mm.meimei/iaia/.lllyllye.ze.zz-z-yiaanyanyaapapacmmtattattatamill.mi.miititleyloyloococsanannhnh.m.m.....ylylyyyyyaaaaaaammmmmmml##ll#llll#ASECphnlpavulrisiertcdoeanrtKm-iuelobnenetvr-ednsleepfteriecensisifotmiuiacroncniesfvseesrtrsides

Creating an Application

Simple Application

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# apps/production/api.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: api-production
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-repo.git
    targetRevision: main
    path: overlays/production/api
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true        # Delete resources removed from Git
      selfHeal: true     # Revert manual changes
    syncOptions:
    - CreateNamespace=true

With Kustomize

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# base/api/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- deployment.yaml
- service.yaml
- configmap.yaml

commonLabels:
  app: api
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# base/api/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: myregistry/api:latest
        ports:
        - containerPort: 8080
        envFrom:
        - configMapRef:
            name: api-config
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../../base/api

replicas:
- name: api
  count: 5

images:
- name: myregistry/api
  newTag: v1.2.3

patchesStrategicMerge:
- resources-patch.yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# overlays/production/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  template:
    spec:
      containers:
      - name: api
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

Helm Integration

ArgoCD works seamlessly with Helm charts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: redis
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://charts.bitnami.com/bitnami
    chart: redis
    targetRevision: 17.0.0
    helm:
      values: |
        architecture: standalone
        auth:
          enabled: true
          password: ${REDIS_PASSWORD}
        master:
          persistence:
            size: 8Gi
  destination:
    server: https://kubernetes.default.svc
    namespace: production

App of Apps Pattern

Manage multiple applications with a single parent:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# apps/root.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-repo.git
    targetRevision: main
    path: apps/production
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Now all YAML files in apps/production/ are automatically managed.

Secrets Management

Don’t commit secrets to Git. Options:

Sealed Secrets

1
2
3
4
5
6
7
8
# Install kubeseal
brew install kubeseal

# Encrypt a secret
kubectl create secret generic api-secrets \
  --from-literal=db-password=supersecret \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-secret.yaml
1
2
3
4
5
6
7
8
# sealed-secret.yaml (safe to commit)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: api-secrets
spec:
  encryptedData:
    db-password: AgBy8hCi...encrypted...

External Secrets Operator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: api-secrets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: api-secrets
  data:
  - secretKey: db-password
    remoteRef:
      key: production/api/database
      property: password

CI/CD Integration

Your CI pipeline updates the GitOps repo, ArgoCD handles deployment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Build and push image
      run: |
        docker build -t myregistry/api:${{ github.sha }} .
        docker push myregistry/api:${{ github.sha }}
    
    - name: Update GitOps repo
      run: |
        git clone https://github.com/myorg/gitops-repo.git
        cd gitops-repo
        
        # Update image tag
        cd overlays/production
        kustomize edit set image myregistry/api:${{ github.sha }}
        
        git config user.name "GitHub Actions"
        git config user.email "actions@github.com"
        git commit -am "Deploy api:${{ github.sha }}"
        git push

Rollback

Rolling back is just reverting a Git commit:

1
2
3
4
5
6
7
8
# Find the commit to revert to
git log --oneline overlays/production/

# Revert
git revert HEAD
git push

# ArgoCD automatically syncs to the reverted state

Or use the ArgoCD CLI:

1
2
3
4
5
# List application history
argocd app history api-production

# Rollback to specific revision
argocd app rollback api-production 3

Monitoring Sync Status

1
2
3
4
5
6
7
8
# Check application status
argocd app get api-production

# Watch for changes
argocd app wait api-production --health

# List all applications
argocd app list

Prometheus metrics are available at /metrics:

1
2
3
4
# Useful metrics
argocd_app_info{sync_status="Synced", health_status="Healthy"}
argocd_app_sync_total{status="Succeeded"}
argocd_app_reconcile_duration_seconds

Best Practices

  1. One repo per team/domain — avoid monolithic GitOps repos
  2. Separate app config from app code — different repos, different lifecycles
  3. Use Kustomize or Helm — don’t copy-paste YAML across environments
  4. Require PRs for production — branch protection + review
  5. Enable auto-sync with self-heal — let ArgoCD maintain desired state
  6. Monitor sync failures — alert when apps are OutOfSync
  7. Use ApplicationSets — generate apps dynamically for multi-cluster/multi-tenant

GitOps transforms deployment from an imperative action (“deploy this”) to a declarative state (“this should be deployed”). The result is auditable, reproducible, and self-healing infrastructure.