What is GitOps and Why ArgoCD?
GitOps is an operational framework that takes DevOps best practices used for application development — version control, collaboration, compliance, CI/CD — and applies them to infrastructure automation. In a GitOps workflow, Git becomes the single source of truth for your desired system state, and an automated process ensures your live environment always matches what's declared in your repository.
Core GitOps Principles
Why ArgoCD?
ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It monitors your Git repositories and automatically synchronizes application definitions, configurations, and environments to your clusters.
Key advantages:
- Pull-based deployment — No need to expose cluster credentials externally.
- Real-time drift detection — Identifies when live state diverges from Git.
- Multi-cluster support — Manage dozens of clusters from one instance.
- Rich UI — Visualize app topology, sync status, and health.
- Declarative app management — Applications defined as Kubernetes CRDs.
- Built-in rollback — Roll back to any synced Git commit instantly.
---
ArgoCD Architecture
Understanding ArgoCD's internal components helps you troubleshoot, scale, and secure your deployment.
Core Components
API Server — Exposes the gRPC/REST API consumed by the Web UI, CLI, and CI/CD systems. Handles authentication, RBAC enforcement, and Git webhook events.
Repo Server — Clones Git repositories, generates Kubernetes manifests from Helm charts, Kustomize overlays, or plain YAML, and caches results. Stateless and horizontally scalable.
Application Controller — The reconciliation engine. Continuously monitors running applications, compares live state against desired state in Git, and takes corrective action based on configured policies.
Redis — Caching layer and backing store for real-time UI event streams.
Dex (optional) — OpenID Connect identity provider for SSO integration.
---
Installation Methods
Method 1: Install with kubectl (Quick Start)
# Create the namespace
kubectl create namespace argocd
# Install ArgoCD (stable release)
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Get the initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
Method 2: Install with Helm (Production Recommended)
# values-argocd.yaml
global:
image:
tag: "v2.12.0"
server:
replicas: 2
ingress:
enabled: true
ingressClassName: nginx
hosts:
- argocd.example.com
tls:
- secretName: argocd-tls
hosts:
- argocd.example.com
controller:
replicas: 2
metrics:
enabled: true
repoServer:
replicas: 2
metrics:
enabled: true
redis-ha:
enabled: true
Install command:
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
--namespace argocd \
--create-namespace \
--values values-argocd.yaml
---
Application CRD — Defining Apps Declaratively
The Application CRD is the heart of ArgoCD. It declares what to deploy, where, and how to sync.
Basic Application Manifest
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/my-org/my-app.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Key Fields Explained
| Field | Purpose |
|---|---|
| <code class="inline-code">source.repoURL</code> | Git repository containing manifests |
| <code class="inline-code">source.targetRevision</code> | Branch, tag, or commit SHA to track |
| <code class="inline-code">source.path</code> | Directory within the repo |
| <code class="inline-code">destination.server</code> | Target cluster API server URL |
| <code class="inline-code">destination.namespace</code> | Target namespace for resources |
| <code class="inline-code">syncPolicy.automated</code> | Enable auto-sync on Git changes |
| <code class="inline-code">syncPolicy.automated.prune</code> | Delete resources removed from Git |
| <code class="inline-code">syncPolicy.automated.selfHeal</code> | Revert manual changes in cluster |
---
Sync Strategies
ArgoCD offers fine-grained control over how and when applications sync.
Automatic Sync
Deploys automatically when Git changes are detected:
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
Manual Sync
Requires explicit approval — ideal for production:
syncPolicy: {}
Trigger via CLI:
argocd app sync my-app --revision main --prune
Sync Windows
Restrict when syncs can occur:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
namespace: argocd
spec:
syncWindows:
- kind: allow
schedule: "0 6 1-5"
duration: 2h
applications:
- "*"
- kind: deny
schedule: "0 0 0"
duration: 24h
applications:
- "*"
Sync Phases and Hooks
Control deployment ordering with resource hooks:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
containers:
- name: migrate
image: my-app:latest
command: ["./migrate.sh"]
restartPolicy: Never
Available phases: PreSync, Sync, PostSync, SyncFail.
---
App-of-Apps Pattern
The app-of-apps pattern uses a single ArgoCD Application to manage a collection of child Applications. This is the recommended approach for managing multiple services at scale.
Root Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: platform-apps
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/my-org/platform-apps.git
targetRevision: main
path: apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
Child Application (apps/frontend.yaml)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: frontend
namespace: argocd
labels:
team: platform
env: production
spec:
project: default
source:
repoURL: https://github.com/my-org/frontend.git
targetRevision: v2.1.0
path: deploy/production
destination:
server: https://kubernetes.default.svc
namespace: frontend
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
ApplicationSet — Dynamic App Generation
For dynamic environments, ApplicationSet generates Applications from templates:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: microservices
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/my-org/services.git
revision: main
directories:
- path: "services/*"
template:
metadata:
name: "{{path.basename}}"
spec:
project: default
source:
repoURL: https://github.com/my-org/services.git
targetRevision: main
path: "{{path}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{path.basename}}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
This automatically creates an Application for each subdirectory under services/.
---
Secrets Management with ArgoCD
Storing secrets in Git violates security best practices. Here are three production-proven approaches.
Option 1: Sealed Secrets (Bitnami)
Sealed Secrets encrypts secrets client-side so they can be safely stored in Git:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: database-credentials
namespace: my-app
spec:
encryptedData:
DB_PASSWORD: AgBy8hCi...encrypted...base64==
DB_USERNAME: AgA2kF9r...encrypted...base64==
template:
metadata:
name: database-credentials
namespace: my-app
type: Opaque
Generate sealed secrets:
kubeseal --controller-name=sealed-secrets \
--controller-namespace=kube-system \
--format yaml < secret.yaml > sealed-secret.yaml
Option 2: External Secrets Operator
Pulls secrets from external providers (AWS Secrets Manager, Vault, GCP Secret Manager):
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
namespace: my-app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: database-credentials
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD
remoteRef:
key: /production/database
property: password
- secretKey: DB_USERNAME
remoteRef:
key: /production/database
property: username
ClusterSecretStore configuration:
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
namespace: external-secrets
Option 3: HashiCorp Vault with ArgoCD Vault Plugin
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
source:
repoURL: https://github.com/my-org/my-app.git
targetRevision: main
path: manifests
plugin:
name: argocd-vault-plugin
env:
- name: VAULT_ADDR
value: https://vault.example.com
- name: AVP_TYPE
value: vault
- name: AVP_AUTH_TYPE
value: k8s
Secret template with Vault placeholders:
apiVersion: v1
kind: Secret
metadata:
name: database-credentials
annotations:
avp.kubernetes.io/path: "secret/data/production/database"
type: Opaque
stringData:
DB_PASSWORD: <password>
DB_USERNAME: <username>
---
Multi-Cluster Deployment
ArgoCD excels at managing applications across multiple clusters from a single control plane.
Register External Clusters
# Add cluster via CLI
argocd cluster add my-production-cluster \
--name production \
--kubeconfig ~/.kube/production-config
# List registered clusters
argocd cluster list
Declarative Cluster Registration
apiVersion: v1
kind: Secret
metadata:
name: production-cluster
namespace: argocd
labels:
argocd.argoproj.io/secret-type: cluster
type: Opaque
stringData:
name: production
server: https://production-k8s.example.com:6443
config: |
{
"bearerToken": "<service-account-token>",
"tlsClientConfig": {
"insecure": false,
"caData": "<base64-ca-cert>"
}
}
Multi-Cluster ApplicationSet
Deploy the same app across all registered clusters:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: monitoring-stack
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
env: production
template:
metadata:
name: "monitoring-{{name}}"
spec:
project: default
source:
repoURL: https://github.com/my-org/monitoring.git
targetRevision: main
path: base
destination:
server: "{{server}}"
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
---
RBAC and SSO Configuration
RBAC Policy
ArgoCD RBAC uses a Casbin-based model. Configure via ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly
policy.csv: |
# Roles
p, role:developers, applications, get, /, allow
p, role:developers, applications, sync, /, allow
p, role:developers, logs, get, /, allow
p, role:admins, applications, , /*, allow
p, role:admins, clusters, , , allow
p, role:admins, repositories, , , allow
p, role:admins, projects, , , allow
# Group bindings
g, dev-team, role:developers
g, platform-team, role:admins
scopes: "[groups]"
SSO with OIDC (Keycloak Example)
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
url: https://argocd.example.com
oidc.config: |
name: Keycloak
issuer: https://keycloak.example.com/realms/devops
clientID: argocd
clientSecret: $oidc.keycloak.clientSecret
requestedScopes:
- openid
- profile
- email
- groups
requestedIDTokenClaims:
groups:
essential: true
Store the client secret:
apiVersion: v1
kind: Secret
metadata:
name: argocd-secret
namespace: argocd
type: Opaque
stringData:
oidc.keycloak.clientSecret: "your-client-secret-here"
---
ArgoCD + Helm + Kustomize Integration
Helm Source Configuration
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx-ingress
namespace: argocd
spec:
project: default
source:
repoURL: https://kubernetes.github.io/ingress-nginx
chart: ingress-nginx
targetRevision: 4.8.3
helm:
releaseName: ingress-nginx
valuesObject:
controller:
replicaCount: 3
metrics:
enabled: true
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
destination:
server: https://kubernetes.default.svc
namespace: ingress-nginx
syncPolicy:
automated:
prune: true
syncOptions:
- CreateNamespace=true
Kustomize with Patches
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-production
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/my-org/my-app.git
targetRevision: main
path: deploy/overlays/production
kustomize:
namePrefix: prod-
commonLabels:
env: production
images:
- name: my-app
newName: registry.example.com/my-app
newTag: v2.1.0
destination:
server: https://kubernetes.default.svc
namespace: my-app-production
Multi-Source Applications (Helm + Values from Git)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
sources:
- repoURL: https://charts.example.com
chart: my-app
targetRevision: 1.2.3
helm:
valueFiles:
- $values/environments/production/values.yaml
- repoURL: https://github.com/my-org/app-config.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: my-app
---
Rollback Strategies
ArgoCD maintains a history of all synced revisions, making rollbacks straightforward.
CLI Rollback
# View deployment history
argocd app history my-app
# Rollback to a specific revision
argocd app rollback my-app 3
# Rollback to a specific Git commit
argocd app sync my-app --revision abc123def
Automated Rollback with Analysis
Combine with Argo Rollouts for progressive delivery:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
replicas: 5
strategy:
canary:
steps:
- setWeight: 20
- pause: { duration: 5m }
- analysis:
templates:
- templateName: success-rate
- setWeight: 50
- pause: { duration: 5m }
- analysis:
templates:
- templateName: success-rate
- setWeight: 100
analysis:
templates:
- templateName: success-rate
args:
- name: service-name
value: my-app
Analysis Template for Automated Rollback
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
count: 5
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{
service="{{args.service-name}}",
status=~"2.."
}[5m])) /
sum(rate(http_requests_total{
service="{{args.service-name}}"
}[5m]))
---
Production Setup Best Practices
1. High Availability Configuration
# HA values for Helm installation
controller:
replicas: 2
env:
- name: ARGOCD_CONTROLLER_REPLICAS
value: "2"
server:
replicas: 2
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 5
repoServer:
replicas: 2
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 5
redis-ha:
enabled: true
haproxy:
enabled: true
2. Resource Limits
controller:
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
repoServer:
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
server:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
3. AppProject Isolation
Restrict what each team can deploy:
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: team-backend
namespace: argocd
spec:
description: Backend team project
sourceRepos:
- "https://github.com/my-org/backend-*"
destinations:
- namespace: "backend-*"
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceBlacklist:
- group: ""
kind: ResourceQuota
- group: ""
kind: LimitRange
roles:
- name: developers
description: Backend developers
policies:
- p, proj:team-backend:developers, applications, sync, team-backend/*, allow
- p, proj:team-backend:developers, applications, get, team-backend/*, allow
groups:
- backend-developers
4. Notifications Configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
trigger.on-sync-failed: |
- when: app.status.sync.status == 'Unknown'
send: [app-sync-failed]
trigger.on-health-degraded: |
- when: app.status.health.status == 'Degraded'
send: [app-health-degraded]
template.app-sync-failed: |
slack:
attachments: |
[{
"color": "#E96D76",
"title": "{{.app.metadata.name}} sync failed",
"text": "Application {{.app.metadata.name}} sync failed.\nRevision: {{.app.status.sync.revision}}"
}]
template.app-health-degraded: |
slack:
attachments: |
[{
"color": "#f4c030",
"title": "{{.app.metadata.name}} health degraded",
"text": "Application {{.app.metadata.name}} is degraded."
}]
5. Repository Credentials Template
apiVersion: v1
kind: Secret
metadata:
name: private-repo-creds
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repo-creds
type: Opaque
stringData:
type: git
url: https://github.com/my-org
password: ghp_xxxxxxxxxxxxxxxxxxxx
username: argocd-bot
6. Performance Tuning
Key settings for large-scale deployments:
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cmd-params-cm
namespace: argocd
data:
# Increase repo server parallelism
reposerver.parallelism.limit: "50"
# Controller settings
controller.status.processors: "50"
controller.operation.processors: "25"
controller.repo.server.timeout.seconds: "300"
# Server settings
server.enable.gzip: "true"
---
Summary
ArgoCD transforms Kubernetes deployments from imperative scripts into a declarative, auditable, and self-healing process. Here is a quick decision guide:
| Scenario | Recommended Approach |
|---|---|
| Single team, few apps | Basic Application CRDs with manual sync |
| Multiple teams | AppProjects + RBAC + App-of-Apps |
| Dynamic environments | ApplicationSets with Git/cluster generators |
| Secrets in Git | External Secrets Operator or Sealed Secrets |
| Multi-cluster | Centralized ArgoCD + cluster registration |
| Progressive delivery | ArgoCD + Argo Rollouts |
| Production HA | Redis-HA + replicated components + monitoring |
Start simple with a single Application resource, then grow into app-of-apps and ApplicationSets as your platform matures. The GitOps model pays dividends in auditability, disaster recovery (redeploy everything from Git), and developer velocity once the initial patterns are established.
---
Frequently Asked Questions
What is ArgoCD and why should I use it for Kubernetes?
ArgoCD is a declarative GitOps continuous delivery tool for Kubernetes that automatically syncs your cluster state with Git repositories. It provides a visual UI showing deployment status, drift detection, and automated rollbacks. Use it when you want Git to be your single source of truth for Kubernetes deployments.
How do I fix ArgoCD sync failed status?
First check the sync status details in the ArgoCD UI or run argocd app get <app-name> to see specific error messages. Common causes include invalid YAML manifests, resource quota exceeded, or missing CRDs. Fix the issue in your Git repository and ArgoCD will automatically attempt to re-sync.
What is the difference between ArgoCD and Flux?
Both are GitOps tools for Kubernetes, but ArgoCD provides a rich web UI and application-centric model, while Flux is more lightweight and controller-based. ArgoCD offers better visualization and RBAC out of the box, whereas Flux integrates more tightly with Helm and has a smaller resource footprint. Choose ArgoCD for teams that value UI visibility and Flux for minimal overhead.
How do I handle secrets in ArgoCD GitOps workflows?
Never store plain-text secrets in Git. Use tools like Sealed Secrets, SOPS with age/GPG encryption, or External Secrets Operator that syncs secrets from AWS Secrets Manager or HashiCorp Vault. ArgoCD supports these through custom plugins or direct integration with external secret stores.
Can ArgoCD deploy to multiple clusters?
Yes, ArgoCD supports multi-cluster deployments from a single control plane. Register additional clusters using argocd cluster add <context-name> and reference the cluster in your Application spec's destination field. This enables centralized GitOps management across development, staging, and production clusters.