Home/Engineer/Git / CI·CD/GitOps in Practice — Fully A…
Git / CI·CDAdvancedUbuntumacOSGitOpsArgoCDKubernetes

GitOps in Practice — Fully Automating Kubernetes Deployments with Argo CD

An advanced guide to configuring Argo CD according to GitOps principles and fully automating multi-environment Kubernetes deployments as code with ApplicationSet, Sync Waves, and Rollouts.

Contents
  1. What Is GitOps
  2. 1. Installing Argo CD
  3. 2. Designing the Repository Structure
  4. 3. ApplicationSet — Automatic Multi-Environment Deployment
  5. 4. Sync Waves — Controlling Deployment Order
  6. 5. Argo Rollouts — Canary and Blue-Green Deployments
  7. 6. Connecting CI to GitOps — Automatic Image Tag Updates

What Is GitOps

GitOps is an operations paradigm that treats a Git repository as the single source of truth for infrastructure and applications, and automatically reconciles cluster state with repository state.

CODE
Developer → git push → GitHub ──► Argo CD (detects) ──► Kubernetes cluster
                                    ↑ continuously syncs state

1. Installing Argo CD

Bash
kubectl create namespace argocd

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

# Install the Argo CD CLI
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
chmod +x argocd && sudo mv argocd /usr/local/bin

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

# Access the UI via port-forward
kubectl port-forward svc/argocd-server -n argocd 8080:443

# CLI login
argocd login localhost:8080 --username admin --password <password> --insecure

Exposing Argo CD Externally with Ingress (Production)

YAML
# argocd-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-ingress
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  ingressClassName: nginx
  rules:
    - host: argocd.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: argocd-server
                port:
                  number: 443

2. Designing the Repository Structure

In GitOps, the standard practice is to separate the application source repository from the infrastructure/deployment repository.

CODE
infra-repo/
├── apps/
│   ├── base/                    # shared base configuration
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   └── kustomization.yaml
│   ├── overlays/
│   │   ├── dev/                 # development overlay
│   │   │   ├── kustomization.yaml
│   │   │   └── patch-replicas.yaml
│   │   ├── staging/
│   │   └── production/
└── argocd/
    ├── projects/
    └── applications/

Multi-Environment Overlays with Kustomize

YAML
# apps/base/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: my-registry.com/api:latest
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
---
# apps/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
patches:
  - path: patch-replicas.yaml
  - path: patch-resources.yaml
images:
  - name: my-registry.com/api
    newTag: v1.5.0    # pin production to a fixed tag
---
# apps/overlays/production/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 5

3. ApplicationSet — Automatic Multi-Environment Deployment

ApplicationSet automatically generates Applications for multiple environments from a single template.

YAML
# argocd/applicationset.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: api-appset
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - env: dev
            cluster: https://kubernetes.default.svc
            namespace: dev
            revision: main
          - env: staging
            cluster: https://staging-cluster.example.com
            namespace: staging
            revision: main
          - env: production
            cluster: https://prod-cluster.example.com
            namespace: production
            revision: v1.5.0   # pin production to a tag
  template:
    metadata:
      name: api-{{env}}
    spec:
      project: default
      source:
        repoURL: https://github.com/myorg/infra-repo
        targetRevision: "{{revision}}"
        path: apps/overlays/{{env}}
      destination:
        server: "{{cluster}}"
        namespace: "{{namespace}}"
      syncPolicy:
        automated:
          prune: true           # automatically prune resources deleted from Git
          selfHeal: true        # automatically revert manual cluster changes
        syncOptions:
          - CreateNamespace=true
          - ServerSideApply=true
Bash
kubectl apply -f argocd/applicationset.yaml

# List generated Applications
argocd app list

# Check sync status
argocd app get api-production

4. Sync Waves — Controlling Deployment Order

Sync Waves let you run deployments in sequence: ConfigMap → DB migration → application.

YAML
# Step 1: create the ConfigMap first
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  annotations:
    argocd.argoproj.io/sync-wave: "-2"   # negative: runs first
---
# Step 2: DB migration Job
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
  annotations:
    argocd.argoproj.io/sync-wave: "-1"
    argocd.argoproj.io/hook: Sync
    argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: my-registry.com/api:v1.5.0
          command: ["python", "manage.py", "migrate"]
---
# Step 3: the actual application (default: wave 0)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  annotations:
    argocd.argoproj.io/sync-wave: "0"

5. Argo Rollouts — Canary and Blue-Green Deployments

Bash
# Install Argo Rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

# kubectl plugin
curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x kubectl-argo-rollouts-linux-amd64 && sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
YAML
# rollout.yaml — canary strategy
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: api
spec:
  replicas: 10
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: my-registry.com/api:v1.5.0
  strategy:
    canary:
      steps:
        - setWeight: 10       # send 10% of traffic to the new version
        - pause: {duration: 5m}
        - analysis:
            templates:
              - templateName: success-rate   # error-rate analysis
        - setWeight: 50
        - pause: {duration: 10m}
        - setWeight: 100
      canaryService: api-canary
      stableService: api-stable
      trafficRouting:
        nginx:
          stableIngress: api-ingress
---
# analysis template
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  metrics:
    - name: success-rate
      interval: 1m
      successCondition: result[0] >= 0.95
      failureLimit: 3
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(http_requests_total{status!~"5.."}[5m]))
            /
            sum(rate(http_requests_total[5m]))
Bash
# Start the rollout
kubectl apply -f rollout.yaml

# Watch progress in real time
kubectl argo rollouts get rollout api --watch

# Manually promote to the next step
kubectl argo rollouts promote api

# Abort and roll back immediately if something goes wrong
kubectl argo rollouts abort api
kubectl argo rollouts undo api

6. Connecting CI to GitOps — Automatic Image Tag Updates

YAML
# .github/workflows/deploy.yml
name: Build and Update Manifest

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build and push image
        run: |
          docker build -t my-registry.com/api:${{ github.sha }} .
          docker push my-registry.com/api:${{ github.sha }}

      - name: Update infra-repo image tag
        run: |
          git clone https://x-access-token:${{ secrets.INFRA_TOKEN }}@github.com/myorg/infra-repo.git
          cd infra-repo
          # update the image tag with kustomize
          cd apps/overlays/staging
          kustomize edit set image my-registry.com/api=my-registry.com/api:${{ github.sha }}
          git config user.email "ci@myorg.com"
          git config user.name "CI Bot"
          git add -A
          git commit -m "chore: update api to ${{ github.sha }}"
          git push
      # Argo CD detects the infra-repo change and deploys automatically
#GitOps#ArgoCD#Kubernetes#ApplicationSet#Rollout#DevOps
Editorial note

AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us

Topic-related official docs (auto-linked · not per-claim sources)Kubernetes documentation ↗Git documentation ↗

Questions & answers

Ask a question about this guide. We'll review and reply.