Introduction
GitOps is a deployment model where Git is the single source of truth, and an operator continuously reconciles your cluster state with what's in the repository. ArgoCD is the most popular GitOps tool in the CNCF ecosystem — and for good reason.
But deploying ArgoCD in production requires more than kubectl apply. You need a strategy for organizing applications, managing secrets, handling multi-cluster deployments, and implementing progressive delivery.
Installation
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl port-forward svc/argocd-server -n argocd 8080:443
Retrieve the initial admin password from the argocd-initial-admin-secret in the argocd namespace.
The App of Apps Pattern
The App of Apps pattern is the foundation of scalable ArgoCD deployments. Instead of manually creating individual Application resources, you create one "root" Application that manages other Applications:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/org/infrastructure
targetRevision: main
path: apps/
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
The apps/ directory contains subdirectories, each with its own Application manifest. Adding a new service is a pull request to the infrastructure repo — no ArgoCD CLI or UI required.
Multi-Cluster Management
ArgoCD can manage multiple clusters from a single control plane. Add clusters via the CLI:
argocd cluster add prod-cluster --name production
argocd cluster add staging-cluster --name staging
Application destinations specify which cluster to deploy to:
spec:
destination:
name: production
namespace: payment-service
Secret Management with External Secrets Operator
Never store plaintext secrets in Git — even in private repositories. The External Secrets Operator (ESO) syncs secrets from AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault into Kubernetes Secrets:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: prod/database/password
ArgoCD deploys the ExternalSecret, ESO fetches the actual secret at sync time, and the application never sees sensitive values in Git.
Health Checks and Sync Waves
ArgoCD supports custom health checks and ordered deployments via sync waves:
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1" # Deploy before wave "2"
Use waves for dependencies: deploy CRDs first (wave 0), then operators (wave 1), then applications (wave 2+). Combine with PruneLast=true to ensure resources are deleted in reverse order:
syncPolicy:
syncOptions:
- CreateNamespace=true
- PruneLast=true
Progressive Delivery with Argo Rollouts
ArgoCD integrates with Argo Rollouts for blue-green and canary deployments:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: payment-service
spec:
replicas: 5
strategy:
canary:
steps:
- setWeight: 20
- pause: {duration: 10m}
- setWeight: 40
- pause: {duration: 10m}
- setWeight: 100
template:
spec:
containers:
- name: app
image: payment-service:v2.1.0
ArgoCD manages the Rollout resource as it would a Deployment — Git is still the source of truth, but the rollout process is progressive rather than instant.
Multi-Source Applications (ArgoCD 2.6+)
ArgoCD 2.6 introduced multi-source Applications, allowing a single Application to pull from multiple Git repos or combine Helm charts with overlay configs:
spec:
sources:
- repoURL: https://github.com/org/charts
targetRevision: main
path: helm/payment-service
helm:
valueFiles:
- $values/production/payment-service.yaml
- repoURL: https://github.com/org/config
targetRevision: main
ref: values
This separates application definition (Helm chart, Kustomize base) from environment configuration (values files, overlays) — different teams can own each repo independently.
Disaster Recovery
Back up Applications and their sync status:
kubectl get applications.argoproj.io -A -o yaml > argocd-backup.yaml
kubectl get configmap argocd-cm -n argocd -o yaml > argocd-cm.yaml
In a disaster, reinstall ArgoCD, restore the configmaps, and apply the Application YAML. ArgoCD will reconcile everything back to the desired state.
Why This Matters for Platform Engineering
ArgoCD is the deployment backbone of any Internal Developer Platform. When platform teams build self-service portals with Backstage or Port, ArgoCD is the engine that actually deploys the code. The App of Apps pattern, combined with External Secrets Operator and Argo Rollouts, gives developers a "push to deploy" experience without granting them direct kubectl access.
For securing your ArgoCD deployment — RBAC, SSO, and network policies — see our Kubernetes security best practices guide.
The goal isn't just GitOps — it's making deployments boring, predictable, and reversible.
Image Updater: Automate Container Updates
ArgoCD Image Updater monitors container registries and automatically updates Application manifests when new images are pushed:
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yaml
Annotate your Application to enable auto-updates:
metadata:
annotations:
argocd-image-updater.argoproj.io/image-list: myimage=org/payment-service
argocd-image-updater.argoproj.io/myimage.update-strategy: semver
argocd-image-updater.argoproj.io/write-back-method: git
With write-back-method: git, the Image Updater commits the new image tag to your Git repository — maintaining the GitOps principle that Git is the single source of truth. ArgoCD then picks up the commit and deploys the new version.
RBAC: Who Can Deploy What
ArgoCD's RBAC model maps SSO groups to project-level permissions:
# argocd-rbac-cm ConfigMap
data:
policy.csv: |
p, role:developer, applications, sync, */*, allow
p, role:developer, applications, get, */*, allow
g, org:frontend-team, role:developer
g, org:backend-team, role:developer
Integrate with Dex or an OIDC provider (Okta, Azure AD, Google Workspace) for SSO:
# argocd-cm ConfigMap
data:
oidc.config: |
name: Okta
issuer: https://company.okta.com
clientID: argocd
clientSecret: $oidc.okta.clientSecret
requestedScopes: ["openid", "profile", "groups"]
This gives every developer a read-only view of their team's applications and the ability to trigger syncs — without kubectl access to production.
Notifications: Alert on Sync Failures
ArgoCD Notifications sends alerts to Slack, email, or webhooks when syncs fail or deployments succeed:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
notifications.argoproj.io/subscribe.on-sync-failed.slack: "#sre-alerts"
notifications.argoproj.io/subscribe.on-deployed.slack: "#deployments"
Configure the Slack webhook in the argocd-notifications-cm ConfigMap. Now every failed sync pings the SRE channel with the Application name, commit hash, and error message — no need to watch the ArgoCD UI.
Testing GitOps Changes Before Merge
Use ArgoCD's --dry-run and application diff to preview changes locally:
argocd app diff payment-service --local /path/to/repo
This shows exactly which Kubernetes resources will be created, modified, or deleted — before you open a pull request. Integrate this into your CI pipeline to fail PRs that would cause cluster drift:
# GitHub Actions workflow
- name: ArgoCD Dry Run
run: |
argocd app diff payment-service --local . --exit-code
If the diff shows any unexpected changes, the pipeline fails and the PR is blocked from merging.
Performance Tuning for Large Clusters
In clusters with 500+ Applications, ArgoCD's default reconciliation interval (3 minutes) can cause CPU spikes. Tune it:
# argocd-cm ConfigMap
data:
timeout.reconciliation: 180s
controller.status.processors: "20"
controller.operation.processors: "10"
For Git repositories with large histories, enable shallow cloning:
spec:
source:
repoURL: https://github.com/org/monorepo
targetRevision: main
path: apps/payment
sourceRepos:
shallowClone:
enabled: true
This reduces clone time from minutes to seconds for repos with thousands of commits.