# Implement GitOps with ArgoCD on Kubernetes

Introduction

GitOps is a modern approach to continuous delivery where Git is the single source of truth for your infrastructure and application definitions. ArgoCD is the most popular GitOps tool for Kubernetes, automating deployments by watching Git repositories and syncing cluster state to match.

In this tutorial, you'll build a complete GitOps pipeline: from installing ArgoCD to deploying multi-environment applications with automated sync, health monitoring, and secret management.

Prerequisites

  • A Kubernetes cluster (minikube, kind, or cloud-managed)
  • kubectl configured
  • helm v3 installed
  • A Git repository (GitHub/GitLab)
  • Basic Kubernetes knowledge
  • Step 1: Install ArgoCD

    First, install ArgoCD into your cluster:

    # 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 to be ready

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

    # Get the initial admin password

    argocd_password=$(kubectl -n argocd get secret argocd-initial-admin-secret \

    -o jsonpath="{.data.password}" | base64 -d)

    echo "ArgoCD Admin Password: $argocd_password"

    Expose the ArgoCD server:

    # Port forward for local access
    

    kubectl port-forward svc/argocd-server -n argocd 8080:443 &

    # Install the ArgoCD 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/

    # Login

    argocd login localhost:8080 --username admin --password $argocd_password --insecure

    Step 2: Set Up the GitOps Repository Structure

    Create a well-organized Git repository:

    gitops-demo/
    

    ├── apps/ # ArgoCD Application definitions

    │ ├── dev/

    │ │ └── web-app.yaml

    │ ├── staging/

    │ │ └── web-app.yaml

    │ └── production/

    │ └── web-app.yaml

    ├── base/ # Base Kubernetes manifests

    │ ├── deployment.yaml

    │ ├── service.yaml

    │ ├── ingress.yaml

    │ └── kustomization.yaml

    ├── overlays/ # Environment-specific patches

    │ ├── dev/

    │ │ ├── kustomization.yaml

    │ │ └── patch-replicas.yaml

    │ ├── staging/

    │ │ ├── kustomization.yaml

    │ │ └── patch-replicas.yaml

    │ └── production/

    │ ├── kustomization.yaml

    │ ├── patch-replicas.yaml

    │ └── patch-resources.yaml

    └── sealed-secrets/

    └── web-app-secrets.yaml

    Step 3: Create Base Kubernetes Manifests

    base/deployment.yaml

    apiVersion: apps/v1
    

    kind: Deployment

    metadata:

    name: web-app

    labels:

    app: web-app

    spec:

    replicas: 1

    selector:

    matchLabels:

    app: web-app

    template:

    metadata:

    labels:

    app: web-app

    spec:

    containers:

    - name: web-app

    image: nginx:1.25-alpine

    ports:

    - containerPort: 80

    livenessProbe:

    httpGet:

    path: /healthz

    port: 80

    initialDelaySeconds: 10

    periodSeconds: 10

    readinessProbe:

    httpGet:

    path: /ready

    port: 80

    initialDelaySeconds: 5

    periodSeconds: 5

    resources:

    requests:

    cpu: 100m

    memory: 128Mi

    limits:

    cpu: 250m

    memory: 256Mi

    envFrom:

    - secretRef:

    name: web-app-secrets

    optional: true

    base/service.yaml

    apiVersion: v1
    

    kind: Service

    metadata:

    name: web-app

    spec:

    type: ClusterIP

    selector:

    app: web-app

    ports:

    - port: 80

    targetPort: 80

    protocol: TCP

    base/kustomization.yaml

    apiVersion: kustomize.config.k8s.io/v1beta1
    

    kind: Kustomization

    resources:

    - deployment.yaml

    - service.yaml

    - ingress.yaml

    Step 4: Create Environment Overlays

    overlays/dev/kustomization.yaml

    apiVersion: kustomize.config.k8s.io/v1beta1
    

    kind: Kustomization

    namespace: dev

    namePrefix: dev-

    bases:

    - ../../base

    patchesStrategicMerge:

    - patch-replicas.yaml

    labels:

    - pairs:

    environment: dev

    overlays/dev/patch-replicas.yaml

    apiVersion: apps/v1
    

    kind: Deployment

    metadata:

    name: web-app

    spec:

    replicas: 1

    overlays/production/kustomization.yaml

    apiVersion: kustomize.config.k8s.io/v1beta1
    

    kind: Kustomization

    namespace: production

    namePrefix: prod-

    bases:

    - ../../base

    patchesStrategicMerge:

    - patch-replicas.yaml

    - patch-resources.yaml

    labels:

    - pairs:

    environment: production

    overlays/production/patch-resources.yaml

    apiVersion: apps/v1
    

    kind: Deployment

    metadata:

    name: web-app

    spec:

    template:

    spec:

    containers:

    - name: web-app

    resources:

    requests:

    cpu: 500m

    memory: 512Mi

    limits:

    cpu: "1"

    memory: 1Gi

    replicas: 3

    strategy:

    rollingUpdate:

    maxSurge: 1

    maxUnavailable: 0

    Step 5: Define ArgoCD Applications

    apps/dev/web-app.yaml

    apiVersion: argoproj.io/v1alpha1
    

    kind: Application

    metadata:

    name: web-app-dev

    namespace: argocd

    finalizers:

    - resources-finalizer.argocd.argoproj.io

    spec:

    project: default

    source:

    repoURL: https://github.com/your-org/gitops-demo.git

    targetRevision: main

    path: overlays/dev

    destination:

    server: https://kubernetes.default.svc

    namespace: dev

    syncPolicy:

    automated:

    prune: true

    selfHeal: true

    allowEmpty: false

    syncOptions:

    - CreateNamespace=true

    - PrunePropagationPolicy=foreground

    - PruneLast=true

    retry:

    limit: 5

    backoff:

    duration: 5s

    factor: 2

    maxDuration: 3m

    apps/production/web-app.yaml

    apiVersion: argoproj.io/v1alpha1
    

    kind: Application

    metadata:

    name: web-app-production

    namespace: argocd

    finalizers:

    - resources-finalizer.argocd.argoproj.io

    spec:

    project: default

    source:

    repoURL: https://github.com/your-org/gitops-demo.git

    targetRevision: main

    path: overlays/production

    destination:

    server: https://kubernetes.default.svc

    namespace: production

    syncPolicy:

    # Manual sync for production — no automated block

    syncOptions:

    - CreateNamespace=true

    - PrunePropagationPolicy=foreground

    Apply the applications:

    # Create environments
    

    kubectl apply -f apps/dev/web-app.yaml

    kubectl apply -f apps/production/web-app.yaml

    # Check status

    argocd app list

    argocd app get web-app-dev

    Step 6: Manage Secrets with Sealed Secrets

    Plain Secrets in Git are a security risk. Use Sealed Secrets:

    # Install Sealed Secrets controller
    

    helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets

    helm install sealed-secrets sealed-secrets/sealed-secrets -n kube-system

    # Install kubeseal CLI

    KUBESEAL_VERSION=$(curl -s https://api.github.com/repos/bitnami-labs/sealed-secrets/releases/latest | grep tag_name | cut -d '"' -f4 | cut -d 'v' -f2)

    curl -OL "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"

    tar -xvzf kubeseal-*.tar.gz kubeseal

    sudo install -m 755 kubeseal /usr/local/bin/kubeseal

    Create and seal a secret:

    # Create a regular secret (don't commit this!)
    

    kubectl create secret generic web-app-secrets \

    --from-literal=DATABASE_URL='postgresql://user:pass@db:5432/app' \

    --from-literal=API_KEY='sk-your-api-key-here' \

    --dry-run=client -o yaml > /tmp/secret.yaml

    # Seal it (safe to commit)

    kubeseal --format yaml < /tmp/secret.yaml > sealed-secrets/web-app-secrets.yaml

    # Clean up the plaintext

    rm /tmp/secret.yaml

    # The sealed secret can be safely committed to Git

    cat sealed-secrets/web-app-secrets.yaml

    Step 7: Implement App of Apps Pattern

    Manage all applications with a single root application:

    # apps/root-app.yaml
    

    apiVersion: argoproj.io/v1alpha1

    kind: Application

    metadata:

    name: root-app

    namespace: argocd

    spec:

    project: default

    source:

    repoURL: https://github.com/your-org/gitops-demo.git

    targetRevision: main

    path: apps

    directory:

    recurse: true

    include: '*.yaml'

    destination:

    server: https://kubernetes.default.svc

    syncPolicy:

    automated:

    prune: true

    selfHeal: true

    kubectl apply -f apps/root-app.yaml
    

    Step 8: Configure Notifications

    Set up Slack notifications for deployment events:

    # argocd-notifications-cm.yaml
    

    apiVersion: v1

    kind: ConfigMap

    metadata:

    name: argocd-notifications-cm

    namespace: argocd

    data:

    service.slack: |

    token: $slack-token

    trigger.on-sync-succeeded: |

    - when: app.status.operationState.phase in ['Succeeded']

    send: [app-sync-succeeded]

    trigger.on-sync-failed: |

    - when: app.status.operationState.phase in ['Error', 'Failed']

    send: [app-sync-failed]

    template.app-sync-succeeded: |

    message: |

    ✅ Application {{.app.metadata.name}} synced successfully.

    Revision: {{.app.status.sync.revision}}

    Environment: {{.app.spec.destination.namespace}}

    template.app-sync-failed: |

    message: |

    ❌ Application {{.app.metadata.name}} sync failed!

    Revision: {{.app.status.sync.revision}}

    Error: {{.app.status.operationState.message}}

    Annotate applications for notifications:

    kubectl patch app web-app-production -n argocd --type merge -p '
    

    {"metadata": {"annotations": {

    "notifications.argoproj.io/subscribe.on-sync-succeeded.slack": "deployments",

    "notifications.argoproj.io/subscribe.on-sync-failed.slack": "deployments"

    }}}'

    Step 9: Implement Progressive Delivery with Argo Rollouts

    Replace standard Deployments with Rollouts for canary deployments:

    # Install Argo Rollouts
    

    kubectl create namespace argo-rollouts

    kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

    # base/rollout.yaml
    

    apiVersion: argoproj.io/v1alpha1

    kind: Rollout

    metadata:

    name: web-app

    spec:

    replicas: 3

    selector:

    matchLabels:

    app: web-app

    template:

    metadata:

    labels:

    app: web-app

    spec:

    containers:

    - name: web-app

    image: nginx:1.25-alpine

    ports:

    - containerPort: 80

    strategy:

    canary:

    steps:

    - setWeight: 10

    - pause: { duration: 2m }

    - setWeight: 30

    - pause: { duration: 2m }

    - setWeight: 60

    - pause: { duration: 2m }

    - setWeight: 100

    canaryService: web-app-canary

    stableService: web-app-stable

    trafficRouting:

    nginx:

    stableIngress: web-app-ingress

    Monitor rollout progress:

    # Install kubectl plugin
    

    kubectl argo rollouts dashboard &

    # Watch rollout

    kubectl argo rollouts get rollout web-app -n production --watch

    # Promote or abort

    kubectl argo rollouts promote web-app -n production

    kubectl argo rollouts abort web-app -n production

    Step 10: Build the Promotion Pipeline

    Automate environment promotion with a GitHub Actions workflow:

    # .github/workflows/promote.yaml
    

    name: Promote to Production

    on:

    workflow_dispatch:

    inputs:

    image_tag:

    description: 'Image tag to promote'

    required: true

    jobs:

    promote:

    runs-on: ubuntu-latest

    steps:

    - uses: actions/checkout@v4

    - name: Update production image

    run: |

    cd overlays/production

    kustomize edit set image nginx=your-registry/web-app:${{ inputs.image_tag }}

    - name: Commit and push

    run: |

    git config user.name "GitHub Actions"

    git config user.email "actions@github.com"

    git add .

    git commit -m "promote: web-app ${{ inputs.image_tag }} to production"

    git push

    - name: Wait for ArgoCD sync

    run: |

    argocd app wait web-app-production --timeout 300

    argocd app get web-app-production

    Useful ArgoCD Commands

    # List all applications
    

    argocd app list

    # Sync an application manually

    argocd app sync web-app-production

    # View application diff (what would change)

    argocd app diff web-app-production

    # Rollback to a previous revision

    argocd app rollback web-app-production 3

    # View application history

    argocd app history web-app-production

    # Hard refresh (clear cache)

    argocd app get web-app-production --hard-refresh

    Summary

    You've built a production-ready GitOps pipeline with:

  • ✅ ArgoCD installation and configuration
  • ✅ Kustomize-based multi-environment setup
  • ✅ Automated sync for dev, manual approval for production
  • ✅ Secret management with Sealed Secrets
  • ✅ App of Apps pattern for scalability
  • ✅ Slack notifications for deployment events
  • ✅ Canary deployments with Argo Rollouts
  • ✅ Environment promotion pipeline

GitOps ensures your cluster state always matches what's in Git — auditable, reproducible, and recoverable. Combined with ArgoCD's powerful UI and Argo Rollouts' progressive delivery, you have a world-class deployment platform.