Last updated: March 22, 2026

How to Set Up ArgoCD for GitOps Workflows

GitOps means your Git repository is the single source of truth for cluster state. You make changes by committing to Git, not by running kubectl apply. ArgoCD watches your repo and applies any diff between Git state and cluster state automatically. For remote teams, this eliminates SSH access for deployments, creates an audit trail in Git history, and lets anyone verify what’s deployed by reading the repo.


Install ArgoCD

kubectl create namespace argocd

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

Wait for ArgoCD to be ready
kubectl rollout status deployment argocd-server -n argocd

Get initial admin password
argocd admin initial-password -n argocd

Expose the UI:

Port-forward for local access
kubectl port-forward svc/argocd-server -n argocd 8080:443

Or expose via Ingress (recommended for teams)
kubectl apply -f - << 'EOF'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: argocd-server-ingress
  namespace: argocd
  annotations:
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
  ingressClassName: nginx
  rules:
    - host: argocd.yourcompany.internal
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: argocd-server
                port:
                  number: 443
  tls:
    - hosts:
        - argocd.yourcompany.internal
      secretName: argocd-tls
EOF

Repository Structure (App of Apps Pattern)

The App of Apps pattern has one “root” ArgoCD Application that manages all other Applications. This makes it easy to add new services. just commit a new Application manifest to the config repo.

gitops-config/
 apps/
    root-app.yaml           -- the App of Apps
    myapp-staging.yaml
    myapp-production.yaml
    monitoring.yaml
    ingress-nginx.yaml
 staging/
    myapp/
        deployment.yaml
        service.yaml
        hpa.yaml
 production/
     myapp/
         deployment.yaml
         service.yaml
         hpa.yaml

apps/root-app.yaml. bootstrap this once with kubectl apply:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: https://github.com/yourorg/gitops-config.git
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
kubectl apply -f apps/root-app.yaml

Application Manifests

apps/myapp-staging.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-staging
  namespace: argocd
  annotations:
    notifications.argoproj.io/subscribe.on-sync-succeeded.slack: deployments
    notifications.argoproj.io/subscribe.on-sync-failed.slack: ops-alerts
spec:
  project: default
  source:
    repoURL: https://github.com/yourorg/gitops-config.git
    targetRevision: main
    path: staging/myapp
  destination:
    server: https://kubernetes.default.svc
    namespace: staging
  syncPolicy:
    automated:
      prune: true      # delete resources removed from Git
      selfHeal: true   # revert manual kubectl changes
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
    retry:
      limit: 3
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

apps/myapp-production.yaml. manual sync only for production:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-production
  namespace: argocd
spec:
  project: production
  source:
    repoURL: https://github.com/yourorg/gitops-config.git
    targetRevision: main
    path: production/myapp
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    # No automated sync. require human approval via UI or CLI
    syncOptions:
      - CreateNamespace=true

RBAC Configuration

Control who can sync which applications:

argocd-rbac-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
    # Developers can sync staging, read production
    p, role:developer, applications, sync, */myapp-staging, allow
    p, role:developer, applications, get, */*, allow

    # SREs can sync everything
    p, role:sre, applications, *, */*, allow
    p, role:sre, clusters, *, *, allow

    # Assign roles to groups (via SSO group claim)
    g, yourorg:engineering, role:developer
    g, yourorg:sre, role:sre
    g, yourorg:admin, role:admin

SSO with GitHub

Configure GitHub OAuth for team authentication:

argocd-cm ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  url: https://argocd.yourcompany.internal
  dex.config: |
    connectors:
      - type: github
        id: github
        name: GitHub
        config:
          clientID: $GITHUB_CLIENT_ID
          clientSecret: $GITHUB_CLIENT_SECRET
          orgs:
            - name: yourorg
              teams:
                - engineering
                - sre

Deploying a New Version

The GitOps deployment flow:

1. Build and push the new image in CI
IMAGE="ghcr.io/yourorg/myapp:$(git rev-parse --short HEAD)"
docker build -t "$IMAGE" .
docker push "$IMAGE"

2. Update the image tag in the GitOps repo
cd gitops-config
sed -i "s|image: ghcr.io/yourorg/myapp:.*|image: $IMAGE|" \
  staging/myapp/deployment.yaml

git add staging/myapp/deployment.yaml
git commit -m "chore: deploy myapp $(git -C ../myapp rev-parse --short HEAD) to staging"
git push origin main

3. ArgoCD detects the change and syncs automatically (staging)
For production, trigger sync manually:
argocd app sync myapp-production --auth-token "$ARGOCD_TOKEN"
argocd app wait myapp-production --health --timeout 300

Multi-Cluster Setup

For organizations with multiple clusters (staging on one cloud, production on another), ArgoCD can manage them all from a single control plane:

Add a remote cluster to ArgoCD
First, get the cluster's context name from kubeconfig
kubectl config get-contexts

Add the cluster (ArgoCD creates a ServiceAccount with cluster-admin in the remote cluster)
argocd cluster add production-eks-cluster --name production-eks

Verify
argocd cluster list
NAME                     SERVER                                    STATUS
in-cluster               https://kubernetes.default.svc            Successful
production-eks           https://123456789.gr7.us-east-1.eks.amazonaws.com  Successful

Now reference the remote cluster in your Application manifests:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-production
  namespace: argocd
spec:
  destination:
    server: https://123456789.gr7.us-east-1.eks.amazonaws.com  # remote cluster
    namespace: production
  source:
    repoURL: https://github.com/yourorg/gitops-config.git
    path: production/myapp
    targetRevision: main

Notifications Setup

ArgoCD Notifications send alerts to Slack, PagerDuty, or email when sync succeeds, fails, or a health check degrades:

Install ArgoCD Notifications
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/stable/manifests/install.yaml

Configure Slack in the argocd-notifications-cm ConfigMap:

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token
  template.app-deployed: |
    message: |
      Application {{.app.metadata.name}} deployed to {{.app.spec.destination.namespace}}.
      Revision: {{.app.status.sync.revision}}
  trigger.on-deployed: |
    - description: Application deployed
      send:
      - app-deployed
      when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy'
  trigger.on-sync-failed: |
    - description: Sync failed
      send:
      - app-sync-failed
      when: app.status.operationState.phase in ['Error', 'Failed']

Subscribe applications to notifications via annotations (already shown in the Application manifests above with notifications.argoproj.io/subscribe.*).


ArgoCD CLI Cheatsheet

Install CLI
brew install argocd

Login
argocd login argocd.yourcompany.internal --sso

List applications
argocd app list

Check app health and sync status
argocd app get myapp-staging

Manually sync
argocd app sync myapp-production

View diff between Git and cluster
argocd app diff myapp-production

Roll back to previous version
argocd app rollback myapp-production

Force sync with pruning
argocd app sync myapp-staging --prune --force

Related Reading


Built by theluckystrike. More at zovo.one