Complete Guide to GitOps with ArgoCD
Deploy Kubernetes applications with GitOps using ArgoCD. Covers installation, ApplicationSets, sync strategies, Helm/Kustomize, RBAC, and multi-cluster management.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Complete Guide to GitOps with ArgoCD
Introduction
GitOps uses Git as the single source of truth for infrastructure and application deployment. ArgoCD is a Kubernetes-native continuous delivery tool that implements GitOps — it watches a Git repository and syncs the desired state to Kubernetes clusters. This guide covers installation, application configuration, sync strategies, Helm/Kustomize integration, RBAC, and multi-cluster management.
GitOps Principles
- Declarative: System state is described declaratively in Git
- Versioned: Git provides versioning, history, and rollback
- Pulled: ArgoCD pulls changes from Git (no push from CI)
- Continuously reconciled: ArgoCD detects drift and corrects it
Installing ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd \
-f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Get admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# Port-forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
Basic Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/example/my-app-deploy
targetRevision: main
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
ApplicationSet (Multi-Environment)
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-app-multi-env
namespace: argocd
spec:
generators:
- list:
elements:
- env: dev
cluster: https://dev-cluster:6443
- env: staging
cluster: https://staging-cluster:6443
- env: prod
cluster: https://prod-cluster:6443
template:
metadata:
name: "my-app-{{env}}"
spec:
project: default
source:
repoURL: https://github.com/example/my-app-deploy
targetRevision: main
path: "manifests/{{env}}"
destination:
server: "{{cluster}}"
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Git Generator (Auto-discover repos)
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: auto-discover
namespace: argocd
spec:
generators:
- git:
repoURL: https://github.com/example/org-deployments
directories:
- path: "*/"
template:
metadata:
name: "{{path.basename}}"
spec:
project: default
source:
repoURL: https://github.com/example/org-deployments
targetRevision: main
path: "{{path}}"
destination:
server: https://kubernetes.default.svc
namespace: "{{path.basename}}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Helm Integration
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-helm
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/example/my-app-deploy
targetRevision: main
path: charts/my-app
helm:
valueFiles:
- values.yaml
- values-prod.yaml
parameters:
- name: image.tag
value: "v1.2.3"
- name: replicaCount
value: "3"
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
Kustomize Integration
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-kustomize
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/example/my-app-deploy
targetRevision: main
path: overlays/production
kustomize:
images:
- my-app:v1.2.3
commonLabels:
env: production
destination:
server: https://kubernetes.default.svc
namespace: my-app
syncPolicy:
automated:
prune: true
selfHeal: true
Sync Strategies
Automated sync
syncPolicy:
automated:
prune: true # Delete resources removed from Git
selfHeal: true # Revert manual changes
Manual sync with hooks
syncPolicy:
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
# Manual sync via CLI: argocd app sync my-app
Sync waves (ordered deployment)
# In your manifests, add annotations:
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0" # Runs first
---
metadata:
annotations:
argocd.argoproj.io/sync-wave: "1" # Runs second
---
metadata:
annotations:
argocd.argoproj.io/sync-wave: "2" # Runs third
Pre/Post sync 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: migration
image: my-app/migrate:v1.2.3
command: ["./migrate", "up"]
restartPolicy: Never
backoffLimit: 3
RBAC
# argocd-rbac-cm
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly
policy.csv: |
p, role:dev-team, applications, sync, dev/*, allow
p, role:dev-team, applications, get, dev/*, allow
p, role:dev-team, applications, action/override, dev/*, deny
p, role:ops-team, applications, *, */*, allow
g, dev-team, role:dev-team
g, ops-team, role:ops-team
Multi-Cluster Management
# Register a cluster with ArgoCD
argocd cluster add prod-cluster --label environment=production
# List registered clusters
argocd cluster list
# Deploy to a specific cluster
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-prod
namespace: argocd
spec:
source:
repoURL: https://github.com/example/my-app-deploy
targetRevision: main
path: overlays/production
destination:
server: https://prod-cluster:6443 # or name: prod-cluster
namespace: my-app
ArgoCD CLI
# Login
argocd login localhost:8080 --username admin --password <password>
# Application management
argocd app create my-app \
--repo https://github.com/example/my-app-deploy \
--path manifests \
--dest-server https://kubernetes.default.svc \
--dest-namespace my-app
argocd app sync my-app
argocd app get my-app
argocd app history my-app
argocd app rollback my-app <version>
argocd app delete my-app
# Diff between Git and cluster
argocd app diff my-app
Notifications
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-notifications-cm
namespace: argocd
data:
service.slack: |
token: $slack-token
trigger.on-deployed: |
- when: app.status.operationState.phase in ['Succeeded']
send: [app-deployed]
template.app-deployed: |
message: |
{{.app.metadata.name}} deployed to {{.app.status.sync.status}}
Best Practices
- Store manifests separately from app code — separate deploy repo or directory
- Use Kustomize overlays — base + per-environment overlays
- Enable selfHeal — revert manual kubectl changes automatically
- Use sync waves — ensure CRDs are applied before resources that use them
- Use AppProject for isolation — restrict which repos, clusters, and namespaces each team can access
- Pin ArgoCD version — do not use
latestin production - Use SSO for ArgoCD — integrate with GitHub, Google, or OIDC
- Monitor ArgoCD itself — it is critical infrastructure
- Use ApplicationSets for scale — avoid creating individual Applications for each microservice
- Enable notifications — alert on sync failures and drift detection
Common Mistakes
- Storing manifests in the app repo — coupling deploy frequency to app release frequency
- Not using selfHeal — manual changes persist and cause drift
- Giving all teams admin access — use RBAC with least privilege
- Not using sync waves — CRDs and operators fail if applied out of order
- Not testing sync before merging — use ArgoCD diff or
argocd app sync --dry-run - Using automated sync in production without canary — consider manual sync for prod
- Not monitoring drift — enable notifications for out-of-sync status
- Forgetting to prune — old resources persist if
prune: false
Frequently Asked Questions
How is GitOps different from CI/CD?
CI builds and tests code. GitOps handles CD — deployment to environments. In GitOps, the deployment is triggered by Git changes, not by CI pushing to a cluster. CI builds images and updates manifests in Git; ArgoCD picks up the manifest change and deploys.
Should I use automated sync in production?
It depends. Automated sync with selfHeal is great for dev and staging. For production, consider manual sync with approval gates, or automated sync with progressive delivery (Argo Rollouts) for canary/blue-green deployments.
How do I handle secrets in GitOps?
Do not store plain secrets in Git. Use Sealed Secrets, SOPS, or External Secrets Operator. These encrypt secrets at rest in Git and decrypt them in the cluster. ArgoCD syncs the encrypted version; the operator handles decryption.